From 335ebfc659a24bddff884c5af31b3dd8d92a0f55 Mon Sep 17 00:00:00 2001 From: ajafri2001 Date: Fri, 28 Mar 2025 05:18:47 +0530 Subject: [PATCH 1/2] Fix : Error message now shows def instead of val properly --- .../dotty/tools/dotc/core/Denotations.scala | 1445 ++++++++++------- .../tools/dotc/reporting/ErrorMessageID.scala | 49 +- .../dotty/tools/dotc/reporting/messages.scala | 13 + .../src/dotty/tools/dotc/typer/Dynamic.scala | 334 ++-- .../src/dotty/tools/dotc/typer/Typer.scala | 13 +- 5 files changed, 1126 insertions(+), 728 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Denotations.scala b/compiler/src/dotty/tools/dotc/core/Denotations.scala index 614a8b691c92..fd30e3f69971 100644 --- a/compiler/src/dotty/tools/dotc/core/Denotations.scala +++ b/compiler/src/dotty/tools/dotc/core/Denotations.scala @@ -2,7 +2,15 @@ package dotty.tools package dotc package core -import SymDenotations.{ SymDenotation, ClassDenotation, NoDenotation, LazyType, stillValid, acceptStale, traceInvalid } +import SymDenotations.{ + SymDenotation, + ClassDenotation, + NoDenotation, + LazyType, + stillValid, + acceptStale, + traceInvalid +} import Contexts.* import Names.* import NameKinds.* @@ -28,56 +36,57 @@ import collection.mutable.ListBuffer import scala.compiletime.uninitialized /** Denotations represent the meaning of symbols and named types. - * The following diagram shows how the principal types of denotations - * and their denoting entities relate to each other. Lines ending in - * a down-arrow `v` are member methods. The two methods shown in the diagram are - * "symbol" and "deref". Both methods are parameterized by the current context, - * and are effectively indexed by current period. - * - * Lines ending in a horizontal line mean subtyping (right is a subtype of left). - * - * NamedType - * | Symbol---------ClassSymbol - * | | | - * | denot | denot | denot - * v v v - * Denotation-+-----SingleDenotation-+------SymDenotation-+----ClassDenotation - * | | - * +-----MultiDenotation | - * | - * +--UniqueRefDenotation - * +--JointRefDenotation - * - * Here's a short summary of the classes in this diagram. - * - * NamedType A type consisting of a prefix type and a name, with fields - * prefix: Type - * name: Name - * It has two subtypes: TermRef and TypeRef - * Symbol A label for a definition or declaration in one compiler run - * ClassSymbol A symbol representing a class - * Denotation The meaning of a named type or symbol during a period - * MultiDenotation A denotation representing several overloaded members - * SingleDenotation A denotation representing a non-overloaded member or definition, with main fields - * symbol: Symbol - * info: Type - * UniqueRefDenotation A denotation referring to a single definition with some member type - * JointRefDenotation A denotation referring to a member that could resolve to several definitions - * SymDenotation A denotation representing a single definition with its original type, with main fields - * name: Name - * owner: Symbol - * flags: Flags - * privateWithin: Symbol - * annotations: List[Annotation] - * ClassDenotation A denotation representing a single class definition. - */ + * The following diagram shows how the principal types of denotations + * and their denoting entities relate to each other. Lines ending in + * a down-arrow `v` are member methods. The two methods shown in the diagram are + * "symbol" and "deref". Both methods are parameterized by the current context, + * and are effectively indexed by current period. + * + * Lines ending in a horizontal line mean subtyping (right is a subtype of left). + * + * NamedType + * | Symbol---------ClassSymbol + * | | | + * | denot | denot | denot + * v v v + * Denotation-+-----SingleDenotation-+------SymDenotation-+----ClassDenotation + * | | + * +-----MultiDenotation | + * | + * +--UniqueRefDenotation + * +--JointRefDenotation + * + * Here's a short summary of the classes in this diagram. + * + * NamedType A type consisting of a prefix type and a name, with fields + * prefix: Type + * name: Name + * It has two subtypes: TermRef and TypeRef + * Symbol A label for a definition or declaration in one compiler run + * ClassSymbol A symbol representing a class + * Denotation The meaning of a named type or symbol during a period + * MultiDenotation A denotation representing several overloaded members + * SingleDenotation A denotation representing a non-overloaded member or definition, with main fields + * symbol: Symbol + * info: Type + * UniqueRefDenotation A denotation referring to a single definition with some member type + * JointRefDenotation A denotation referring to a member that could resolve to several definitions + * SymDenotation A denotation representing a single definition with its original type, with main fields + * name: Name + * owner: Symbol + * flags: Flags + * privateWithin: Symbol + * annotations: List[Annotation] + * ClassDenotation A denotation representing a single class definition. + */ object Denotations { implicit def eqDenotation: CanEqual[Denotation, Denotation] = CanEqual.derived - /** A PreDenotation represents a group of single denotations or a single multi-denotation - * It is used as an optimization to avoid forming MultiDenotations too eagerly. - */ + /** A PreDenotation represents a group of single denotations or a single + * multi-denotation It is used as an optimization to avoid forming + * MultiDenotations too eagerly. + */ abstract class PreDenotation { /** A denotation in the group exists */ @@ -96,32 +105,43 @@ object Denotations { /** Group contains a denotation with the same signature as `other` */ def matches(other: SingleDenotation)(using Context): Boolean - /** Keep only those denotations in this group which satisfy predicate `p`. */ + /** Keep only those denotations in this group which satisfy predicate `p`. + */ def filterWithPredicate(p: SingleDenotation => Boolean): PreDenotation - /** Keep only those denotations in this group which have a signature - * that's not already defined by `denots`. - */ + /** Keep only those denotations in this group which have a signature that's + * not already defined by `denots`. + */ def filterDisjoint(denots: PreDenotation)(using Context): PreDenotation - /** Keep only those inherited members M of this predenotation for which the following is true - * - M is not marked Private - * - If M has a unique symbol, it does not appear in `prevDenots`. - * - M's signature as seen from prefix `pre` does not appear in `ownDenots` - * Return the denotation as seen from `pre`. - * Called from SymDenotations.computeMember. There, `ownDenots` are the denotations found in - * the base class, which shadow any inherited denotations with the same signature. - * `prevDenots` are the denotations that are defined in the class or inherited from - * a base type which comes earlier in the linearization. - */ - def mapInherited(ownDenots: PreDenotation, prevDenots: PreDenotation, pre: Type)(using Context): PreDenotation - - /** Keep only those denotations in this group that have all of the flags in `required`, - * but none of the flags in `excluded`. - */ - def filterWithFlags(required: FlagSet, excluded: FlagSet)(using Context): PreDenotation - - /** Map `f` over all single denotations and aggregate the results with `g`. */ + /** Keep only those inherited members M of this predenotation for which the + * following is true + * - M is not marked Private + * - If M has a unique symbol, it does not appear in `prevDenots`. + * - M's signature as seen from prefix `pre` does not appear in + * `ownDenots` + * Return the denotation as seen from `pre`. Called from + * SymDenotations.computeMember. There, `ownDenots` are the denotations + * found in the base class, which shadow any inherited denotations with the + * same signature. `prevDenots` are the denotations that are defined in the + * class or inherited from a base type which comes earlier in the + * linearization. + */ + def mapInherited( + ownDenots: PreDenotation, + prevDenots: PreDenotation, + pre: Type + )(using Context): PreDenotation + + /** Keep only those denotations in this group that have all of the flags in + * `required`, but none of the flags in `excluded`. + */ + def filterWithFlags(required: FlagSet, excluded: FlagSet)(using + Context + ): PreDenotation + + /** Map `f` over all single denotations and aggregate the results with `g`. + */ def aggregate[T](f: SingleDenotation => T, g: (T, T) => T): T private var cachedPrefix: Type = uninitialized @@ -139,8 +159,7 @@ object Denotations { validAsSeenFrom = if (pre.isProvisional) Nowhere else ctx.period } cachedAsSeenFrom - } - else computeAsSeenFrom(pre) + } else computeAsSeenFrom(pre) protected def computeAsSeenFrom(pre: Type)(using Context): AsSeenFromResult @@ -151,50 +170,54 @@ object Denotations { else DenotUnion(this, that) } - /** A denotation is the result of resolving - * a name (either simple identifier or select) during a given period. - * - * Denotations can be combined with `&` and `|`. - * & is conjunction, | is disjunction. - * - * `&` will create an overloaded denotation from two - * non-overloaded denotations if their signatures differ. - * Analogously `|` of two denotations with different signatures will give - * an empty denotation `NoDenotation`. - * - * A denotation might refer to `NoSymbol`. This is the case if the denotation - * was produced from a disjunction of two denotations with different symbols - * and there was no common symbol in a superclass that could substitute for - * both symbols. Here is an example: - * - * Say, we have: - * - * class A { def f: A } - * class B { def f: B } - * val x: A | B = if (test) new A else new B - * val y = x.f - * - * Then the denotation of `y` is `SingleDenotation(NoSymbol, A | B)`. - * - * @param symbol The referencing symbol, or NoSymbol is none exists - */ - abstract class Denotation(val symbol: Symbol, protected var myInfo: Type, val isType: Boolean) extends PreDenotation with printing.Showable { + /** A denotation is the result of resolving a name (either simple identifier + * or select) during a given period. + * + * Denotations can be combined with `&` and `|`. & is conjunction, | is + * disjunction. + * + * `&` will create an overloaded denotation from two non-overloaded + * denotations if their signatures differ. Analogously `|` of two denotations + * with different signatures will give an empty denotation `NoDenotation`. + * + * A denotation might refer to `NoSymbol`. This is the case if the denotation + * was produced from a disjunction of two denotations with different symbols + * and there was no common symbol in a superclass that could substitute for + * both symbols. Here is an example: + * + * Say, we have: + * + * class A { def f: A } class B { def f: B } val x: A | B = if (test) new A + * else new B val y = x.f + * + * Then the denotation of `y` is `SingleDenotation(NoSymbol, A | B)`. + * + * @param symbol + * The referencing symbol, or NoSymbol is none exists + */ + abstract class Denotation( + val symbol: Symbol, + protected var myInfo: Type, + val isType: Boolean + ) extends PreDenotation + with printing.Showable { type AsSeenFromResult <: Denotation - /** The type info. - * The info is an instance of TypeType iff this is a type denotation - * Uncompleted denotations set myInfo to a LazyType. - */ + /** The type info. The info is an instance of TypeType iff this is a type + * denotation Uncompleted denotations set myInfo to a LazyType. + */ final def info(using Context): Type = { def completeInfo = { // Written this way so that `info` is small enough to be inlined - this.asInstanceOf[SymDenotation].completeFrom(myInfo.asInstanceOf[LazyType]); info + this + .asInstanceOf[SymDenotation] + .completeFrom(myInfo.asInstanceOf[LazyType]); info } if (myInfo.isInstanceOf[LazyType]) completeInfo else myInfo } - /** The type info, or, if this is a SymDenotation where the symbol - * is not yet completed, the completer - */ + /** The type info, or, if this is a SymDenotation where the symbol is not + * yet completed, the completer + */ def infoOrCompleter: Type /** Is this a reference to a term symbol? */ @@ -203,9 +226,9 @@ object Denotations { /** Is this denotation overloaded? */ final def isOverloaded: Boolean = isInstanceOf[MultiDenotation] - /** Denotation points to unique symbol; false for overloaded denotations - * and JointRef denotations. - */ + /** Denotation points to unique symbol; false for overloaded denotations and + * JointRef denotations. + */ def hasUniqueSym: Boolean /** The name of the denotation */ @@ -215,15 +238,21 @@ object Denotations { def signature(using Context): Signature /** Resolve overloaded denotation to pick the ones with the given signature - * when seen from prefix `site`. - * @param relaxed When true, consider only parameter signatures for a match. - */ - def atSignature(sig: Signature, targetName: Name, site: Type = NoPrefix, relaxed: Boolean = false)(using Context): Denotation - - /** The variant of this denotation that's current in the given context. - * If no such denotation exists, returns the denotation with each alternative - * at its first point of definition. - */ + * when seen from prefix `site`. + * @param relaxed + * When true, consider only parameter signatures for a match. + */ + def atSignature( + sig: Signature, + targetName: Name, + site: Type = NoPrefix, + relaxed: Boolean = false + )(using Context): Denotation + + /** The variant of this denotation that's current in the given context. If + * no such denotation exists, returns the denotation with each alternative + * at its first point of definition. + */ def current(using Context): Denotation /** The period during which this denotation is valid. */ @@ -242,7 +271,8 @@ object Denotations { def mapInfo(f: Type => Type)(using Context): Denotation /** If this denotation does not exist, fallback to alternative */ - inline def orElse(inline that: Denotation): Denotation = if (this.exists) this else that + inline def orElse(inline that: Denotation): Denotation = + if (this.exists) this else that /** The set of alternative single-denotations making up this denotation */ final def alternatives: List[SingleDenotation] = altsWith(alwaysTrue) @@ -250,10 +280,11 @@ object Denotations { /** The alternatives of this denotation that satisfy the predicate `p`. */ def altsWith(p: Symbol => Boolean): List[SingleDenotation] - /** The unique alternative of this denotation that satisfies the predicate `p`, - * or NoDenotation if no satisfying alternative exists. - * @throws TypeError if there is at more than one alternative that satisfies `p`. - */ + /** The unique alternative of this denotation that satisfies the predicate + * `p`, or NoDenotation if no satisfying alternative exists. + * @throws TypeError + * if there is at more than one alternative that satisfies `p`. + */ def suchThat(p: Symbol => Boolean)(using Context): SingleDenotation override def filterWithPredicate(p: SingleDenotation => Boolean): Denotation @@ -261,47 +292,59 @@ object Denotations { /** If this is a SingleDenotation, return it, otherwise throw a TypeError */ def checkUnique(using Context): SingleDenotation = suchThat(alwaysTrue) - /** Does this denotation have an alternative that satisfies the predicate `p`? */ + /** Does this denotation have an alternative that satisfies the predicate + * `p`? + */ def hasAltWith(p: SingleDenotation => Boolean): Boolean - inline final def hasAltWithInline(inline p: SingleDenotation => Boolean): Boolean = inline this match + inline final def hasAltWithInline( + inline p: SingleDenotation => Boolean + ): Boolean = inline this match case mbr: SingleDenotation => mbr.exists && p(mbr) - case mbr => mbr.hasAltWith(p) - - /** The denotation made up from the alternatives of this denotation that - * are accessible from prefix `pre`, or NoDenotation if no accessible alternative exists. - */ - def accessibleFrom(pre: Type, superAccess: Boolean = false)(using Context): Denotation - - /** Find member of this denotation with given `name`, all `required` - * flags and no `excluded` flag, and produce a denotation that contains the type of the member - * as seen from given prefix `pre`. - */ - def findMember(name: Name, pre: Type, required: FlagSet, excluded: FlagSet)(using Context): Denotation = + case mbr => mbr.hasAltWith(p) + + /** The denotation made up from the alternatives of this denotation that are + * accessible from prefix `pre`, or NoDenotation if no accessible + * alternative exists. + */ + def accessibleFrom(pre: Type, superAccess: Boolean = false)(using + Context + ): Denotation + + /** Find member of this denotation with given `name`, all `required` flags + * and no `excluded` flag, and produce a denotation that contains the type + * of the member as seen from given prefix `pre`. + */ + def findMember(name: Name, pre: Type, required: FlagSet, excluded: FlagSet)( + using Context + ): Denotation = info.findMember(name, pre, required, excluded) - /** If this denotation is overloaded, filter with given predicate. - * If result is still overloaded throw a TypeError. - * Note: disambiguate is slightly different from suchThat in that - * single-denotations that do not satisfy the predicate are left alone - * (whereas suchThat would map them to NoDenotation). - */ - inline def disambiguate(inline p: Symbol => Boolean)(using Context): SingleDenotation = this match { + /** If this denotation is overloaded, filter with given predicate. If result + * is still overloaded throw a TypeError. Note: disambiguate is slightly + * different from suchThat in that single-denotations that do not satisfy + * the predicate are left alone (whereas suchThat would map them to + * NoDenotation). + */ + inline def disambiguate( + inline p: Symbol => Boolean + )(using Context): SingleDenotation = this match { case sdenot: SingleDenotation => sdenot case mdenot => suchThat(p) orElse NoQualifyingRef(alternatives) } - /** Return symbol in this denotation that satisfies the given predicate. - * if generateStubs is specified, return a stubsymbol if denotation is a missing ref. - * Throw a `TypeError` if predicate fails to disambiguate symbol or no alternative matches. - */ - def requiredSymbol(kind: String, - name: Name, - site: Denotation = NoDenotation, - args: List[Type] = Nil, - generateStubs: Boolean = true) - (p: Symbol => Boolean) - (using Context): Symbol = + /** Return symbol in this denotation that satisfies the given predicate. if + * generateStubs is specified, return a stubsymbol if denotation is a + * missing ref. Throw a `TypeError` if predicate fails to disambiguate + * symbol or no alternative matches. + */ + def requiredSymbol( + kind: String, + name: Name, + site: Denotation = NoDenotation, + args: List[Type] = Nil, + generateStubs: Boolean = true + )(p: Symbol => Boolean)(using Context): Symbol = disambiguate(p) match { case m @ MissingRef(ownerd, name) if generateStubs => if ctx.settings.YdebugMissingRefs.value then m.ex.printStackTrace() @@ -309,7 +352,8 @@ object Denotations { case NoDenotation | _: NoQualifyingRef | _: MissingRef => def argStr = if (args.isEmpty) "" else i" matching ($args%, %)" val msg = - if site.exists then em"$site does not have a member $kind $name$argStr" + if site.exists then + em"$site does not have a member $kind $name$argStr" else em"missing: $kind $name$argStr" throw TypeError(msg) case denot => @@ -318,28 +362,42 @@ object Denotations { def requiredMethod(pname: PreName)(using Context): TermSymbol = { val name = pname.toTermName - info.member(name).requiredSymbol("method", name, this)(_.is(Method)).asTerm + info + .member(name) + .requiredSymbol("method", name, this)(_.is(Method)) + .asTerm } def requiredMethodRef(name: PreName)(using Context): TermRef = requiredMethod(name).termRef - def requiredMethod(pname: PreName, argTypes: List[Type])(using Context): TermSymbol = { + def requiredMethod(pname: PreName, argTypes: List[Type])(using + Context + ): TermSymbol = { val name = pname.toTermName - info.member(name).requiredSymbol("method", name, this, argTypes) { x => - x.is(Method) && { - x.info.paramInfoss match { - case paramInfos :: Nil => paramInfos.corresponds(argTypes)(_ =:= _) - case _ => false + info + .member(name) + .requiredSymbol("method", name, this, argTypes) { x => + x.is(Method) && { + x.info.paramInfoss match { + case paramInfos :: Nil => + paramInfos.corresponds(argTypes)(_ =:= _) + case _ => false + } } } - }.asTerm + .asTerm } - def requiredMethodRef(name: PreName, argTypes: List[Type])(using Context): TermRef = + def requiredMethodRef(name: PreName, argTypes: List[Type])(using + Context + ): TermRef = requiredMethod(name, argTypes).termRef def requiredValue(pname: PreName)(using Context): TermSymbol = { val name = pname.toTermName - info.member(name).requiredSymbol("field or getter", name, this)(_.info.isParameterless).asTerm + info + .member(name) + .requiredSymbol("field or getter", name, this)(_.info.isParameterless) + .asTerm } def requiredValueRef(name: PreName)(using Context): TermRef = requiredValue(name).termRef @@ -354,16 +412,26 @@ object Denotations { info.member(name).requiredSymbol("type", name, this)(_.isType).asType } - /** The alternative of this denotation that has a type matching `targetType` when seen - * as a member of type `site` and that has a target name matching `targetName`, or - * `NoDenotation` if none exists. - */ - def matchingDenotation(site: Type, targetType: Type, targetName: Name)(using Context): SingleDenotation = { + /** The alternative of this denotation that has a type matching `targetType` + * when seen as a member of type `site` and that has a target name matching + * `targetName`, or `NoDenotation` if none exists. + */ + def matchingDenotation(site: Type, targetType: Type, targetName: Name)(using + Context + ): SingleDenotation = { def qualifies(sym: Symbol) = - site.memberInfo(sym).matchesLoosely(targetType) && sym.hasTargetName(targetName) + site.memberInfo(sym).matchesLoosely(targetType) && sym.hasTargetName( + targetName + ) if (isOverloaded) - atSignature(targetType.signature, targetName, site, relaxed = true) match { - case sd: SingleDenotation => sd.matchingDenotation(site, targetType, targetName) + atSignature( + targetType.signature, + targetName, + site, + relaxed = true + ) match { + case sd: SingleDenotation => + sd.matchingDenotation(site, targetType, targetName) case md => md.suchThat(qualifies(_)) } else if (exists && !qualifies(symbol)) NoDenotation @@ -371,53 +439,60 @@ object Denotations { } /** Form a denotation by conjoining with denotation `that`. - * - * NoDenotations are dropped. MultiDenotations are handled by merging - * parts with same signatures. SingleDenotations with equal signatures - * are joined by following this sequence of steps: - * - * 1. If exactly one the denotations has an inaccessible symbol, pick the other one. - * 2. Otherwise, if one of the infos overrides the other one, and the associated - * symbol does not score strictly lower than the other one, - * pick the associated denotation. - * 3. Otherwise, if the two infos can be combined with `infoMeet`, pick that as - * result info, and pick the symbol that scores higher as result symbol, - * or pick `sym1` as a tie breaker. The picked info and symbol are combined - * in a JointDenotation. - * 4. Otherwise, if one of the two symbols scores strongly higher than the - * other one, pick the associated denotation. - * 5. Otherwise return a multi-denotation consisting of both denotations. - * - * Symbol scoring is determined according to the following ranking - * where earlier criteria trump later ones. Cases marked with (*) - * give a strong score advantage, the others a weak one. - * - * 1. The symbol exists, and the other one does not. (*) - * 2. The symbol is not a bridge, but the other one is. (*) - * 3. The symbol is concrete, and the other one is deferred - * 4. The symbol appears before the other in the linearization of `pre` - * 5. The symbol's visibility is strictly greater than the other one's. - * 6. The symbol is a method, but the other one is not. - */ - def meet(that: Denotation, pre: Type, safeIntersection: Boolean = false)(using Context): Denotation = { + * + * NoDenotations are dropped. MultiDenotations are handled by merging parts + * with same signatures. SingleDenotations with equal signatures are joined + * by following this sequence of steps: + * + * 1. If exactly one the denotations has an inaccessible symbol, pick the + * other one. 2. Otherwise, if one of the infos overrides the other + * one, and the associated symbol does not score strictly lower than + * the other one, pick the associated denotation. 3. Otherwise, if the + * two infos can be combined with `infoMeet`, pick that as result + * info, and pick the symbol that scores higher as result symbol, or + * pick `sym1` as a tie breaker. The picked info and symbol are + * combined in a JointDenotation. 4. Otherwise, if one of the two + * symbols scores strongly higher than the other one, pick the + * associated denotation. 5. Otherwise return a multi-denotation + * consisting of both denotations. + * + * Symbol scoring is determined according to the following ranking where + * earlier criteria trump later ones. Cases marked with (*) give a strong + * score advantage, the others a weak one. + * + * 1. The symbol exists, and the other one does not. (*) 2. The symbol is + * not a bridge, but the other oneis. (*) 3. The symbol is concrete, + * and the other one is deferred 4. The symbol appears before the + * other in the linearization of `pre` 5. The symbol's visibility is + * strictly greater than the other one's. 6. The symbol is a method, + * but the other one is not. + */ + def meet(that: Denotation, pre: Type, safeIntersection: Boolean = false)( + using Context + ): Denotation = { + /** Try to merge denot1 and denot2 without adding a new signature. */ - def mergeDenot(denot1: Denotation, denot2: SingleDenotation): Denotation = denot1 match { - case denot1 @ MultiDenotation(denot11, denot12) => - val d1 = mergeDenot(denot11, denot2) - if (d1.exists) denot1.derivedUnionDenotation(d1, denot12) - else { - val d2 = mergeDenot(denot12, denot2) - if (d2.exists) denot1.derivedUnionDenotation(denot11, d2) + def mergeDenot(denot1: Denotation, denot2: SingleDenotation): Denotation = + denot1 match { + case denot1 @ MultiDenotation(denot11, denot12) => + val d1 = mergeDenot(denot11, denot2) + if (d1.exists) denot1.derivedUnionDenotation(d1, denot12) + else { + val d2 = mergeDenot(denot12, denot2) + if (d2.exists) denot1.derivedUnionDenotation(denot11, d2) + else NoDenotation + } + case denot1: SingleDenotation => + if (denot1 eq denot2) denot1 + else if denot1.matches(denot2) then mergeSingleDenot(denot1, denot2) else NoDenotation - } - case denot1: SingleDenotation => - if (denot1 eq denot2) denot1 - else if denot1.matches(denot2) then mergeSingleDenot(denot1, denot2) - else NoDenotation - } + } /** Try to merge single-denotations. */ - def mergeSingleDenot(denot1: SingleDenotation, denot2: SingleDenotation): Denotation = + def mergeSingleDenot( + denot1: SingleDenotation, + denot2: SingleDenotation + ): Denotation = val info1 = denot1.info val info2 = denot2.info val sym1 = denot1.symbol @@ -439,12 +514,16 @@ object Denotations { else searchBaseClasses(pre.baseClasses) end linearScore - /** Similar to SymDenotation#accessBoundary, but without the special cases. */ + /** Similar to SymDenotation#accessBoundary, but without the special + * cases. + */ def accessBoundary(sym: Symbol) = if (sym.is(Private)) sym.owner - else sym.privateWithin.orElse( - if (sym.is(Protected)) sym.owner.enclosingPackageClass - else defn.RootClass) + else + sym.privateWithin.orElse( + if (sym.is(Protected)) sym.owner.enclosingPackageClass + else defn.RootClass + ) def isHidden(sym: Symbol) = sym.exists && !sym.isAccessibleFrom(pre) // In typer phase filter out denotations with symbols that are not @@ -478,37 +557,56 @@ object Denotations { else if sym1.is(Method) && !sym2.is(Method) then 1 else 0 - val matchLoosely = sym1.matchNullaryLoosely || sym2.matchNullaryLoosely - - if symScore <= 0 && info2.overrides(info1, matchLoosely, checkClassInfo = false) then - denot2 - else if symScore >= 0 && info1.overrides(info2, matchLoosely, checkClassInfo = false) then - denot1 + val matchLoosely = + sym1.matchNullaryLoosely || sym2.matchNullaryLoosely + + if symScore <= 0 && info2.overrides( + info1, + matchLoosely, + checkClassInfo = false + ) + then denot2 + else if symScore >= 0 && info1.overrides( + info2, + matchLoosely, + checkClassInfo = false + ) + then denot1 else val jointInfo = infoMeet(info1, info2, safeIntersection) if jointInfo.exists then val sym = if symScore >= 0 then sym1 else sym2 - JointRefDenotation(sym, jointInfo, denot1.validFor & denot2.validFor, pre, denot1.isRefinedMethod || denot2.isRefinedMethod) + JointRefDenotation( + sym, + jointInfo, + denot1.validFor & denot2.validFor, + pre, + denot1.isRefinedMethod || denot2.isRefinedMethod + ) else if symScore == 2 then denot1 else if symScore == -2 then denot2 else - overload.println(i"overloaded with same signature: ${sym1.showLocated}: $info1 / ${sym2.showLocated}: $info2, info = ${info1.getClass}, ${info2.getClass}, $jointInfo") + overload.println( + i"overloaded with same signature: ${sym1.showLocated}: $info1 / ${sym2.showLocated}: $info2, info = ${info1.getClass}, ${info2.getClass}, $jointInfo" + ) MultiDenotation(denot1, denot2) end mergeSingleDenot if (this eq that) this else if (!this.exists) that else if (!that.exists) this - else that match { - case that: SingleDenotation => - val r = mergeDenot(this, that) - if (r.exists) r else MultiDenotation(this, that) - case that @ MultiDenotation(denot1, denot2) => - this.meet(denot1, pre).meet(denot2, pre) - } + else + that match { + case that: SingleDenotation => + val r = mergeDenot(this, that) + if (r.exists) r else MultiDenotation(this, that) + case that @ MultiDenotation(denot1, denot2) => + this.meet(denot1, pre).meet(denot2, pre) + } } - final def asSingleDenotation: SingleDenotation = asInstanceOf[SingleDenotation] + final def asSingleDenotation: SingleDenotation = + asInstanceOf[SingleDenotation] final def asSymDenotation: SymDenotation = asInstanceOf[SymDenotation] def toText(printer: Printer): Text = printer.toText(this) @@ -516,123 +614,176 @@ object Denotations { // ------ PreDenotation ops ---------------------------------------------- final def toDenot(pre: Type)(using Context): Denotation = this - final def containsSym(sym: Symbol): Boolean = hasUniqueSym && (symbol eq sym) + final def containsSym(sym: Symbol): Boolean = + hasUniqueSym && (symbol eq sym) } // ------ Info meets ---------------------------------------------------- - /** Merge parameter names of lambda types. If names in corresponding positions match, keep them, - * otherwise generate new synthetic names. + /** Merge parameter names of lambda types. If names in corresponding positions + * match, keep them, otherwise generate new synthetic names. */ - private def mergeParamNames(tp1: LambdaType, tp2: LambdaType): List[tp1.ThisName] = - (for ((name1, name2, idx) <- tp1.paramNames.lazyZip(tp2.paramNames).lazyZip(tp1.paramNames.indices)) - yield if (name1 == name2) name1 else tp1.companion.syntheticParamName(idx)).toList - - /** Normally, `tp1 & tp2`, with extra care taken to return `tp1` or `tp2` directly if that's - * a valid answer. Special cases for matching methods and classes, with - * the possibility of returning NoType. Special handling of ExprTypes, where mixed - * intersections widen the ExprType away. - */ - def infoMeet(tp1: Type, tp2: Type, safeIntersection: Boolean)(using Context): Type = + private def mergeParamNames( + tp1: LambdaType, + tp2: LambdaType + ): List[tp1.ThisName] = + (for ( + (name1, name2, idx) <- tp1.paramNames + .lazyZip(tp2.paramNames) + .lazyZip(tp1.paramNames.indices) + ) + yield + if (name1 == name2) name1 + else tp1.companion.syntheticParamName(idx)).toList + + /** Normally, `tp1 & tp2`, with extra care taken to return `tp1` or `tp2` + * directly if that's a valid answer. Special cases for matching methods and + * classes, with the possibility of returning NoType. Special handling of + * ExprTypes, where mixed intersections widen the ExprType away. + */ + def infoMeet(tp1: Type, tp2: Type, safeIntersection: Boolean)(using + Context + ): Type = if tp1 eq tp2 then tp1 - else tp1 match - case tp1: TypeBounds => - tp2 match - case tp2: TypeBounds => if safeIntersection then tp1 safe_& tp2 else tp1 & tp2 - case tp2: ClassInfo => tp2 - case _ => NoType - case tp1: ClassInfo => - tp2 match - case tp2: ClassInfo if tp1.cls eq tp2.cls => tp1.derivedClassInfo(tp1.prefix & tp2.prefix) - case tp2: TypeBounds => tp1 - case _ => NoType - case tp1: MethodType => - tp2 match - case tp2: MethodType - if TypeComparer.matchingMethodParams(tp1, tp2) - && tp1.isImplicitMethod == tp2.isImplicitMethod => - val resType = infoMeet(tp1.resType, tp2.resType.subst(tp2, tp1), safeIntersection) - if resType.exists then - tp1.derivedLambdaType(mergeParamNames(tp1, tp2), tp1.paramInfos, resType) - else NoType - case _ => NoType - case tp1: PolyType => - tp2 match - case tp2: PolyType if tp1.paramNames.hasSameLengthAs(tp2.paramNames) => - val resType = infoMeet(tp1.resType, tp2.resType.subst(tp2, tp1), safeIntersection) - if resType.exists then - tp1.derivedLambdaType( - mergeParamNames(tp1, tp2), - tp1.paramInfos.zipWithConserve(tp2.paramInfos)( _ & _ ), - resType) - else NoType - case _ => NoType - case ExprType(rtp1) => - tp2 match - case ExprType(rtp2) => ExprType(rtp1 & rtp2) - case _ => infoMeet(rtp1, tp2, safeIntersection) - case _ => - tp2 match - case _: MethodType | _: PolyType => NoType - case _ => tp1 & tp2.widenExpr + else + tp1 match + case tp1: TypeBounds => + tp2 match + case tp2: TypeBounds => + if safeIntersection then tp1 safe_& tp2 else tp1 & tp2 + case tp2: ClassInfo => tp2 + case _ => NoType + case tp1: ClassInfo => + tp2 match + case tp2: ClassInfo if tp1.cls eq tp2.cls => + tp1.derivedClassInfo(tp1.prefix & tp2.prefix) + case tp2: TypeBounds => tp1 + case _ => NoType + case tp1: MethodType => + tp2 match + case tp2: MethodType + if TypeComparer.matchingMethodParams(tp1, tp2) + && tp1.isImplicitMethod == tp2.isImplicitMethod => + val resType = infoMeet( + tp1.resType, + tp2.resType.subst(tp2, tp1), + safeIntersection + ) + if resType.exists then + tp1.derivedLambdaType( + mergeParamNames(tp1, tp2), + tp1.paramInfos, + resType + ) + else NoType + case _ => NoType + case tp1: PolyType => + tp2 match + case tp2: PolyType + if tp1.paramNames.hasSameLengthAs(tp2.paramNames) => + val resType = infoMeet( + tp1.resType, + tp2.resType.subst(tp2, tp1), + safeIntersection + ) + if resType.exists then + tp1.derivedLambdaType( + mergeParamNames(tp1, tp2), + tp1.paramInfos.zipWithConserve(tp2.paramInfos)(_ & _), + resType + ) + else NoType + case _ => NoType + case ExprType(rtp1) => + tp2 match + case ExprType(rtp2) => ExprType(rtp1 & rtp2) + case _ => infoMeet(rtp1, tp2, safeIntersection) + case _ => + tp2 match + case _: MethodType | _: PolyType => NoType + case _ => tp1 & tp2.widenExpr end infoMeet /** A non-overloaded denotation */ - abstract class SingleDenotation(symbol: Symbol, initInfo: Type, isType: Boolean) extends Denotation(symbol, initInfo, isType) { - protected def newLikeThis(symbol: Symbol, info: Type, pre: Type, isRefinedMethod: Boolean)(using Context): SingleDenotation + abstract class SingleDenotation( + symbol: Symbol, + initInfo: Type, + isType: Boolean + ) extends Denotation(symbol, initInfo, isType) { + protected def newLikeThis( + symbol: Symbol, + info: Type, + pre: Type, + isRefinedMethod: Boolean + )(using Context): SingleDenotation final def name(using Context): Name = symbol.name - /** For SymDenotation, this is NoPrefix. For other denotations this is the prefix - * under which the denotation was constructed. - * - * Note that `asSeenFrom` might return a `SymDenotation` and therefore in - * general one cannot rely on `prefix` being set, see - * `Config.reuseSymDenotations` for details. - */ + /** For SymDenotation, this is NoPrefix. For other denotations this is the + * prefix under which the denotation was constructed. + * + * Note that `asSeenFrom` might return a `SymDenotation` and therefore in + * general one cannot rely on `prefix` being set, see + * `Config.reuseSymDenotations` for details. + */ def prefix: Type = NoPrefix /** True if the info of this denotation comes from a refinement. */ def isRefinedMethod: Boolean = false - /** For SymDenotations, the language-specific signature of the info, depending on - * where the symbol is defined. For non-SymDenotations, the Scala 3 - * signature. - * - * Invariants: - * - Before erasure, the signature of a denotation is always equal to the - * signature of its corresponding initial denotation. - * - Two distinct overloads will have SymDenotations with distinct - * signatures (the SELECTin tag in Tasty relies on this to refer to an - * overload unambiguously). Note that this only applies to - * SymDenotations, in general we cannot assume that distinct - * SingleDenotations will have distinct signatures (cf #9050). - */ + /** For SymDenotations, the language-specific signature of the info, + * depending on where the symbol is defined. For non-SymDenotations, the + * Scala 3 signature. + * + * Invariants: + * - Before erasure, the signature of a denotation is always equal to the + * signature of its corresponding initial denotation. + * - Two distinct overloads will have SymDenotations with distinct + * signatures (the SELECTin tag in Tasty relies on this to refer to an + * overload unambiguously). Note that this only applies to + * SymDenotations, in general we cannot assume that distinct + * SingleDenotations will have distinct signatures (cf #9050). + */ final def signature(using Context): Signature = - signature(sourceLanguage = if isType || !this.isInstanceOf[SymDenotation] then SourceLanguage.Scala3 else SourceLanguage(symbol)) - - /** Overload of `signature` which lets the caller pick the language used - * to compute the signature of the info. Useful to match denotations defined in - * different classes (see `matchesLoosely`). - */ + signature(sourceLanguage = + if isType || !this.isInstanceOf[SymDenotation] then + SourceLanguage.Scala3 + else SourceLanguage(symbol) + ) + + /** Overload of `signature` which lets the caller pick the language used to + * compute the signature of the info. Useful to match denotations defined + * in different classes (see `matchesLoosely`). + */ def signature(sourceLanguage: SourceLanguage)(using Context): Signature = - if (isType) Signature.NotAMethod // don't force info if this is a type denotation - else info match - case info: MethodOrPoly => - try info.signature(sourceLanguage) - catch case ex: Exception => - if ctx.debug then report.echo(s"cannot take signature of $info") - throw ex - case _ => Signature.NotAMethod - - def derivedSingleDenotation(symbol: Symbol, info: Type, pre: Type = this.prefix, isRefinedMethod: Boolean = this.isRefinedMethod)(using Context): SingleDenotation = - if ((symbol eq this.symbol) && (info eq this.info) && (pre eq this.prefix) && (isRefinedMethod == this.isRefinedMethod)) this + if (isType) + Signature.NotAMethod // don't force info if this is a type denotation + else + info match + case info: MethodOrPoly => + try info.signature(sourceLanguage) + catch + case ex: Exception => + if ctx.debug then report.echo(s"cannot take signature of $info") + throw ex + case _ => Signature.NotAMethod + + def derivedSingleDenotation( + symbol: Symbol, + info: Type, + pre: Type = this.prefix, + isRefinedMethod: Boolean = this.isRefinedMethod + )(using Context): SingleDenotation = + if ( + (symbol eq this.symbol) && (info eq this.info) && (pre eq this.prefix) && (isRefinedMethod == this.isRefinedMethod) + ) this else newLikeThis(symbol, info, pre, isRefinedMethod) def mapInfo(f: Type => Type)(using Context): SingleDenotation = derivedSingleDenotation(symbol, f(info)) - inline def orElse(inline that: SingleDenotation): SingleDenotation = if (this.exists) this else that + inline def orElse(inline that: SingleDenotation): SingleDenotation = + if (this.exists) this else that def altsWith(p: Symbol => Boolean): List[SingleDenotation] = if (exists && p(symbol)) this :: Nil else Nil @@ -643,10 +794,18 @@ object Denotations { def hasAltWith(p: SingleDenotation => Boolean): Boolean = exists && p(this) - def accessibleFrom(pre: Type, superAccess: Boolean)(using Context): Denotation = - if (!symbol.exists || symbol.isAccessibleFrom(pre, superAccess)) this else NoDenotation - - def atSignature(sig: Signature, targetName: Name, site: Type, relaxed: Boolean)(using Context): SingleDenotation = + def accessibleFrom(pre: Type, superAccess: Boolean)(using + Context + ): Denotation = + if (!symbol.exists || symbol.isAccessibleFrom(pre, superAccess)) this + else NoDenotation + + def atSignature( + sig: Signature, + targetName: Name, + site: Type, + relaxed: Boolean + )(using Context): SingleDenotation = val situated = if site == NoPrefix then this else asSeenFrom(site) val sigMatches = try @@ -660,41 +819,46 @@ object Denotations { relaxed case noMatch => false - catch case ex: MissingType => - false - if sigMatches && symbol.hasTargetName(targetName) then this else NoDenotation + catch + case ex: MissingType => + false + if sigMatches && symbol.hasTargetName(targetName) then this + else NoDenotation def matchesImportBound(bound: Type)(using Context): Boolean = if bound.isRef(defn.NothingClass) then false else if bound.isAny then true - else NoViewsAllowed.normalizedCompatible(info, bound, keepConstraint = false) + else + NoViewsAllowed.normalizedCompatible(info, bound, keepConstraint = false) // ------ Transformations ----------------------------------------- - /** The next SingleDenotation in this run, with wrap-around from last to first. - * - * There may be several `SingleDenotation`s with different validity - * representing the same underlying definition at different phases. - * These are called a "flock". Flock members are generated by - * @See current. Flock members are connected in a ring - * with their `nextInRun` fields. - * - * There are the following invariants concerning flock members - * - * 1) validity periods are non-overlapping - * 2) the union of all validity periods is a contiguous - * interval. - */ + /** The next SingleDenotation in this run, with wrap-around from last to + * first. + * + * There may be several `SingleDenotation`s with different validity + * representing the same underlying definition at different phases. These + * are called a "flock". Flock members are generated by + * @See + * current. Flock members are connected in a ring with their `nextInRun` + * fields. + * + * There are the following invariants concerning flock members + * + * 1) validity periods are non-overlapping 2) the union of all validity + * periods is a contiguous interval. + */ protected var nextInRun: SingleDenotation = this /** The version of this SingleDenotation that was valid in the first phase - * of this run. - */ + * of this run. + */ def initial: SingleDenotation = if (validFor.firstPhaseId <= 1) this else { var current = nextInRun - while (current.validFor.code > this.validFor.code) current = current.nextInRun + while (current.validFor.code > this.validFor.code) + current = current.nextInRun current } @@ -706,49 +870,59 @@ object Denotations { current = current.nextInRun current ne initial }) - () + () b.toList } - /** Invalidate all caches and fields that depend on base classes and their contents */ + /** Invalidate all caches and fields that depend on base classes and their + * contents + */ def invalidateInheritedInfo(): Unit = () private def updateValidity()(using Context): this.type = { assert( ctx.runId >= validFor.runId - || ctx.settings.YtestPickler.value // mixing test pickler with debug printing can travel back in time - || ctx.mode.is(Mode.Printing) // no use to be picky when printing error messages - || symbol.isOneOf(ValidForeverFlags) - || ctx.tolerateErrorsForBestEffort, - s"denotation $this invalid in run ${ctx.runId}. ValidFor: $validFor") + || ctx.settings.YtestPickler.value // mixing test pickler with debug printing can travel back in time + || ctx.mode.is( + Mode.Printing + ) // no use to be picky when printing error messages + || symbol.isOneOf(ValidForeverFlags) + || ctx.tolerateErrorsForBestEffort, + s"denotation $this invalid in run ${ctx.runId}. ValidFor: $validFor" + ) var d: SingleDenotation = this while ({ - d.validFor = Period(ctx.runId, d.validFor.firstPhaseId, d.validFor.lastPhaseId) + d.validFor = + Period(ctx.runId, d.validFor.firstPhaseId, d.validFor.lastPhaseId) d.invalidateInheritedInfo() d = d.nextInRun d ne this }) - () + () this } - /** Move validity period of this denotation to a new run. Throw a StaleSymbol error - * if denotation is no longer valid. - * However, StaleSymbol error is not thrown in the following situations: - * - * - If acceptStale returns true (e.g. because we are in the IDE), - * update the symbol to the new version if it exists, or return - * the old version otherwise. - * - If the symbol did not have a denotation that was defined at the current phase - * return a NoDenotation instead. - * - If the symbol was first defined in one of the transform phases (after pickling), it should not - * be visible in new runs, so also return a NoDenotation. - */ + /** Move validity period of this denotation to a new run. Throw a + * StaleSymbol error if denotation is no longer valid. However, StaleSymbol + * error is not thrown in the following situations: + * + * - If acceptStale returns true (e.g. because we are in the IDE), update + * the symbol to the new version if it exists, or return the old + * version otherwise. + * - If the symbol did not have a denotation that was defined at the + * current phase return a NoDenotation instead. + * - If the symbol was first defined in one of the transform phases + * (after pickling), it should not be visible in new runs, so also + * return a NoDenotation. + */ private def bringForward()(using Context): SingleDenotation = { this match { case symd: SymDenotation => if (stillValid(symd)) return updateValidity() - if acceptStale(symd) && symd.initial.validFor.firstPhaseId <= ctx.lastPhaseId then + if acceptStale( + symd + ) && symd.initial.validFor.firstPhaseId <= ctx.lastPhaseId + then // New run might have fewer phases than old, so symbol might no longer be // visible at all. TabCompleteTests have examples where this happens. return symd.currentSymbol.denot.orElse(symd).updateValidity() @@ -756,15 +930,16 @@ object Denotations { } if (!symbol.exists) return updateValidity() if (!coveredInterval.containsPhaseId(ctx.phaseId)) return NoDenotation - if (coveredInterval.firstPhaseId >= Phases.firstTransformPhase.id) return NoDenotation + if (coveredInterval.firstPhaseId >= Phases.firstTransformPhase.id) + return NoDenotation if (ctx.debug) traceInvalid(this) staleSymbolError } /** The next defined denotation (following `nextInRun`) or an arbitrary - * undefined denotation, if all denotations in a `nextinRun` cycle are - * undefined. - */ + * undefined denotation, if all denotations in a `nextinRun` cycle are + * undefined. + */ private def nextDefined: SingleDenotation = { var p1 = this var p2 = nextInRun @@ -776,32 +951,34 @@ object Denotations { } /** Skip any denotations that have been removed by an installAfter or that - * are otherwise undefined. - */ + * are otherwise undefined. + */ def skipRemoved(using Context): SingleDenotation = if (validFor.code <= 0) nextDefined else this - /** Produce a denotation that is valid for the given context. - * Usually called when !(validFor contains ctx.period) - * (even though this is not a precondition). - * If the runId of the context is the same as runId of this denotation, - * the right flock member is located, or, if it does not exist yet, - * created by invoking a transformer (@See Transformers). - * If the runId's differ, but this denotation is a SymDenotation - * and its toplevel owner class or module - * is still a member of its enclosing package, then the whole flock - * is brought forward to be valid in the new runId. Otherwise - * the symbol is stale, which constitutes an internal error. - */ + /** Produce a denotation that is valid for the given context. Usually called + * when !(validFor contains ctx.period) (even though this is not a + * precondition). If the runId of the context is the same as runId of this + * denotation, the right flock member is located, or, if it does not exist + * yet, created by invoking a transformer (@See Transformers). If the + * runId's differ, but this denotation is a SymDenotation and its toplevel + * owner class or module is still a member of its enclosing package, then + * the whole flock is brought forward to be valid in the new runId. + * Otherwise the symbol is stale, which constitutes an internal error. + */ def current(using Context): SingleDenotation = util.Stats.record("current") val currentPeriod = ctx.period val valid = validFor - def assertNotPackage(d: SingleDenotation, transformer: DenotTransformer) = d match - case d: ClassDenotation => - assert(!d.is(Package), s"illegal transformation of package denotation by transformer $transformer") - case _ => + def assertNotPackage(d: SingleDenotation, transformer: DenotTransformer) = + d match + case d: ClassDenotation => + assert( + !d.is(Package), + s"illegal transformation of package denotation by transformer $transformer" + ) + case _ => def toNewRun = util.Stats.record("current.bringForward") @@ -811,7 +988,8 @@ object Denotations { var cur = this // search for containing period as long as nextInRun increases. var next = nextInRun - while next.validFor.code > valid.code && !(next.validFor contains currentPeriod) do + while next.validFor.code > valid.code && !(next.validFor contains currentPeriod) + do cur = next next = next.nextInRun if next.validFor.code > valid.code then @@ -819,36 +997,40 @@ object Denotations { cur = next cur else - //println(s"might need new denot for $cur, valid for ${cur.validFor} at $currentPeriod") + // println(s"might need new denot for $cur, valid for ${cur.validFor} at $currentPeriod") // not found, cur points to highest existing variant - val nextTransformerId = ctx.base.nextDenotTransformerId(cur.validFor.lastPhaseId) + val nextTransformerId = + ctx.base.nextDenotTransformerId(cur.validFor.lastPhaseId) if currentPeriod.lastPhaseId <= nextTransformerId then - cur.validFor = Period(currentPeriod.runId, cur.validFor.firstPhaseId, nextTransformerId) + cur.validFor = Period( + currentPeriod.runId, + cur.validFor.firstPhaseId, + nextTransformerId + ) else var startPid = nextTransformerId + 1 val transformer = ctx.base.denotTransformers(nextTransformerId) - //println(s"transforming $this with $transformer") + // println(s"transforming $this with $transformer") val savedPeriod = ctx.period val mutCtx = ctx.asInstanceOf[FreshContext] try mutCtx.setPhase(transformer) next = transformer.transform(cur) - // We temporarily update the context with the new phase instead of creating a - // new one. This is done for performance. We cut down on about 30% of context - // creations that way, and also avoid phase caches in contexts to get large. - // To work correctly, we need to demand that the context with the new phase - // is not retained in the result. - finally - mutCtx.setPeriod(savedPeriod) - if next eq cur then - startPid = cur.validFor.firstPhaseId + // We temporarily update the context with the new phase instead of creating a + // new one. This is done for performance. We cut down on about 30% of context + // creations that way, and also avoid phase caches in contexts to get large. + // To work correctly, we need to demand that the context with the new phase + // is not retained in the result. + finally mutCtx.setPeriod(savedPeriod) + if next eq cur then startPid = cur.validFor.firstPhaseId else assertNotPackage(next, transformer) next.insertAfter(cur) cur = next - cur.validFor = Period(currentPeriod.runId, startPid, transformer.lastPhaseId) - //printPeriods(cur) - //println(s"new denot: $cur, valid for ${cur.validFor}") + cur.validFor = + Period(currentPeriod.runId, startPid, transformer.lastPhaseId) + // printPeriods(cur) + // println(s"new denot: $cur, valid for ${cur.validFor}") cur.current // multiple transformations could be required end goForward @@ -858,7 +1040,7 @@ object Denotations { var cur = this var cnt = 0 while !(cur.validFor contains currentPeriod) do - //println(s"searching: $cur at $currentPeriod, valid for ${cur.validFor}") + // println(s"searching: $cur at $currentPeriod, valid for ${cur.validFor}") cur = cur.nextInRun // Note: One might be tempted to add a `prev` field to get to the new denotation // more directly here. I tried that, but it degrades rather than improves @@ -875,21 +1057,19 @@ object Denotations { // wholesale by an installAfter; in this case, proceed to the next // denotation and try again. nextDefined - else if valid.runId != currentPeriod.runId then - toNewRun - else if currentPeriod.code > valid.code then - goForward - else - goBack + else if valid.runId != currentPeriod.runId then toNewRun + else if currentPeriod.code > valid.code then goForward + else goBack end current private def demandOutsideDefinedMsg(using Context): String = s"demanding denotation of $this at phase ${ctx.phase}(${ctx.phaseId}) outside defined interval: defined periods are${definedPeriodsString}" - /** Install this denotation to be the result of the given denotation transformer. - * This is the implementation of the same-named method in SymDenotations. - * It's placed here because it needs access to private fields of SingleDenotation. - */ + /** Install this denotation to be the result of the given denotation + * transformer. This is the implementation of the same-named method in + * SymDenotations. It's placed here because it needs access to private + * fields of SingleDenotation. + */ protected def installAfter(phase: DenotTransformer)(using Context): Unit = { val targetId = phase.next.id if (ctx.phaseId != targetId) atPhase(phase.next)(installAfter(phase)) @@ -897,28 +1077,36 @@ object Denotations { val current = symbol.current // println(s"installing $this after $phase/${phase.id}, valid = ${current.validFor}") // println(current.definedPeriodsString) - this.validFor = Period(ctx.runId, targetId, current.validFor.lastPhaseId) + this.validFor = + Period(ctx.runId, targetId, current.validFor.lastPhaseId) if (current.validFor.firstPhaseId >= targetId) current.replaceWith(this) symbol.denot - // Let symbol point to updated denotation - // Without this we can get problems when we immediately recompute the denotation - // at another phase since the invariant that symbol used to point to a valid - // denotation is lost. + // Let symbol point to updated denotation + // Without this we can get problems when we immediately recompute the denotation + // at another phase since the invariant that symbol used to point to a valid + // denotation is lost. else { - current.validFor = Period(ctx.runId, current.validFor.firstPhaseId, targetId - 1) + current.validFor = + Period(ctx.runId, current.validFor.firstPhaseId, targetId - 1) insertAfter(current) } // println(current.definedPeriodsString) } } - /** Apply a transformation `f` to all denotations in this group that start at or after - * given phase. Denotations are replaced while keeping the same validity periods. - */ - protected def transformAfter(phase: DenotTransformer, f: SymDenotation => SymDenotation)(using Context): Unit = { + /** Apply a transformation `f` to all denotations in this group that start + * at or after given phase. Denotations are replaced while keeping the same + * validity periods. + */ + protected def transformAfter( + phase: DenotTransformer, + f: SymDenotation => SymDenotation + )(using Context): Unit = { var current = symbol.current - while (current.validFor.firstPhaseId < phase.id && (current.nextInRun.validFor.code > current.validFor.code)) + while ( + current.validFor.firstPhaseId < phase.id && (current.nextInRun.validFor.code > current.validFor.code) + ) current = current.nextInRun var hasNext = true while ((current.validFor.firstPhaseId >= phase.id) && hasNext) { @@ -938,15 +1126,14 @@ object Denotations { prev.nextInRun = this } - /** Insert this denotation instead of `old`. - * Also ensure that `old` refers with `nextInRun` to this denotation - * and set its `validFor` field to `Nowhere`. This is necessary so that - * references to the old denotation can be brought forward via `current` - * to a valid denotation. - * - * The code to achieve this is subtle in that it works correctly - * whether the replaced denotation is the only one in its cycle or not. - */ + /** Insert this denotation instead of `old`. Also ensure that `old` refers + * with `nextInRun` to this denotation and set its `validFor` field to + * `Nowhere`. This is necessary so that references to the old denotation + * can be brought forward via `current` to a valid denotation. + * + * The code to achieve this is subtle in that it works correctly whether + * the replaced denotation is the only one in its cycle or not. + */ private[dotc] def replaceWith(newd: SingleDenotation): Unit = { var prev = this while (prev.nextInRun ne this) prev = prev.nextInRun @@ -965,14 +1152,14 @@ object Denotations { def staleSymbolMsg(using Context): String = { def ownerMsg = this match { case denot: SymDenotation => s"in ${denot.owner}" - case _ => "" + case _ => "" } s"stale symbol; $this#${symbol.id} $ownerMsg, defined in ${validFor}, is referred to in run ${ctx.period}" } - /** The period (interval of phases) for which there exists - * a valid denotation in this flock. - */ + /** The period (interval of phases) for which there exists a valid + * denotation in this flock. + */ def coveredInterval(using Context): Period = { var cur = this var cnt = 0 @@ -984,13 +1171,13 @@ object Denotations { interval |= cur.validFor cur ne this }) - () + () interval } - /** Show declaration string; useful for showing declarations - * as seen from subclasses. - */ + /** Show declaration string; useful for showing declarations as seen from + * subclasses. + */ def showDcl(using Context): String = ctx.printer.dclText(this).show override def toString: String = @@ -1019,22 +1206,26 @@ object Denotations { def matches(other: SingleDenotation)(using Context): Boolean = symbol.hasTargetName(other.symbol.targetName) - && matchesLoosely(other) + && matchesLoosely(other) /** `matches` without a target name check. - * - * For definitions coming from different languages, we pick a common - * language to compute their signatures. This allows us for example to - * override some Java definitions from Scala even if they have a different - * erasure (see i8615b, i9109b), Erasure takes care of adding any necessary - * bridge to make this work at runtime. - */ - def matchesLoosely(other: SingleDenotation, alwaysCompareTypes: Boolean = false)(using Context): Boolean = + * + * For definitions coming from different languages, we pick a common + * language to compute their signatures. This allows us for example to + * override some Java definitions from Scala even if they have a different + * erasure (see i8615b, i9109b), Erasure takes care of adding any necessary + * bridge to make this work at runtime. + */ + def matchesLoosely( + other: SingleDenotation, + alwaysCompareTypes: Boolean = false + )(using Context): Boolean = if isType then true else val thisLanguage = SourceLanguage(symbol) val otherLanguage = SourceLanguage(other.symbol) - val commonLanguage = SourceLanguage.commonLanguage(thisLanguage, otherLanguage) + val commonLanguage = + SourceLanguage.commonLanguage(thisLanguage, otherLanguage) val sig = signature(commonLanguage) val otherSig = other.signature(commonLanguage) sig.matchDegree(otherSig) match @@ -1043,15 +1234,12 @@ object Denotations { case MethodNotAMethodMatch => !ctx.erasedTypes && { // A Scala zero-parameter method and a Scala non-method always match. - if !thisLanguage.isJava && !otherLanguage.isJava then - true + if !thisLanguage.isJava && !otherLanguage.isJava then true // Java allows defining both a field and a zero-parameter method with the same name, // so they must not match. - else if thisLanguage.isJava && otherLanguage.isJava then - false + else if thisLanguage.isJava && otherLanguage.isJava then false // A Java field never matches a Scala method. - else if thisLanguage.isJava then - symbol.is(Method) + else if thisLanguage.isJava then symbol.is(Method) else // otherLanguage.isJava other.symbol.is(Method) } @@ -1061,7 +1249,11 @@ object Denotations { case noMatch => false - def mapInherited(ownDenots: PreDenotation, prevDenots: PreDenotation, pre: Type)(using Context): SingleDenotation = + def mapInherited( + ownDenots: PreDenotation, + prevDenots: PreDenotation, + pre: Type + )(using Context): SingleDenotation = if hasUniqueSym && prevDenots.containsSym(symbol) then NoDenotation else if isType then filterDisjoint(ownDenots).asSeenFrom(pre) else asSeenFrom(pre).filterDisjoint(ownDenots) @@ -1070,33 +1262,39 @@ object Denotations { if (p(this)) this else NoDenotation def filterDisjoint(denots: PreDenotation)(using Context): SingleDenotation = if (denots.exists && denots.matches(this)) NoDenotation else this - def filterWithFlags(required: FlagSet, excluded: FlagSet)(using Context): SingleDenotation = + def filterWithFlags(required: FlagSet, excluded: FlagSet)(using + Context + ): SingleDenotation = val realExcluded = if ctx.isAfterTyper || ctx.mode.is(Mode.ResolveFromTASTy) then excluded else excluded | Invisible def symd: SymDenotation = this match case symd: SymDenotation => symd - case _ => symbol.denot + case _ => symbol.denot if !required.isEmpty && !symd.isAllOf(required) - || symd.isOneOf(realExcluded) then NoDenotation + || symd.isOneOf(realExcluded) + then NoDenotation else this def aggregate[T](f: SingleDenotation => T, g: (T, T) => T): T = f(this) type AsSeenFromResult = SingleDenotation - protected def computeAsSeenFrom(pre: Type)(using Context): SingleDenotation = { + protected def computeAsSeenFrom( + pre: Type + )(using Context): SingleDenotation = { val symbol = this.symbol val owner = this match { case thisd: SymDenotation => thisd.owner case _ => if (symbol.exists) symbol.owner else NoSymbol } - /** The derived denotation with the given `info` transformed with `asSeenFrom`. - * - * As a performance hack, we might reuse an existing SymDenotation, - * instead of creating a new denotation with a given `prefix`, - * see `Config.reuseSymDenotations`. - */ + /** The derived denotation with the given `info` transformed with + * `asSeenFrom`. + * + * As a performance hack, we might reuse an existing SymDenotation, + * instead of creating a new denotation with a given `prefix`, see + * `Config.reuseSymDenotations`. + */ def derived(info: Type) = /** Do we need to return a denotation with a prefix set? */ def needsPrefix = @@ -1106,10 +1304,9 @@ object Denotations { val derivedInfo = info.asSeenFrom(pre, owner) if Config.reuseSymDenotations && this.isInstanceOf[SymDenotation] - && (derivedInfo eq info) && !needsPrefix then - this - else - derivedSingleDenotation(symbol, derivedInfo, pre) + && (derivedInfo eq info) && !needsPrefix + then this + else derivedSingleDenotation(symbol, derivedInfo, pre) end derived // Tt could happen that we see the symbol with prefix `this` as a member a different class @@ -1117,102 +1314,137 @@ object Denotations { // through the asSeenFrom to switch the type back. Test case is pos/i9352.scala. def hasOriginalInfo: Boolean = this match case sd: SymDenotation => true - case _ => info eq symbol.info + case _ => info eq symbol.info def ownerIsPrefix = pre match case pre: ThisType => pre.sameThis(owner.thisType) - case _ => false + case _ => false - if !owner.membersNeedAsSeenFrom(pre) && (!ownerIsPrefix || hasOriginalInfo) - || symbol.is(NonMember) + if !owner.membersNeedAsSeenFrom( + pre + ) && (!ownerIsPrefix || hasOriginalInfo) + || symbol.is(NonMember) then this else if symbol.isAllOf(ClassTypeParam) then val arg = symbol.typeRef.argForParam(pre, widenAbstract = true) if arg.exists - then derivedSingleDenotation(symbol, normalizedArgBounds(arg.bounds), pre) + then + derivedSingleDenotation(symbol, normalizedArgBounds(arg.bounds), pre) else derived(symbol.info) else derived(symbol.info) } - /** The argument bounds, possibly intersected with the parameter's info TypeBounds, - * if the latter is not F-bounded and does not refer to other type parameters - * of the same class, and the intersection is provably nonempty. - */ - private def normalizedArgBounds(argBounds: TypeBounds)(using Context): TypeBounds = + /** The argument bounds, possibly intersected with the parameter's info + * TypeBounds, if the latter is not F-bounded and does not refer to other + * type parameters of the same class, and the intersection is provably + * nonempty. + */ + private def normalizedArgBounds( + argBounds: TypeBounds + )(using Context): TypeBounds = if symbol.isCompleted && !hasBoundsDependingOnParamsOf(symbol.owner) then val combined @ TypeBounds(lo, hi) = symbol.info.bounds & argBounds if (lo frozen_<:< hi) then combined else argBounds else argBounds - private def hasBoundsDependingOnParamsOf(cls: Symbol)(using Context): Boolean = + private def hasBoundsDependingOnParamsOf(cls: Symbol)(using + Context + ): Boolean = val acc = new TypeAccumulator[Boolean]: def apply(x: Boolean, tp: Type): Boolean = tp match case _: LazyRef => true case tp: TypeRef - if tp.symbol.isAllOf(ClassTypeParam) && tp.symbol.owner == cls => true + if tp.symbol.isAllOf(ClassTypeParam) && tp.symbol.owner == cls => + true case _ => foldOver(x, tp) acc(false, symbol.info) } - abstract class NonSymSingleDenotation(symbol: Symbol, initInfo: Type, override val prefix: Type) - extends SingleDenotation(symbol, initInfo, initInfo.isInstanceOf[TypeType]) { + abstract class NonSymSingleDenotation( + symbol: Symbol, + initInfo: Type, + override val prefix: Type + ) extends SingleDenotation( + symbol, + initInfo, + initInfo.isInstanceOf[TypeType] + ) { def infoOrCompleter: Type = initInfo } class UniqueRefDenotation( - symbol: Symbol, - initInfo: Type, - initValidFor: Period, - prefix: Type) extends NonSymSingleDenotation(symbol, initInfo, prefix) { + symbol: Symbol, + initInfo: Type, + initValidFor: Period, + prefix: Type + ) extends NonSymSingleDenotation(symbol, initInfo, prefix) { validFor = initValidFor override def hasUniqueSym: Boolean = true - protected def newLikeThis(s: Symbol, i: Type, pre: Type, isRefinedMethod: Boolean)(using Context): SingleDenotation = + protected def newLikeThis( + s: Symbol, + i: Type, + pre: Type, + isRefinedMethod: Boolean + )(using Context): SingleDenotation = if isRefinedMethod then new JointRefDenotation(s, i, currentStablePeriod, pre, isRefinedMethod) - else - new UniqueRefDenotation(s, i, currentStablePeriod, pre) + else new UniqueRefDenotation(s, i, currentStablePeriod, pre) } class JointRefDenotation( - symbol: Symbol, - initInfo: Type, - initValidFor: Period, - prefix: Type, - override val isRefinedMethod: Boolean) extends NonSymSingleDenotation(symbol, initInfo, prefix) { + symbol: Symbol, + initInfo: Type, + initValidFor: Period, + prefix: Type, + override val isRefinedMethod: Boolean + ) extends NonSymSingleDenotation(symbol, initInfo, prefix) { validFor = initValidFor override def hasUniqueSym: Boolean = false - protected def newLikeThis(s: Symbol, i: Type, pre: Type, isRefinedMethod: Boolean)(using Context): SingleDenotation = + protected def newLikeThis( + s: Symbol, + i: Type, + pre: Type, + isRefinedMethod: Boolean + )(using Context): SingleDenotation = new JointRefDenotation(s, i, currentStablePeriod, pre, isRefinedMethod) } - class ErrorDenotation(using Context) extends NonSymSingleDenotation(NoSymbol, NoType, NoType) { + class ErrorDenotation(using Context) + extends NonSymSingleDenotation(NoSymbol, NoType, NoType) { override def exists: Boolean = false override def hasUniqueSym: Boolean = false validFor = Period.allInRun(ctx.runId) - protected def newLikeThis(s: Symbol, i: Type, pre: Type, isRefinedMethod: Boolean)(using Context): SingleDenotation = + protected def newLikeThis( + s: Symbol, + i: Type, + pre: Type, + isRefinedMethod: Boolean + )(using Context): SingleDenotation = this } /** An error denotation that provides more info about the missing reference. - * Produced by staticRef, consumed by requiredSymbol. - */ - case class MissingRef(val owner: SingleDenotation, name: Name)(using Context) extends ErrorDenotation { + * Produced by staticRef, consumed by requiredSymbol. + */ + case class MissingRef(val owner: SingleDenotation, name: Name)(using Context) + extends ErrorDenotation { val ex: Exception = new Exception // DEBUG } - /** An error denotation that provides more info about alternatives - * that were found but that do not qualify. - * Produced by staticRef, consumed by requiredSymbol. - */ - case class NoQualifyingRef(alts: List[SingleDenotation])(using Context) extends ErrorDenotation + /** An error denotation that provides more info about alternatives that were + * found but that do not qualify. Produced by staticRef, consumed by + * requiredSymbol. + */ + case class NoQualifyingRef(alts: List[SingleDenotation])(using Context) + extends ErrorDenotation /** A double definition - */ + */ def isDoubleDef(sym1: Symbol, sym2: Symbol)(using Context): Boolean = (sym1.exists && sym2.exists && - (sym1 `ne` sym2) && (sym1.effectiveOwner `eq` sym2.effectiveOwner) && - !sym1.is(Bridge) && !sym2.is(Bridge)) + (sym1 `ne` sym2) && (sym1.effectiveOwner `eq` sym2.effectiveOwner) && + !sym1.is(Bridge) && !sym2.is(Bridge)) // --- Overloaded denotations and predenotations ------------------------------------------------- @@ -1220,19 +1452,34 @@ object Denotations { def denot1: PreDenotation def denot2: PreDenotation - assert(denot1.exists && denot2.exists, s"Union of non-existing denotations ($denot1) and ($denot2)") + assert( + denot1.exists && denot2.exists, + s"Union of non-existing denotations ($denot1) and ($denot2)" + ) def first: Denotation = denot1.first def last: Denotation = denot2.last def matches(other: SingleDenotation)(using Context): Boolean = denot1.matches(other) || denot2.matches(other) - def mapInherited(owndenot: PreDenotation, prevdenot: PreDenotation, pre: Type)(using Context): PreDenotation = - derivedUnion(denot1.mapInherited(owndenot, prevdenot, pre), denot2.mapInherited(owndenot, prevdenot, pre)) + def mapInherited( + owndenot: PreDenotation, + prevdenot: PreDenotation, + pre: Type + )(using Context): PreDenotation = + derivedUnion( + denot1.mapInherited(owndenot, prevdenot, pre), + denot2.mapInherited(owndenot, prevdenot, pre) + ) def filterWithPredicate(p: SingleDenotation => Boolean): PreDenotation = derivedUnion(denot1 filterWithPredicate p, denot2 filterWithPredicate p) def filterDisjoint(denot: PreDenotation)(using Context): PreDenotation = derivedUnion(denot1 filterDisjoint denot, denot2 filterDisjoint denot) - def filterWithFlags(required: FlagSet, excluded: FlagSet)(using Context): PreDenotation = - derivedUnion(denot1.filterWithFlags(required, excluded), denot2.filterWithFlags(required, excluded)) + def filterWithFlags(required: FlagSet, excluded: FlagSet)(using + Context + ): PreDenotation = + derivedUnion( + denot1.filterWithFlags(required, excluded), + denot2.filterWithFlags(required, excluded) + ) def aggregate[T](f: SingleDenotation => T, g: (T, T) => T): T = g(denot1.aggregate(f, g), denot2.aggregate(f, g)) protected def derivedUnion(denot1: PreDenotation, denot2: PreDenotation) = @@ -1240,7 +1487,8 @@ object Denotations { else denot1 union denot2 } - final case class DenotUnion(denot1: PreDenotation, denot2: PreDenotation) extends MultiPreDenotation { + final case class DenotUnion(denot1: PreDenotation, denot2: PreDenotation) + extends MultiPreDenotation { def exists: Boolean = true def toDenot(pre: Type)(using Context): Denotation = denot1.toDenot(pre).meet(denot2.toDenot(pre), pre) @@ -1251,20 +1499,31 @@ object Denotations { derivedUnion(denot1.asSeenFrom(pre), denot2.asSeenFrom(pre)) } - /** An overloaded denotation consisting of the alternatives of both given denotations. - */ - case class MultiDenotation(denot1: Denotation, denot2: Denotation) extends Denotation(NoSymbol, NoType, isType = false) with MultiPreDenotation { + /** An overloaded denotation consisting of the alternatives of both given + * denotations. + */ + case class MultiDenotation(denot1: Denotation, denot2: Denotation) + extends Denotation(NoSymbol, NoType, isType = false) + with MultiPreDenotation { validFor = denot1.validFor & denot2.validFor final def infoOrCompleter: Type = multiHasNot("info") final def hasUniqueSym: Boolean = false final def name(using Context): Name = denot1.name - final def signature(using Context): Signature = Signature.OverloadedSignature - def atSignature(sig: Signature, targetName: Name, site: Type, relaxed: Boolean)(using Context): Denotation = + final def signature(using Context): Signature = + Signature.OverloadedSignature + def atSignature( + sig: Signature, + targetName: Name, + site: Type, + relaxed: Boolean + )(using Context): Denotation = if (sig eq Signature.OverloadedSignature) this - else derivedUnionDenotation( - denot1.atSignature(sig, targetName, site, relaxed), - denot2.atSignature(sig, targetName, site, relaxed)) + else + derivedUnionDenotation( + denot1.atSignature(sig, targetName, site, relaxed), + denot2.atSignature(sig, targetName, site, relaxed) + ) def current(using Context): Denotation = derivedUnionDenotation(denot1.current, denot2.current) def altsWith(p: Symbol => Boolean): List[SingleDenotation] = @@ -1274,18 +1533,24 @@ object Denotations { val sd2 = denot2.suchThat(p) if sd1.exists then if sd2.exists then - throw TypeError( - em"""Failure to disambiguate overloaded reference with + throw TypeError(em"""Failure to disambiguate overloaded reference with | ${denot1.symbol.showLocated}: ${denot1.info} and | ${denot2.symbol.showLocated}: ${denot2.info}""") else sd1 else sd2 } - override def filterWithPredicate(p: SingleDenotation => Boolean): Denotation = - derivedUnionDenotation(denot1.filterWithPredicate(p), denot2.filterWithPredicate(p)) + override def filterWithPredicate( + p: SingleDenotation => Boolean + ): Denotation = + derivedUnionDenotation( + denot1.filterWithPredicate(p), + denot2.filterWithPredicate(p) + ) def hasAltWith(p: SingleDenotation => Boolean): Boolean = denot1.hasAltWith(p) || denot2.hasAltWith(p) - def accessibleFrom(pre: Type, superAccess: Boolean)(using Context): Denotation = { + def accessibleFrom(pre: Type, superAccess: Boolean)(using + Context + ): Denotation = { val d1 = denot1.accessibleFrom(pre, superAccess) val d2 = denot2.accessibleFrom(pre, superAccess) if (!d1.exists) d2 @@ -1306,23 +1571,31 @@ object Denotations { private def multiHasNot(op: String): Nothing = throw new UnsupportedOperationException( - s"multi-denotation with alternatives $alternatives does not implement operation $op") + s"multi-denotation with alternatives $alternatives does not implement operation $op" + ) } - /** The current denotation of the static reference given by path, - * or a MissingRef or NoQualifyingRef instance, if it does not exist. - * if generateStubs is set, generates stubs for missing top-level symbols + /** The current denotation of the static reference given by path, or a + * MissingRef or NoQualifyingRef instance, if it does not exist. if + * generateStubs is set, generates stubs for missing top-level symbols */ - def staticRef(path: Name, generateStubs: Boolean = true, isPackage: Boolean = false)(using Context): Denotation = { + def staticRef( + path: Name, + generateStubs: Boolean = true, + isPackage: Boolean = false + )(using Context): Denotation = { def select(prefix: Denotation, selector: Name): Denotation = { val owner = prefix.disambiguate(_.info.isParameterless) def isPackageFromCoreLibMissing: Boolean = // if the scala package is missing, the stdlib must be missing owner.symbol == defn.RootClass && selector == nme.scala if (owner.exists) { - val result = if (isPackage) owner.info.decl(selector) else owner.info.member(selector) + val result = + if (isPackage) owner.info.decl(selector) + else owner.info.member(selector) if (result.exists) result - else if (isPackageFromCoreLibMissing) throw new MissingCoreLibraryException(selector.toString) + else if (isPackageFromCoreLibMissing) + throw new MissingCoreLibraryException(selector.toString) else { val alt = if (generateStubs) missingHook(owner.symbol.moduleClass, selector) @@ -1330,39 +1603,38 @@ object Denotations { if (alt.exists) alt.denot else MissingRef(owner, selector) } - } - else owner - } - def recur(path: Name, wrap: TermName => Name = identity): Denotation = path match { - case path: TypeName => - recur(path.toTermName, n => n.toTypeName) - case ModuleClassName(underlying) => - recur(underlying, n => wrap(ModuleClassName(n))) - case QualifiedName(prefix, selector) => - select(recur(prefix), wrap(selector)) - case qn @ AnyQualifiedName(prefix, _) => - recur(prefix, n => wrap(qn.info.mkString(n).toTermName)) - case path: SimpleName => - def recurSimple(len: Int, wrap: TermName => Name): Denotation = { - val point = path.lastIndexOf('.', len - 1) - val selector = wrap(path.slice(point + 1, len).asTermName) - val prefix = - if (point > 0) recurSimple(point, identity) - else if (selector.isTermName) defn.RootClass.denot - else defn.EmptyPackageClass.denot - select(prefix, selector) - } - recurSimple(path.length, wrap) + } else owner } + def recur(path: Name, wrap: TermName => Name = identity): Denotation = + path match { + case path: TypeName => + recur(path.toTermName, n => n.toTypeName) + case ModuleClassName(underlying) => + recur(underlying, n => wrap(ModuleClassName(n))) + case QualifiedName(prefix, selector) => + select(recur(prefix), wrap(selector)) + case qn @ AnyQualifiedName(prefix, _) => + recur(prefix, n => wrap(qn.info.mkString(n).toTermName)) + case path: SimpleName => + def recurSimple(len: Int, wrap: TermName => Name): Denotation = { + val point = path.lastIndexOf('.', len - 1) + val selector = wrap(path.slice(point + 1, len).asTermName) + val prefix = + if (point > 0) recurSimple(point, identity) + else if (selector.isTermName) defn.RootClass.denot + else defn.EmptyPackageClass.denot + select(prefix, selector) + } + recurSimple(path.length, wrap) + } val run = ctx.run if run == null then recur(path) else run.staticRefs.getOrElseUpdate(path, recur(path)) } - /** If we are looking for a non-existing term name in a package, - * assume it is a package for which we do not have a directory and - * enter it. + /** If we are looking for a non-existing term name in a package, assume it is + * a package for which we do not have a directory and enter it. */ def missingHook(owner: Symbol, name: Name)(using Context): Symbol = if (owner.is(Package) && name.isTermName) @@ -1372,17 +1644,20 @@ object Denotations { trait StaleSymbol extends Exception - /** An exception for accessing symbols that are no longer valid in current run */ + /** An exception for accessing symbols that are no longer valid in current run + */ class StaleSymbolException(msg: => String) extends Exception, StaleSymbol { util.Stats.record("stale symbol") override def getMessage(): String = msg } - /** An exception that is at the same type a StaleSymbol and a TypeError. - * Sine it is a TypeError it can be reported as a nroaml error instead of crashing - * the compiler. - */ - class StaleSymbolTypeError(symbol: Symbol)(using Context) extends TypeError, StaleSymbol: + /** An exception that is at the same type a StaleSymbol and a TypeError. Sine + * it is a TypeError it can be reported as a nroaml error instead of crashing + * the compiler. + */ + class StaleSymbolTypeError(symbol: Symbol)(using Context) + extends TypeError, + StaleSymbol: def toMessage(using Context) = em"Cyclic macro dependency; macro refers to a toplevel symbol in ${symbol.source} from which the macro is called" } diff --git a/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala b/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala index 8f8f4676f43b..69dccfae2a7f 100644 --- a/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala +++ b/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala @@ -5,15 +5,19 @@ package dotty.tools.dotc.reporting // Only add new IDs at end of the enumeration list and never remove IDs // ////////////////////////////////////////////////////////////////////////// -/** Unique IDs identifying the messages, this will be used to reference documentation online. - * - * @param isActive Whether or not the compile still emits this ErrorMessageID - **/ -enum ErrorMessageID(val isActive: Boolean = true) extends java.lang.Enum[ErrorMessageID]: +/** Unique IDs identifying the messages, this will be used to reference + * documentation online. + * + * @param isActive + * Whether or not the compile still emits this ErrorMessageID + */ +enum ErrorMessageID(val isActive: Boolean = true) + extends java.lang.Enum[ErrorMessageID]: case NoExplanationID // errorNumber: -1 - case EmptyCatchOrFinallyBlockID extends ErrorMessageID(isActive = false) // errorNumber: 0 + case EmptyCatchOrFinallyBlockID + extends ErrorMessageID(isActive = false) // errorNumber: 0 case EmptyCatchBlockID // errorNumber: 1 case EmptyCatchAndFinallyBlockID // errorNumber: 2 case DeprecatedWithOperatorID // errorNumber: 3 @@ -23,11 +27,13 @@ enum ErrorMessageID(val isActive: Boolean = true) extends java.lang.Enum[ErrorMe case TypeMismatchID // errorNumber: 7 case NotAMemberID // errorNumber: 8 case EarlyDefinitionsNotSupportedID // errorNumber: 9 - case TopLevelImplicitClassID extends ErrorMessageID(isActive = false) // errorNumber: 10 + case TopLevelImplicitClassID + extends ErrorMessageID(isActive = false) // errorNumber: 10 case ImplicitCaseClassID // errorNumber: 11 case ImplicitClassPrimaryConstructorArityID // errorNumber: 12 case ObjectMayNotHaveSelfTypeID // errorNumber: 13 - case TupleTooLongID extends ErrorMessageID(isActive = false) // errorNumber: 14 + case TupleTooLongID + extends ErrorMessageID(isActive = false) // errorNumber: 14 case RepeatedModifierID // errorNumber: 15 case InterpolatedStringErrorID // errorNumber: 16 case UnboundPlaceholderParameterID // errorNumber: 17 @@ -49,7 +55,8 @@ enum ErrorMessageID(val isActive: Boolean = true) extends java.lang.Enum[ErrorMe case PkgDuplicateSymbolID // errorNumber: 33 case ExistentialTypesNoLongerSupportedID // errorNumber: 34 case UnboundWildcardTypeID // errorNumber: 35 - case DanglingThisInPathID extends ErrorMessageID(isActive = false) // errorNumber: 36 + case DanglingThisInPathID + extends ErrorMessageID(isActive = false) // errorNumber: 36 case OverridesNothingID // errorNumber: 37 case OverridesNothingButNameExistsID // errorNumber: 38 case ForwardReferenceExtendsOverDefinitionID // errorNumber: 39 @@ -67,14 +74,16 @@ enum ErrorMessageID(val isActive: Boolean = true) extends java.lang.Enum[ErrorMe case AmbiguousOverloadID // errorNumber: 51 case ReassignmentToValID // errorNumber: 52 case TypeDoesNotTakeParametersID // errorNumber: 53 - case ParameterizedTypeLacksArgumentsID extends ErrorMessageID(isActive = false) // errorNumber: 54 + case ParameterizedTypeLacksArgumentsID + extends ErrorMessageID(isActive = false) // errorNumber: 54 case VarValParametersMayNotBeCallByNameID // errorNumber: 55 case MissingTypeParameterForID // errorNumber: 56 case DoesNotConformToBoundID // errorNumber: 57 case DoesNotConformToSelfTypeID // errorNumber: 58 case DoesNotConformToSelfTypeCantBeInstantiatedID // errorNumber: 59 case AbstractMemberMayNotHaveModifierID // errorNumber: 60 - case TopLevelCantBeImplicitID extends ErrorMessageID(isActive = false) // errorNumber: 61 + case TopLevelCantBeImplicitID + extends ErrorMessageID(isActive = false) // errorNumber: 61 case TypesAndTraitsCantBeImplicitID // errorNumber: 62 case OnlyClassesCanBeAbstractID // errorNumber: 63 case AbstractOverrideOnlyInTraitsID // errorNumber: 64 @@ -92,8 +101,10 @@ enum ErrorMessageID(val isActive: Boolean = true) extends java.lang.Enum[ErrorMe case ValueClassesMayNotWrapAnotherValueClassID // errorNumber: 76 case ValueClassParameterMayNotBeAVarID // errorNumber: 77 case ValueClassNeedsExactlyOneValParamID // errorNumber: 78 - case OnlyCaseClassOrCaseObjectAllowedID extends ErrorMessageID(isActive = false) // errorNumber: 79 - case ExpectedTopLevelDefID extends ErrorMessageID(isActive = false) // errorNumber: 80 + case OnlyCaseClassOrCaseObjectAllowedID + extends ErrorMessageID(isActive = false) // errorNumber: 79 + case ExpectedTopLevelDefID + extends ErrorMessageID(isActive = false) // errorNumber: 80 case AnonymousFunctionMissingParamTypeID // errorNumber: 81 case SuperCallsNotAllowedInlineableID // errorNumber: 82 case NotAPathID // errorNumber: 83 @@ -168,7 +179,8 @@ enum ErrorMessageID(val isActive: Boolean = true) extends java.lang.Enum[ErrorMe case ExtensionCanOnlyHaveDefsID // errorNumber: 152 case UnexpectedPatternForSummonFromID // errorNumber: 153 case AnonymousInstanceCannotBeEmptyID // errorNumber: 154 - case TypeSpliceInValPatternID extends ErrorMessageID(isActive = false) // errorNumber: 155 + case TypeSpliceInValPatternID + extends ErrorMessageID(isActive = false) // errorNumber: 155 case ModifierNotAllowedForDefinitionID // errorNumber: 156 case CannotExtendJavaEnumID // errorNumber: 157 case InvalidReferenceInImplicitNotFoundAnnotationID // errorNumber: 158 @@ -176,7 +188,8 @@ enum ErrorMessageID(val isActive: Boolean = true) extends java.lang.Enum[ErrorMe case JavaEnumParentArgsID // errorNumber: 160 case AlreadyDefinedID // errorNumber: 161 case CaseClassInInlinedCodeID // errorNumber: 162 - case OverrideTypeMismatchErrorID extends ErrorMessageID(isActive = false) // errorNumber: 163 + case OverrideTypeMismatchErrorID + extends ErrorMessageID(isActive = false) // errorNumber: 163 case OverrideErrorID // errorNumber: 164 case MatchableWarningID // errorNumber: 165 case CannotExtendFunctionID // errorNumber: 166 @@ -212,7 +225,7 @@ enum ErrorMessageID(val isActive: Boolean = true) extends java.lang.Enum[ErrorMe case ContextBoundCompanionNotValueID // errorNumber: 196 case InlinedAnonClassWarningID // errorNumber: 197 case UnusedSymbolID // errorNumber: 198 - case TailrecNestedCallID //errorNumber: 199 + case TailrecNestedCallID // errorNumber: 199 case FinalLocalDefID // errorNumber: 200 case NonNamedArgumentInJavaAnnotationID // errorNumber: 201 case QuotedTypeMissingID // errorNumber: 202 @@ -222,6 +235,7 @@ enum ErrorMessageID(val isActive: Boolean = true) extends java.lang.Enum[ErrorMe case EnumMayNotBeValueClassesID // errorNumber: 206 case IllegalUnrollPlacementID // errorNumber: 207 case ExtensionHasDefaultID // errorNumber: 208 + case ReassignmentToDefID // errorNumber: 209 def errorNumber = ordinal - 1 @@ -230,5 +244,4 @@ object ErrorMessageID: val enumId = n + 1 if enumId >= 1 && enumId < ErrorMessageID.values.length then Some(fromOrdinal(enumId)) - else - None + else None diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index dcd7ed10987b..779b6f44387b 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -1539,6 +1539,19 @@ class ReassignmentToVal(name: Name)(using Context) |""" } +class ReassignmentToDef(name: Name)(using Context) + extends TypeMsg(ReassignmentToDefID) { + def msg(using Context) = i"""Reassignment to def $name""" + def explain(using Context) = + i"""|You can not assign a new definition to $name as definitions can't be changed. + |Keep in mind that every statement has a value, so you may e.g. use + | ${hl("val")} $name ${hl("= if (condition) 2 else 5")} + |In case you need a reassignable name, you can declare it as + |variable + | ${hl("var")} $name ${hl("=")} ... + |""" +} + class TypeDoesNotTakeParameters(tpe: Type, params: List[untpd.Tree])(using Context) extends TypeMsg(TypeDoesNotTakeParametersID) { private def fboundsAddendum(using Context) = diff --git a/compiler/src/dotty/tools/dotc/typer/Dynamic.scala b/compiler/src/dotty/tools/dotc/typer/Dynamic.scala index 14cc7bf963a6..1421f3291fdd 100644 --- a/compiler/src/dotty/tools/dotc/typer/Dynamic.scala +++ b/compiler/src/dotty/tools/dotc/typer/Dynamic.scala @@ -25,74 +25,102 @@ object Dynamic { private def isDynamicMethod(name: Name): Boolean = name == nme.applyDynamic || name == nme.selectDynamic || name == nme.updateDynamic || name == nme.applyDynamicNamed - /** Is `tree` a reference over `Dynamic` that should be expanded to a - * dyanmic `applyDynamic`, `selectDynamic`, `updateDynamic`, or `applyDynamicNamed` call? - */ + /** Is `tree` a reference over `Dynamic` that should be expanded to a dyanmic + * `applyDynamic`, `selectDynamic`, `updateDynamic`, or `applyDynamicNamed` + * call? + */ def isDynamicExpansion(tree: untpd.RefTree)(using Context): Boolean = isDynamicMethod(tree.name) - || tree.match - case Select(Apply(fun: untpd.RefTree, _), nme.apply) - if defn.isContextFunctionClass(fun.symbol.owner) => - isDynamicExpansion(fun) - case Select(qual, nme.apply) => - isDynamicMethod(qual.symbol.name) && tree.span.isSynthetic - case _ => false + || tree.match + case Select(Apply(fun: untpd.RefTree, _), nme.apply) + if defn.isContextFunctionClass(fun.symbol.owner) => + isDynamicExpansion(fun) + case Select(qual, nme.apply) => + isDynamicMethod(qual.symbol.name) && tree.span.isSynthetic + case _ => false } object DynamicUnapply { def unapply(tree: tpd.Tree): Option[List[tpd.Tree]] = tree match case TypeApply(Select(qual, name), _) if name == nme.asInstanceOfPM => unapply(qual) - case Apply(Apply(Select(selectable, fname), Literal(Constant(name)) :: ctag :: Nil), _ :: implicits) - if fname == nme.applyDynamic && (name == "unapply" || name == "unapplySeq") => + case Apply( + Apply( + Select(selectable, fname), + Literal(Constant(name)) :: ctag :: Nil + ), + _ :: implicits + ) + if fname == nme.applyDynamic && (name == "unapply" || name == "unapplySeq") => Some(selectable :: ctag :: implicits) case _ => None } /** Handles programmable member selections of `Dynamic` instances and values - * with structural types. Two functionalities: - * - * 1. Translates selection that does not typecheck according to the scala.Dynamic rules: - * foo.bar(baz) = quux ~~> foo.selectDynamic(bar).update(baz, quux) - * foo.bar = baz ~~> foo.updateDynamic("bar")(baz) - * foo.bar(x = bazX, y = bazY, baz, ...) ~~> foo.applyDynamicNamed("bar")(("x", bazX), ("y", bazY), ("", baz), ...) - * foo.bar(baz0, baz1, ...) ~~> foo.applyDynamic(bar)(baz0, baz1, ...) - * foo.bar ~~> foo.selectDynamic(bar) - * - * The first matching rule of is applied. - * - * 2. Translates member selections on structural types to calls of `selectDynamic` - * or `applyDynamic` on a `Selectable` instance. @See handleStructural. - * - */ + * with structural types. Two functionalities: + * + * 1. Translates selection that does not typecheck according to the + * scala.Dynamic rules: foo.bar(baz) = quux ~~> + * foo.selectDynamic(bar).update(baz, quux) foo.bar = baz ~~> + * foo.updateDynamic("bar")(baz) foo.bar(x = bazX, y = bazY, baz, ...) ~~> + * foo.applyDynamicNamed("bar")(("x", bazX), ("y", bazY), ("", baz), ...) + * foo.bar(baz0, baz1, ...) ~~> foo.applyDynamic(bar)(baz0, baz1, ...) + * foo.bar ~~> foo.selectDynamic(bar) + * + * The first matching rule of is applied. + * + * 2. Translates member selections on structural types to calls of + * `selectDynamic` or `applyDynamic` on a `Selectable` instance. @See + * handleStructural. + */ trait Dynamic { self: Typer & Applications => import Dynamic.* import tpd.* - /** Translate selection that does not typecheck according to the normal rules into a applyDynamic/applyDynamicNamed. - * foo.bar(baz0, baz1, ...) ~~> foo.applyDynamic(bar)(baz0, baz1, ...) - * foo.bar[T0, ...](baz0, baz1, ...) ~~> foo.applyDynamic[T0, ...](bar)(baz0, baz1, ...) - * foo.bar(x = bazX, y = bazY, baz, ...) ~~> foo.applyDynamicNamed("bar")(("x", bazX), ("y", bazY), ("", baz), ...) - * foo.bar[T0, ...](x = bazX, y = bazY, baz, ...) ~~> foo.applyDynamicNamed[T0, ...]("bar")(("x", bazX), ("y", bazY), ("", baz), ...) - */ - def typedDynamicApply(tree: untpd.Apply, isInsertedApply: Boolean, pt: Type)(using Context): Tree = { - def typedDynamicApply(qual: untpd.Tree, name: Name, selSpan: Span, targs: List[untpd.Tree]): Tree = { - def isNamedArg(arg: untpd.Tree): Boolean = arg match { case NamedArg(_, _) => true; case _ => false } + /** Translate selection that does not typecheck according to the normal rules + * into a applyDynamic/applyDynamicNamed. foo.bar(baz0, baz1, ...) ~~> + * foo.applyDynamic(bar)(baz0, baz1, ...) foo.bar[T0, ...](baz0, baz1, ...) + * ~~> foo.applyDynamic[T0, ...](bar)(baz0, baz1, ...) foo.bar(x = bazX, y = + * bazY, baz, ...) ~~> foo.applyDynamicNamed("bar")(("x", bazX), ("y", bazY), + * ("", baz), ...) foo.bar[T0, ...](x = bazX, y = bazY, baz, ...) ~~> + * foo.applyDynamicNamed[T0, ...]("bar")(("x", bazX), ("y", bazY), ("", baz), + * ...) + */ + def typedDynamicApply(tree: untpd.Apply, isInsertedApply: Boolean, pt: Type)( + using Context + ): Tree = { + def typedDynamicApply( + qual: untpd.Tree, + name: Name, + selSpan: Span, + targs: List[untpd.Tree] + ): Tree = { + def isNamedArg(arg: untpd.Tree): Boolean = arg match { + case NamedArg(_, _) => true; case _ => false + } val args = tree.args - val dynName = if (args.exists(isNamedArg)) nme.applyDynamicNamed else nme.applyDynamic + val dynName = + if (args.exists(isNamedArg)) nme.applyDynamicNamed else nme.applyDynamic if (dynName == nme.applyDynamicNamed && untpd.isWildcardStarArgList(args)) - errorTree(tree, em"applyDynamicNamed does not support passing a vararg parameter") + errorTree( + tree, + em"applyDynamicNamed does not support passing a vararg parameter" + ) else { - def namedArgTuple(name: String, arg: untpd.Tree) = untpd.Tuple(List(Literal(Constant(name)), arg)) + def namedArgTuple(name: String, arg: untpd.Tree) = + untpd.Tuple(List(Literal(Constant(name)), arg)) def namedArgs = args.map { case NamedArg(argName, arg) => namedArgTuple(argName.toString, arg) - case arg => namedArgTuple("", arg) + case arg => namedArgTuple("", arg) } val args1 = if (dynName == nme.applyDynamic) args else namedArgs - typedApply(untpd.Apply(coreDynamic(qual, dynName, name, selSpan, targs), args1), pt) + typedApply( + untpd.Apply(coreDynamic(qual, dynName, name, selSpan, targs), args1), + pt + ) } } @@ -107,7 +135,8 @@ trait Dynamic { tree.fun match { case sel @ Select(qual, name) if !isDynamicMethod(name) => typedDynamicApply(qual, name, sel.span, Nil) - case TypeApply(sel @ Select(qual, name), targs) if !isDynamicMethod(name) => + case TypeApply(sel @ Select(qual, name), targs) + if !isDynamicMethod(name) => typedDynamicApply(qual, name, sel.span, targs) case _ => errorTree(tree, em"Dynamic insertion not applicable") @@ -115,33 +144,72 @@ trait Dynamic { } } - /** Translate selection that does not typecheck according to the normal rules into a selectDynamic. - * foo.bar ~~> foo.selectDynamic(bar) - * foo.bar[T0, ...] ~~> foo.selectDynamic[T0, ...](bar) - * - * Note: inner part of translation foo.bar(baz) = quux ~~> foo.selectDynamic(bar).update(baz, quux) is achieved - * through an existing transformation of in typedAssign [foo.bar(baz) = quux ~~> foo.bar.update(baz, quux)]. - */ - def typedDynamicSelect(tree: untpd.Select, targs: List[untpd.Tree], pt: Type)(using Context): Tree = - typedApply(coreDynamic(tree.qualifier, nme.selectDynamic, tree.name, tree.span, targs), pt) + /** Translate selection that does not typecheck according to the normal rules + * into a selectDynamic. foo.bar ~~> foo.selectDynamic(bar) foo.bar[T0, ...] + * ~~> foo.selectDynamic[T0, ...](bar) + * + * Note: inner part of translation foo.bar(baz) = quux ~~> + * foo.selectDynamic(bar).update(baz, quux) is achieved through an existing + * transformation of in typedAssign [foo.bar(baz) = quux ~~> + * foo.bar.update(baz, quux)]. + */ + def typedDynamicSelect(tree: untpd.Select, targs: List[untpd.Tree], pt: Type)( + using Context + ): Tree = + typedApply( + coreDynamic( + tree.qualifier, + nme.selectDynamic, + tree.name, + tree.span, + targs + ), + pt + ) - /** Translate selection that does not typecheck according to the normal rules into a updateDynamic. - * foo.bar = baz ~~> foo.updateDynamic(bar)(baz) - */ + /** Translate selection that does not typecheck according to the normal rules + * into a updateDynamic. foo.bar = baz ~~> foo.updateDynamic(bar)(baz) + */ def typedDynamicAssign(tree: untpd.Assign, pt: Type)(using Context): Tree = { - def typedDynamicAssign(qual: untpd.Tree, name: Name, selSpan: Span, targs: List[untpd.Tree]): Tree = - typedApply(untpd.Apply(coreDynamic(qual, nme.updateDynamic, name, selSpan, targs), tree.rhs), pt) + def typedDynamicAssign( + qual: untpd.Tree, + name: Name, + selSpan: Span, + targs: List[untpd.Tree] + ): Tree = + typedApply( + untpd.Apply( + coreDynamic(qual, nme.updateDynamic, name, selSpan, targs), + tree.rhs + ), + pt + ) + + def reassignmentError = + val isDef = tree.lhs.symbol.is(Method) + val err = + if isDef then ReassignmentToDef(tree.lhs.symbol.name) + else ReassignmentToVal(tree.lhs.symbol.name) + errorTree(tree, err) + tree.lhs match { case sel @ Select(qual, name) if !isDynamicMethod(name) => typedDynamicAssign(qual, name, sel.span, Nil) - case TypeApply(sel @ Select(qual, name), targs) if !isDynamicMethod(name) => + case TypeApply(sel @ Select(qual, name), targs) + if !isDynamicMethod(name) => typedDynamicAssign(qual, name, sel.span, targs) case _ => - errorTree(tree, ReassignmentToVal(tree.lhs.symbol.name)) + reassignmentError } } - private def coreDynamic(qual: untpd.Tree, dynName: Name, name: Name, selSpan: Span, targs: List[untpd.Tree])(using Context): untpd.Apply = { + private def coreDynamic( + qual: untpd.Tree, + dynName: Name, + name: Name, + selSpan: Span, + targs: List[untpd.Tree] + )(using Context): untpd.Apply = { val select = untpd.Select(qual, dynName).withSpan(selSpan) val selectWithTypes = if (targs.isEmpty) select @@ -150,46 +218,48 @@ trait Dynamic { } /** Handle reflection-based dispatch for members of structural types. - * - * Given `x.a`, where `x` is of (widened) type `T` (a value type or a nullary method type), - * and `x.a` is of type `U`, map `x.a` to the equivalent of: - * - * ```scala - * x1.selectDynamic("a").asInstanceOf[U] - * ``` - * where `x1` is `x` adapted to `Selectable`. - * - * Given `x.a(a11, ..., a1n)...(aN1, ..., aNn)`, where `x.a` is of (widened) type - * `(T11, ..., T1n)...(TN1, ..., TNn): R`, it is desugared to: - * - * ```scala - * x1.applyDynamic("a")(a11, ..., a1n, ..., aN1, ..., aNn) - * .asInstanceOf[R] - * ``` - * If this call resolves to an `applyDynamic` method that takes a `Class[?]*` as second - * parameter, we further rewrite this call to - * scala``` - * x1.applyDynamic("a", c11, ..., c1n, ..., cN1, ... cNn) - * (a11, ..., a1n, ..., aN1, ..., aNn) - * .asInstanceOf[R] - * ``` - * where c11, ..., cNn are the classOf constants representing the erasures of T11, ..., TNn. - * - * It's an error if U is neither a value nor a method type, or a dependent method - * type - */ + * + * Given `x.a`, where `x` is of (widened) type `T` (a value type or a nullary + * method type), and `x.a` is of type `U`, map `x.a` to the equivalent of: + * + * ```scala + * x1.selectDynamic("a").asInstanceOf[U] + * ``` + * where `x1` is `x` adapted to `Selectable`. + * + * Given `x.a(a11, ..., a1n)...(aN1, ..., aNn)`, where `x.a` is of (widened) + * type `(T11, ..., T1n)...(TN1, ..., TNn): R`, it is desugared to: + * + * ```scala + * x1.applyDynamic("a")(a11, ..., a1n, ..., aN1, ..., aNn) + * .asInstanceOf[R] + * ``` + * If this call resolves to an `applyDynamic` method that takes a `Class[?]*` + * as second parameter, we further rewrite this call to scala``` + * x1.applyDynamic("a", c11, ..., c1n, ..., cN1, ... cNn) (a11, ..., a1n, + * ..., aN1, ..., aNn) .asInstanceOf[R] \``` where c11, ..., cNn are the + * classOf constants representing the erasures of T11, ..., TNn. + * + * It's an error if U is neither a value nor a method type, or a dependent + * method type + */ def handleStructural(tree: Tree)(using Context): Tree = { val fun @ Select(qual, name) = funPart(tree): @unchecked val vargss = termArgss(tree) def structuralCall(selectorName: TermName, classOfs: => List[Tree]) = { - val selectable = adapt(qual, defn.SelectableClass.typeRef | defn.DynamicClass.typeRef) + val selectable = + adapt(qual, defn.SelectableClass.typeRef | defn.DynamicClass.typeRef) // ($qual: Selectable).$selectorName("$name") val base = untpd.Apply( - untpd.Select(untpd.TypedSplice(selectable), selectorName).withSpan(fun.span), - (Literal(Constant(name.encode.toString)) :: Nil).map(untpd.TypedSplice(_))) + untpd + .Select(untpd.TypedSplice(selectable), selectorName) + .withSpan(fun.span), + (Literal(Constant(name.encode.toString)) :: Nil) + .map(untpd.TypedSplice(_)) + ) val scall = if (vargss.isEmpty) base @@ -202,14 +272,20 @@ trait Dynamic { cpy.Apply(tree)(addClassOfs(fn), args) case Apply(fn @ Select(_, nme.applyDynamic), nameArg :: _ :: Nil) => fn.tpe.widen match - case mt: MethodType => mt.paramInfos match - case _ :: classOfsParam :: Nil - if classOfsParam.isRepeatedParam - && classOfsParam.argInfos.head.isRef(defn.ClassClass) => - val jlClassType = defn.ClassClass.typeRef.appliedTo(TypeBounds.empty) - cpy.Apply(tree)(fn, - nameArg :: seqToRepeated(SeqLiteral(classOfs, TypeTree(jlClassType))) :: Nil) - case _ => tree + case mt: MethodType => + mt.paramInfos match + case _ :: classOfsParam :: Nil + if classOfsParam.isRepeatedParam + && classOfsParam.argInfos.head.isRef(defn.ClassClass) => + val jlClassType = + defn.ClassClass.typeRef.appliedTo(TypeBounds.empty) + cpy.Apply(tree)( + fn, + nameArg :: seqToRepeated( + SeqLiteral(classOfs, TypeTree(jlClassType)) + ) :: Nil + ) + case _ => tree case other => tree case _ => tree @@ -221,35 +297,45 @@ trait Dynamic { } def fail(reason: String): Tree = - errorTree(tree, em"Structural access not allowed on method $name because it $reason") + errorTree( + tree, + em"Structural access not allowed on method $name because it $reason" + ) extension (tree: Tree) - /** The implementations of `selectDynamic` and `applyDynamic` in `scala.reflect.SelectDynamic` have no information about the expected return type of a value/method which was declared in the refinement, - * only the JVM type after erasure can be obtained through reflection, e.g. - * - * class Foo(val i: Int) extends AnyVal - * class Reflective extends reflect.Selectable - * val reflective = new Reflective { - * def foo = Foo(1) // Foo at compile time, java.lang.Integer in reflection - * } - * - * Because of that reflective access cannot be implemented properly in `scala.reflect.SelectDynamic` itself - * because it's not known there if the value should be wrapped in a value class constructor call or not. - * Hence the logic of wrapping is performed here, relying on the fact that the implementations of `selectDynamic` and `applyDynamic` in `scala.reflect.SelectDynamic` are final. - */ + /** The implementations of `selectDynamic` and `applyDynamic` in + * `scala.reflect.SelectDynamic` have no information about the expected + * return type of a value/method which was declared in the refinement, + * only the JVM type after erasure can be obtained through reflection, + * e.g. + * + * class Foo(val i: Int) extends AnyVal class Reflective extends + * reflect.Selectable val reflective = new Reflective { def foo = Foo(1) + * // Foo at compile time, java.lang.Integer in reflection } + * + * Because of that reflective access cannot be implemented properly in + * `scala.reflect.SelectDynamic` itself because it's not known there if + * the value should be wrapped in a value class constructor call or not. + * Hence the logic of wrapping is performed here, relying on the fact + * that the implementations of `selectDynamic` and `applyDynamic` in + * `scala.reflect.SelectDynamic` are final. + */ def maybeBoxingCast(tpe: Type) = val maybeBoxed = - if tpe.classSymbol.isDerivedValueClass && qual.tpe <:< defn.ReflectSelectableTypeRef then - val genericUnderlying = ValueClasses.valueClassUnbox(tpe.classSymbol.asClass) + if tpe.classSymbol.isDerivedValueClass && qual.tpe <:< defn.ReflectSelectableTypeRef + then + val genericUnderlying = + ValueClasses.valueClassUnbox(tpe.classSymbol.asClass) val underlying = tpe.select(genericUnderlying).widen.resultType New(tpe.widen, tree.cast(underlying) :: Nil) - else - tree + else tree maybeBoxed.cast(tpe) fun.tpe.widen match { case tpe: ValueType => - structuralCall(nme.selectDynamic, Nil).maybeBoxingCast(fun.tpe.widenExpr) + structuralCall(nme.selectDynamic, Nil).maybeBoxingCast( + fun.tpe.widenExpr + ) case tpe: MethodType => def isDependentMethod(tpe: Type): Boolean = tpe match { @@ -265,11 +351,17 @@ trait Dynamic { fail(i"has a method type with inter-parameter dependencies") else { def classOfs = - if tpe.paramInfoss.nestedExists(!TypeErasure.hasStableErasure(_)) then - fail(i"has a parameter type with an unstable erasure") :: Nil + if tpe.paramInfoss.nestedExists(!TypeErasure.hasStableErasure(_)) + then fail(i"has a parameter type with an unstable erasure") :: Nil else - TypeErasure.erasure(tpe).asInstanceOf[MethodType].paramInfos.map(clsOf(_)) - structuralCall(nme.applyDynamic, classOfs).maybeBoxingCast(tpe.finalResultType) + TypeErasure + .erasure(tpe) + .asInstanceOf[MethodType] + .paramInfos + .map(clsOf(_)) + structuralCall(nme.applyDynamic, classOfs).maybeBoxingCast( + tpe.finalResultType + ) } // (@allanrenucci) I think everything below is dead code diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 6b7b840e7606..38f11a51cc57 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1388,10 +1388,14 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer val lhsCore = typedUnadapted(lhs, LhsProto, locked) def lhs1 = adapt(lhsCore, LhsProto, locked) - def reassignmentToVal = + def reassignmentToVal = report.error(ReassignmentToVal(lhsCore.symbol.name), tree.srcPos) cpy.Assign(tree)(lhsCore, typed(tree.rhs, lhs1.tpe.widen)).withType(defn.UnitType) + def reassignmentToDef = + report.error(ReassignmentToDef(lhsCore.symbol.name), tree.srcPos) + cpy.Assign(tree)(lhsCore, typed(tree.rhs, lhs1.tpe.widen)).withType(defn.UnitType) + def canAssign(sym: Symbol) = sym.isMutableVar || ctx.owner.isPrimaryConstructor && !sym.is(Method) && sym.maybeOwner == ctx.owner.owner || @@ -1452,8 +1456,9 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer case _ => lhsCore.tpe match { case ref: TermRef => val lhsVal = lhsCore.denot.suchThat(!_.is(Method)) + val isDef = !lhsVal.exists && lhsCore.denot.exists && lhsCore.denot.symbol.is(Method) val lhsSym = lhsVal.symbol - if canAssign(lhsSym) then + if (!isDef && lhsVal.exists && canAssign(lhsVal.symbol)) then rememberNonLocalAssignToPrivate(lhsSym) // lhsBounds: (T .. Any) as seen from lhs prefix, where T is the type of lhsSym // This ensures we do the as-seen-from on T with variance -1. Test case neg/i2928.scala @@ -1472,11 +1477,11 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer val lhs2 = untpd.rename(lhsCore, setterName).withType(setterType) typedUnadapted(untpd.Apply(untpd.TypedSplice(lhs2), tree.rhs :: Nil), WildcardType, locked) case _ => - reassignmentToVal + if isDef then reassignmentToDef else reassignmentToVal } case TryDynamicCallType => typedDynamicAssign(tree, pt) - case tpe => + case tpe => reassignmentToVal } } From a726470f8ce5a2ddecc28c53e3c1e0d71bf5678d Mon Sep 17 00:00:00 2001 From: ajafri2001 Date: Fri, 28 Mar 2025 05:29:32 +0530 Subject: [PATCH 2/2] Added negative test + better modern error messages (replaced if(cond) x to if cond then x) --- compiler/src/dotty/tools/dotc/reporting/messages.scala | 6 +++--- tests/neg/i22822.scala | 3 +++ 2 files changed, 6 insertions(+), 3 deletions(-) create mode 100644 tests/neg/i22822.scala diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index 779b6f44387b..6ecfd30b409a 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -1532,7 +1532,7 @@ class ReassignmentToVal(name: Name)(using Context) def explain(using Context) = i"""|You can not assign a new value to $name as values can't be changed. |Keep in mind that every statement has a value, so you may e.g. use - | ${hl("val")} $name ${hl("= if (condition) 2 else 5")} + | ${hl("val")} $name ${hl("= if condition then 2 else 5")} |In case you need a reassignable name, you can declare it as |variable | ${hl("var")} $name ${hl("=")} ... @@ -1543,9 +1543,9 @@ class ReassignmentToDef(name: Name)(using Context) extends TypeMsg(ReassignmentToDefID) { def msg(using Context) = i"""Reassignment to def $name""" def explain(using Context) = - i"""|You can not assign a new definition to $name as definitions can't be changed. + i"""|You can not assign a new value to $name as definitions can't be changed. |Keep in mind that every statement has a value, so you may e.g. use - | ${hl("val")} $name ${hl("= if (condition) 2 else 5")} + | ${hl("def(condition)")} $name ${hl("= if condition then 2 else 5")} |In case you need a reassignable name, you can declare it as |variable | ${hl("var")} $name ${hl("=")} ... diff --git a/tests/neg/i22822.scala b/tests/neg/i22822.scala new file mode 100644 index 000000000000..4805fa6287a5 --- /dev/null +++ b/tests/neg/i22822.scala @@ -0,0 +1,3 @@ +def main = + def x = 3 + x = 4 // error