diff --git a/community-build/community-projects/munit b/community-build/community-projects/munit index 996fe9410185..33b73b2d1009 160000 --- a/community-build/community-projects/munit +++ b/community-build/community-projects/munit @@ -1 +1 @@ -Subproject commit 996fe94101856f23d948e51cc5a677e665683d63 +Subproject commit 33b73b2d100984c3510c7d30d91817633c3df5f7 diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 5cf45fd2078d..aafdc006591b 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -645,8 +645,6 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { keywordStr("${") ~ toTextGlobal(dropBlock(tree)) ~ keywordStr("}") case TypSplice(tree) => keywordStr("${") ~ toTextGlobal(dropBlock(tree)) ~ keywordStr("}") - case tree: Applications.ExtMethodApply => - toText(tree.app) ~ Str("(ext method apply)").provided(printDebug) case Thicket(trees) => "Thicket {" ~~ toTextGlobal(trees, "\n") ~~ "}" case MacroTree(call) => diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 1a7203115e73..0421b3506b4f 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -196,23 +196,6 @@ object Applications { def wrapDefs(defs: mutable.ListBuffer[Tree], tree: Tree)(using Context): Tree = if (defs != null && defs.nonEmpty) tpd.Block(defs.toList, tree) else tree - abstract class AppProxy(implicit @constructorOnly src: SourceFile) extends ProxyTree { - def app: Tree - override def span = app.span - - def forwardTo = app - def canEqual(that: Any): Boolean = app.canEqual(that) - def productArity: Int = app.productArity - def productElement(n: Int): Any = app.productElement(n) - } - - /** A wrapper indicating that its argument is an application of an extension method. - */ - class ExtMethodApply(val app: Tree)(implicit @constructorOnly src: SourceFile) extends AppProxy: - overwriteType(app.tpe) - // ExtMethodApply always has wildcard type in order not to prompt any further adaptations - // such as eta expansion before the method is fully applied. - /** Find reference to default parameter getter for parameter #n in current * parameter list, or NoType if none was found */ diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index a7a509f0f804..0c654c3fb024 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -648,6 +648,11 @@ object Checking { else "Cannot override non-inline parameter with an inline parameter", p1.srcPos) + def checkConversionsSpecific(to: Type, pos: SrcPos)(using Context): Unit = + if to.isRef(defn.AnyValClass, skipRefined = false) + || to.isRef(defn.ObjectClass, skipRefined = false) + then + report.error(em"the result of an implicit conversion must be more specific than $to", pos) } trait Checking { diff --git a/compiler/src/dotty/tools/dotc/typer/Dynamic.scala b/compiler/src/dotty/tools/dotc/typer/Dynamic.scala index 355fcf4b42dc..232ad8456c0a 100644 --- a/compiler/src/dotty/tools/dotc/typer/Dynamic.scala +++ b/compiler/src/dotty/tools/dotc/typer/Dynamic.scala @@ -19,8 +19,21 @@ import ErrorReporting._ import reporting._ object Dynamic { - def isDynamicMethod(name: Name): Boolean = + 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? + */ + 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 } object DynamicUnapply { diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 0dfd224e9223..4e48b8b93cf3 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -396,12 +396,13 @@ object Implicits: } /** A successful search - * @param tree The typed tree that needs to be inserted - * @param ref The implicit reference that succeeded - * @param level The level where the reference was found + * @param tree The typed tree that needs to be inserted + * @param ref The implicit reference that succeeded + * @param level The level where the reference was found + * @param isExtension Whether the result is an extension method application * @param tstate The typer state to be committed if this alternative is chosen */ - case class SearchSuccess(tree: Tree, ref: TermRef, level: Int)(val tstate: TyperState, val gstate: GadtConstraint) + case class SearchSuccess(tree: Tree, ref: TermRef, level: Int, isExtension: Boolean = false)(val tstate: TyperState, val gstate: GadtConstraint) extends SearchResult with RefAndLevel with Showable /** A failed search */ @@ -491,12 +492,14 @@ object Implicits: class AmbiguousImplicits(val alt1: SearchSuccess, val alt2: SearchSuccess, val expectedType: Type, val argument: Tree) extends SearchFailureType { def explanation(using Context): String = em"both ${err.refStr(alt1.ref)} and ${err.refStr(alt2.ref)} $qualify" - override def whyNoConversion(using Context): String = { - val what = if (expectedType.isInstanceOf[SelectionProto]) "extension methods" else "conversions" - i""" - |Note that implicit $what cannot be applied because they are ambiguous; - |$explanation""" - } + override def whyNoConversion(using Context): String = + if !argument.isEmpty && argument.tpe.widen.isRef(defn.NothingClass) then + "" + else + val what = if (expectedType.isInstanceOf[SelectionProto]) "extension methods" else "conversions" + i""" + |Note that implicit $what cannot be applied because they are ambiguous; + |$explanation""" } class MismatchedImplicit(ref: TermRef, @@ -802,12 +805,11 @@ trait Implicits: val inferred = inferImplicit(adjust(to), from, from.span) inferred match { - case SearchSuccess(tree, ref, _) - if isOldStyleFunctionConversion(ref.underlying) && !tree.isInstanceOf[Applications.ExtMethodApply] => - report.migrationWarning( - i"The conversion ${ref} will not be applied implicitly here in Scala 3 because only implicit methods and instances of Conversion class will continue to work as implicit views.", - from - ) + case SearchSuccess(_, ref, _, false) if isOldStyleFunctionConversion(ref.underlying) => + report.migrationWarning( + i"The conversion ${ref} will not be applied implicitly here in Scala 3 because only implicit methods and instances of Conversion class will continue to work as implicit views.", + from + ) case _ => } @@ -829,7 +831,7 @@ trait Implicits: */ def inferImplicitArg(formal: Type, span: Span)(using Context): Tree = inferImplicit(formal, EmptyTree, span) match - case SearchSuccess(arg, _, _) => arg + case SearchSuccess(arg, _, _, _) => arg case fail @ SearchFailure(failed) => if fail.isAmbiguous then failed else @@ -937,7 +939,6 @@ trait Implicits: case Select(qual, _) => apply(x, qual) case Apply(fn, _) => apply(x, fn) case TypeApply(fn, _) => apply(x, fn) - case tree: Applications.AppProxy => apply(x, tree.app) case _: This => false case _ => foldOver(x, tree) } @@ -1026,13 +1027,23 @@ trait Implicits: pt, locked) } pt match - case selProto @ SelectionProto(selName: TermName, mbrType, _, _) if cand.isExtension => + case selProto @ SelectionProto(selName: TermName, mbrType, _, _) => + def tryExtension(using Context) = extMethodApply(untpd.Select(untpdGenerated, selName), argument, mbrType) - if cand.isConversion then + + def tryConversionForSelection(using Context) = + val converted = tryConversion + if !ctx.reporter.hasErrors && !selProto.isMatchedBy(converted.tpe) then + // this check is needed since adapting relative to a `SelectionProto` can succeed + // even if the term is not a subtype of the `SelectionProto` + err.typeMismatch(converted, selProto) + converted + + if cand.isExtension && cand.isConversion then val extensionCtx, conversionCtx = ctx.fresh.setNewTyperState() val extensionResult = tryExtension(using extensionCtx) - val conversionResult = tryConversion(using conversionCtx) + val conversionResult = tryConversionForSelection(using conversionCtx) if !extensionCtx.reporter.hasErrors then extensionCtx.typerState.commit() if !conversionCtx.reporter.hasErrors then @@ -1041,7 +1052,8 @@ trait Implicits: else conversionCtx.typerState.commit() conversionResult - else tryExtension + else if cand.isExtension then tryExtension + else tryConversionForSelection case _ => tryConversion } @@ -1060,10 +1072,7 @@ trait Implicits: SearchFailure(adapted.withType(new MismatchedImplicit(ref, pt, argument))) } else - val returned = - if (cand.isExtension) Applications.ExtMethodApply(adapted) - else adapted - SearchSuccess(returned, ref, cand.level)(ctx.typerState, ctx.gadt) + SearchSuccess(adapted, ref, cand.level, cand.isExtension)(ctx.typerState, ctx.gadt) } /** An implicit search; parameters as in `inferImplicit` */ @@ -1126,20 +1135,13 @@ trait Implicits: case alt1: SearchSuccess => var diff = compareAlternatives(alt1, alt2) assert(diff <= 0) // diff > 0 candidates should already have been eliminated in `rank` - if diff == 0 then + if diff == 0 && alt1.isExtension && alt2.isExtension then // Fall back: if both results are extension method applications, // compare the extension methods instead of their wrappers. - object extMethodApply: - def unapply(t: Tree): Option[Type] = t match - case t: Applications.ExtMethodApply => Some(methPart(stripApply(t.app)).tpe) - case _ => None - end extMethodApply - - (alt1.tree, alt2.tree) match - case (extMethodApply(ref1: TermRef), extMethodApply(ref2: TermRef)) => - diff = compare(ref1, ref2) + def stripExtension(alt: SearchSuccess) = methPart(stripApply(alt.tree)).tpe + (stripExtension(alt1), stripExtension(alt2)) match + case (ref1: TermRef, ref2: TermRef) => diff = compare(ref1, ref2) case _ => - if diff < 0 then alt2 else if diff > 0 then alt1 else SearchFailure(new AmbiguousImplicits(alt1, alt2, pt, argument), span) @@ -1565,7 +1567,7 @@ final class SearchRoot extends SearchHistory: else result match { case failure: SearchFailure => failure - case success @ SearchSuccess(tree, _, _) => + case success: SearchSuccess => import tpd._ // We might have accumulated dictionary entries for by name implicit arguments @@ -1588,7 +1590,7 @@ final class SearchRoot extends SearchHistory: else prune(in.map(_._2) ++ trees, out, in ++ acc) } - val pruned = prune(List(tree), implicitDictionary.map(_._2).toList, Nil) + val pruned = prune(List(success.tree), implicitDictionary.map(_._2).toList, Nil) myImplicitDictionary = null if (pruned.isEmpty) result else if (pruned.exists(_._2 == EmptyTree)) NoMatchingImplicitsFailure @@ -1645,7 +1647,7 @@ final class SearchRoot extends SearchHistory: case tree => tree }) - val res = resMap(tree) + val res = resMap(success.tree) val blk = Block(classDef :: inst :: Nil, res).withSpan(span) diff --git a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala index d374ca7678e3..1392ed688959 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala @@ -38,6 +38,14 @@ object Inferencing { result } + /** Try to fully define `tp`. Return whether constraint has changed. + * Any changed constraint is kept. + */ + def canDefineFurther(tp: Type)(using Context): Boolean = + val prevConstraint = ctx.typerState.constraint + isFullyDefined(tp, force = ForceDegree.all) + && (ctx.typerState.constraint ne prevConstraint) + /** The fully defined type, where all type variables are forced. * Throws an error if type contains wildcards. */ diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index d704bcee3f1f..79e1b739b542 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -16,7 +16,7 @@ import transform.SymUtils._ import Contexts._ import Names.{Name, TermName} import NameKinds.{InlineAccessorName, InlineBinderName, InlineScrutineeName, BodyRetainerName} -import ProtoTypes.selectionProto +import ProtoTypes.shallowSelectionProto import Annotations.Annotation import SymDenotations.SymDenotation import Inferencing.isFullyDefined @@ -1240,7 +1240,7 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(using Context) { override def typedSelect(tree: untpd.Select, pt: Type)(using Context): Tree = { assert(tree.hasType, tree) - val qual1 = typed(tree.qualifier, selectionProto(tree.name, pt, this)) + val qual1 = typed(tree.qualifier, shallowSelectionProto(tree.name, pt, this)) val resNoReduce = untpd.cpy.Select(tree)(qual1, tree.name).withType(tree.typeOpt) val resMaybeReduced = constToLiteral(reducer.reduceProjection(resNoReduce)) if (resNoReduce ne resMaybeReduced) diff --git a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index 3f3894c2b8be..1da24e4f8146 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -235,12 +235,11 @@ object ProtoTypes { /** Create a selection proto-type, but only one level deep; * treat constructors specially */ - def selectionProto(name: Name, tp: Type, typer: Typer)(using Context): TermType = + def shallowSelectionProto(name: Name, tp: Type, typer: Typer)(using Context): TermType = if (name.isConstructorName) WildcardType - else tp match { + else tp match case tp: UnapplyFunProto => new UnapplySelectionProto(name) case tp => SelectionProto(name, IgnoredProto(tp), typer, privateOK = true) - } /** A prototype for expressions [] that are in some unspecified selection operation * diff --git a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala index 4d3cb79ea95e..a50c50b19186 100644 --- a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -70,56 +70,39 @@ trait TypeAssigner { case ex: StaleSymbol => false } - /** If `tpe` is a named type, check that its denotation is accessible in the - * current context. Return the type with those alternatives as denotations - * which are accessible. + /** If `tpe` is a named type, return the type with those alternatives as denotations + * which are accessible (or NoType, if no alternatives are accessible). * * Also performs the following normalizations on the type `tpe`. - * (1) parameter accessors are always dereferenced. - * (2) if the owner of the denotation is a package object, it is assured + * (1) if the owner of the denotation is a package object, it is assured * that the package object shows up as the prefix. - * (3) in Java compilation units, `Object` is replaced by `defn.FromJavaObjectType` + * (2) in Java compilation units, `Object` is replaced by `defn.FromJavaObjectType` */ - def ensureAccessible(tpe: Type, superAccess: Boolean, pos: SrcPos)(using Context): Type = { - def test(tpe: Type, firstTry: Boolean): Type = tpe match { + def accessibleType(tpe: Type, superAccess: Boolean)(using Context): Type = + tpe match case tpe: NamedType => val pre = tpe.prefix val name = tpe.name + def postProcess(d: Denotation) = + if ctx.isJava && tpe.isAnyRef then defn.FromJavaObjectType + else TypeOps.makePackageObjPrefixExplicit(tpe withDenot d) val d = tpe.denot.accessibleFrom(pre, superAccess) - if !d.exists then + if d.exists then postProcess(d) + else // it could be that we found an inaccessible private member, but there is // an inherited non-private member with the same name and signature. - val d2 = pre.nonPrivateMember(name) - if (reallyExists(d2) && firstTry) - test(NamedType(pre, name, d2), false) - else if (pre.derivesFrom(defn.DynamicClass) && name.isTermName) - TryDynamicCallType - else { - val alts = tpe.denot.alternatives.map(_.symbol).filter(_.exists) - var packageAccess = false - val whatCanNot = alts match { - case Nil => - em"$name cannot" - case sym :: Nil => - em"${if (sym.owner == pre.typeSymbol) sym.show else sym.showLocated} cannot" - case _ => - em"none of the overloaded alternatives named $name can" - } - val where = if (ctx.owner.exists) s" from ${ctx.owner.enclosingClass}" else "" - val whyNot = new StringBuffer - alts foreach (_.isAccessibleFrom(pre, superAccess, whyNot)) - if (tpe.isError) tpe - else errorType(ex"$whatCanNot be accessed as a member of $pre$where.$whyNot", pos) - } - else if ctx.isJava && tpe.isAnyRef then - defn.FromJavaObjectType - else - TypeOps.makePackageObjPrefixExplicit(tpe withDenot d) - case _ => - tpe - } - test(tpe, true) - } + val d2 = pre.nonPrivateMember(name).accessibleFrom(pre, superAccess) + if reallyExists(d2) then postProcess(d2) + else NoType + case tpe => tpe + + /** Try to make `tpe` accessible, emit error if not possible */ + def ensureAccessible(tpe: Type, superAccess: Boolean, pos: SrcPos)(using Context): Type = + val tpe1 = accessibleType(tpe, superAccess) + if tpe1.exists then tpe1 + else tpe match + case tpe: NamedType => inaccessibleErrorType(tpe, superAccess, pos) + case NoType => tpe /** Return a potentially skolemized version of `qualTpe` to be used * as a prefix when selecting `name`. @@ -133,53 +116,66 @@ trait TypeAssigner { qualType /** The type of the selection `tree`, where `qual1` is the typed qualifier part. */ - def selectionType(tree: untpd.RefTree, qual1: Tree)(using Context): Type = { + def selectionType(tree: untpd.RefTree, qual1: Tree)(using Context): Type = var qualType = qual1.tpe.widenIfUnstable - if (!qualType.hasSimpleKind && tree.name != nme.CONSTRUCTOR) + if !qualType.hasSimpleKind && tree.name != nme.CONSTRUCTOR then // constructors are selected on typeconstructor, type arguments are passed afterwards qualType = errorType(em"$qualType takes type parameters", qual1.srcPos) - else if (!qualType.isInstanceOf[TermType]) + else if !qualType.isInstanceOf[TermType] then qualType = errorType(em"$qualType is illegal as a selection prefix", qual1.srcPos) + def arrayElemType = qual1.tpe.widen match + case JavaArrayType(elemtp) => elemtp + case qualType => + report.error("Expected Array but was " + qualType.show, tree.srcPos) + defn.NothingType + val name = tree.name - val pre = maybeSkolemizePrefix(qualType, name) - val mbr = qualType.findMember(name, pre) - def isDynamicExpansion(tree: untpd.RefTree): Boolean = { - Dynamic.isDynamicMethod(name) || ( - tree match - case Select(Apply(fun: untpd.RefTree, _), nme.apply) if defn.isContextFunctionClass(fun.symbol.owner) => - isDynamicExpansion(fun) - case Select(qual, nme.apply) => - Dynamic.isDynamicMethod(qual.symbol.name) && tree.span.isSynthetic - case _ => false - ) - } - if (reallyExists(mbr)) - qualType.select(name, mbr) - else if (qualType.derivesFrom(defn.DynamicClass) && name.isTermName && !isDynamicExpansion(tree)) - TryDynamicCallType - else if (qualType.isErroneous || name.toTermName == nme.ERROR) - UnspecifiedErrorType - else if couldInstantiateTypeVar(qualType) then - // try again with more defined qualifier type - selectionType(tree, qual1) - else if (name == nme.CONSTRUCTOR) - errorType(ex"$qualType does not have a constructor", tree.srcPos) - else { - val kind = if (name.isTypeName) "type" else "value" - def addendum = err.selectErrorAddendum(tree, qual1, qualType, importSuggestionAddendum) - errorType(NotAMember(qualType, name, kind, addendum), tree.srcPos) - } - } + val p = nme.primitive + name match + case p.arrayApply => MethodType(defn.IntType :: Nil, arrayElemType) + case p.arrayUpdate => MethodType(defn.IntType :: arrayElemType :: Nil, defn.UnitType) + case p.arrayLength => MethodType(Nil, defn.IntType) + // Note that we do not need to handle calls to Array[T]#clone() specially: + // The JLS section 10.7 says "The return type of the clone method of an array type + // T[] is T[]", but the actual return type at the bytecode level is Object which + // is casted to T[] by javac. Since the return type of Array[T]#clone() is Array[T], + // this is exactly what Erasure will do. + case _ => + val pre = maybeSkolemizePrefix(qualType, name) + val mbr = qualType.findMember(name, pre) + if reallyExists(mbr) then qualType.select(name, mbr) + else if qualType.isErroneous || name.toTermName == nme.ERROR then UnspecifiedErrorType + else NoType + end selectionType def importSuggestionAddendum(pt: Type)(using Context): String = "" - /** The type of the selection in `tree`, where `qual1` is the typed qualifier part. - * The selection type is additionally checked for accessibility. - */ - def accessibleSelectionType(tree: untpd.RefTree, qual1: Tree)(using Context): Type = - val ownType = selectionType(tree, qual1) - ensureAccessible(ownType, qual1.isInstanceOf[Super], tree.srcPos) + def notAMemberErrorType(tree: untpd.Select, qual: Tree)(using Context): ErrorType = + val qualType = qual.tpe.widenIfUnstable + def kind = if tree.isType then "type" else "value" + def addendum = err.selectErrorAddendum(tree, qual, qualType, importSuggestionAddendum) + val msg: Message = + if tree.name == nme.CONSTRUCTOR then ex"$qualType does not have a constructor" + else NotAMember(qualType, tree.name, kind, addendum) + errorType(msg, tree.srcPos) + + def inaccessibleErrorType(tpe: NamedType, superAccess: Boolean, pos: SrcPos)(using Context): Type = + val pre = tpe.prefix + val name = tpe.name + val alts = tpe.denot.alternatives.map(_.symbol).filter(_.exists) + val whatCanNot = alts match + case Nil => + em"$name cannot" + case sym :: Nil => + em"${if (sym.owner == pre.typeSymbol) sym.show else sym.showLocated} cannot" + case _ => + em"none of the overloaded alternatives named $name can" + val where = if (ctx.owner.exists) s" from ${ctx.owner.enclosingClass}" else "" + val whyNot = new StringBuffer + alts.foreach(_.isAccessibleFrom(pre, superAccess, whyNot)) + if tpe.isError then tpe + else errorType(ex"$whatCanNot be accessed as a member of $pre$where.$whyNot", pos) /** Type assignment method. Each method takes as parameters * - an untpd.Tree to which it assigns a type, @@ -189,32 +185,14 @@ trait TypeAssigner { def assignType(tree: untpd.Ident, tp: Type)(using Context): Ident = tree.withType(tp) - def assignType(tree: untpd.Select, qual: Tree)(using Context): Select = { - def qualType = qual.tpe.widen - def arrayElemType = { - qualType match { - case JavaArrayType(elemtp) => elemtp - case _ => - report.error("Expected Array but was " + qualType.show, tree.srcPos) - defn.NothingType - } - } - val p = nme.primitive - val tp = tree.name match { - case p.arrayApply => MethodType(defn.IntType :: Nil, arrayElemType) - case p.arrayUpdate => MethodType(defn.IntType :: arrayElemType :: Nil, defn.UnitType) - case p.arrayLength => MethodType(Nil, defn.IntType) - - // Note that we do not need to handle calls to Array[T]#clone() specially: - // The JLS section 10.7 says "The return type of the clone method of an array type - // T[] is T[]", but the actual return type at the bytecode level is Object which - // is casted to T[] by javac. Since the return type of Array[T]#clone() is Array[T], - // this is exactly what Erasure will do. - - case _ => accessibleSelectionType(tree, qual) - } + def assignType(tree: untpd.Select, tp: Type)(using Context): Select = ConstFold.Select(tree.withType(tp)) - } + + def assignType(tree: untpd.Select, qual: Tree)(using Context): Select = + val rawType = selectionType(tree, qual) + val checkedType = ensureAccessible(rawType, qual.isInstanceOf[Super], tree.srcPos) + val ownType = checkedType.orElse(notAMemberErrorType(tree, qual)) + assignType(tree, ownType) /** Normalize type T appearing in a new T by following eta expansions to * avoid higher-kinded types. diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index ab90bce8b6b0..ae71b3de6416 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -25,12 +25,13 @@ import Decorators._ import ErrorReporting._ import Checking._ import Inferencing._ +import Dynamic.isDynamicExpansion import EtaExpansion.etaExpand import TypeComparer.CompareResult import util.Spans._ import util.common._ import util.{Property, SimpleIdentityMap, SrcPos} -import Applications.{ExtMethodApply, productSelectorTypes, wrapDefs, defaultArgument} +import Applications.{productSelectorTypes, wrapDefs, defaultArgument} import collection.mutable import annotation.tailrec @@ -547,29 +548,47 @@ class Typer extends Namer then report.error(StableIdentPattern(tree, pt), tree.srcPos) - def typedSelect(tree: untpd.Select, pt: Type, qual: Tree)(using Context): Tree = qual match { - case qual: ExtMethodApply => - qual.app - case qual => - val select = assignType(cpy.Select(tree)(qual, tree.name), qual) - val select1 = toNotNullTermRef(select, pt) - - if (tree.name.isTypeName) checkStable(qual.tpe, qual.srcPos, "type prefix") - - if select1.tpe ne TryDynamicCallType then - checkStableIdentPattern(select1, pt) - ConstFold(select1) - else if pt.isInstanceOf[FunOrPolyProto] || pt == AssignProto then - select1 + def typedSelect(tree0: untpd.Select, pt: Type, qual: Tree)(using Context): Tree = + val selName = tree0.name + val tree = cpy.Select(tree0)(qual, selName) + val superAccess = qual.isInstanceOf[Super] + val rawType = selectionType(tree, qual) + val checkedType = accessibleType(rawType, superAccess) + if checkedType.exists then + val select = toNotNullTermRef(assignType(tree, checkedType), pt) + if selName.isTypeName then checkStable(qual.tpe, qual.srcPos, "type prefix") + checkStableIdentPattern(select, pt) + ConstFold(select) + else if couldInstantiateTypeVar(qual.tpe.widen) then + // try again with more defined qualifier type + typedSelect(tree, pt, qual) + else + val tree1 = tryExtensionOrConversion( + tree, pt, IgnoredProto(pt), qual, ctx.typerState.ownedVars, this, privateOK = true) + if !tree1.isEmpty then + tree1 + else if qual.tpe.derivesFrom(defn.DynamicClass) + && selName.isTermName && !isDynamicExpansion(tree) + then + val tree2 = cpy.Select(tree0)(untpd.TypedSplice(qual), selName) + if pt.isInstanceOf[FunOrPolyProto] || pt == AssignProto then + assignType(tree2, TryDynamicCallType) + else + typedDynamicSelect(tree2, Nil, pt) else - typedDynamicSelect(tree, Nil, pt) - } + assignType(tree, + rawType match + case rawType: NamedType => + inaccessibleErrorType(rawType, superAccess, tree.srcPos) + case _ => + notAMemberErrorType(tree, qual)) + end typedSelect def typedSelect(tree: untpd.Select, pt: Type)(using Context): Tree = { record("typedSelect") def typeSelectOnTerm(using Context): Tree = - typedSelect(tree, pt, typedExpr(tree.qualifier, selectionProto(tree.name, pt, this))) + typedSelect(tree, pt, typedExpr(tree.qualifier, shallowSelectionProto(tree.name, pt, this))) .withSpan(tree.span) .computeNullable() @@ -586,7 +605,7 @@ class Typer extends Namer tryAlternatively(typeSelectOnTerm)(fallBack) if (tree.qualifier.isType) { - val qual1 = typedType(tree.qualifier, selectionProto(tree.name, pt, this)) + val qual1 = typedType(tree.qualifier, shallowSelectionProto(tree.name, pt, this)) assignType(cpy.Select(tree)(qual1, tree.name), qual1) } else if (ctx.isJava && tree.name.isTypeName) @@ -658,7 +677,7 @@ class Typer extends Namer case Floating => defn.FromDigits_FloatingClass } inferImplicit(fromDigitsCls.typeRef.appliedTo(target), EmptyTree, tree.span) match { - case SearchSuccess(arg, _, _) => + case SearchSuccess(arg, _, _, _) => val fromDigits = untpd.Select(untpd.TypedSplice(arg), nme.fromDigits).withSpan(tree.span) val firstArg = Literal(Constant(digits)) val otherArgs = tree.kind match { @@ -819,7 +838,7 @@ class Typer extends Namer withoutMode(Mode.Pattern)( inferImplicit(tpe, EmptyTree, tree.tpt.span) ) match - case SearchSuccess(clsTag, _, _) => + case SearchSuccess(clsTag, _, _, _) => withMode(Mode.InTypeTest) { Some(typed(untpd.Apply(untpd.TypedSplice(clsTag), untpd.TypedSplice(tree.expr)), pt)) } @@ -2604,9 +2623,7 @@ class Typer extends Namer /** Interpolate and simplify the type of the given tree. */ protected def simplify(tree: Tree, pt: Type, locked: TypeVars)(using Context): tree.type = - if !tree.denot.isOverloaded // for overloaded trees: resolve overloading before simplifying - && !tree.isInstanceOf[Applications.AppProxy] // don't interpolate in the middle of an extension method application - then + if !tree.denot.isOverloaded then // for overloaded trees: resolve overloading before simplifying if !tree.tpe.widen.isInstanceOf[MethodOrPoly] // wait with simplifying until method is fully applied || tree.isDef // ... unless tree is a definition then @@ -2880,23 +2897,80 @@ class Typer extends Namer } } - /** If this tree is a select node `qual.name`, try to insert an implicit conversion - * `c` around `qual` so that `c(qual).name` conforms to `pt`. + /** If this tree is a select node `qual.name` that does not conform to `pt`, + * try to insert an implicit conversion `c` around `qual` so that + * `c(qual).name` conforms to `pt`. */ def tryInsertImplicitOnQualifier(tree: Tree, pt: Type, locked: TypeVars)(using Context): Option[Tree] = trace(i"try insert impl on qualifier $tree $pt") { tree match { - case Select(qual, name) if name != nme.CONSTRUCTOR => - val qualProto = SelectionProto(name, pt, NoViewsAllowed, privateOK = false) - tryEither { - val qual1 = adapt(qual, qualProto, locked) - if ((qual eq qual1) || summon[Context].reporter.hasErrors) None - else Some(typed(cpy.Select(tree)(untpd.TypedSplice(qual1), name), pt, locked)) - } { (_, _) => None - } + case tree @ Select(qual, name) if name != nme.CONSTRUCTOR => + val selProto = SelectionProto(name, pt, NoViewsAllowed, privateOK = false) + if selProto.isMatchedBy(qual.tpe) then None + else + tryEither { + val tree1 = tryExtensionOrConversion(tree, pt, pt, qual, locked, NoViewsAllowed, privateOK = false) + if tree1.isEmpty then None + else Some(adapt(tree1, pt, locked)) + } { (_, _) => None + } case _ => None } } + /** Given a selection `qual.name`, try to convert to an extension method + * application `name(qual)` or insert an implicit conversion `c(qual).name`. + * @return The converted tree, or `EmptyTree` is not successful. + */ + def tryExtensionOrConversion + (tree: untpd.Select, pt: Type, mbrProto: Type, qual: Tree, locked: TypeVars, compat: Compatibility, privateOK: Boolean) + (using Context): Tree = + + def selectionProto = SelectionProto(tree.name, mbrProto, compat, privateOK) + + def tryExtension(using Context): Tree = + findRef(tree.name, WildcardType, ExtensionMethod, EmptyFlags, qual.srcPos) match + case ref: TermRef => + extMethodApply(untpd.ref(ref).withSpan(tree.span), qual, pt) + case _ => + EmptyTree + + // try an extension method in scope + try + val nestedCtx = ctx.fresh.setNewTyperState() + val app = tryExtension(using nestedCtx) + if !app.isEmpty && !nestedCtx.reporter.hasErrors then + nestedCtx.typerState.commit() + return app + for err <- nestedCtx.reporter.allErrors.take(1) do + rememberSearchFailure(qual, + SearchFailure(app.withType(FailedExtension(app, selectionProto, err.msg)))) + catch case ex: TypeError => + rememberSearchFailure(qual, + SearchFailure(qual.withType(NestedFailure(ex.toMessage, selectionProto)))) + + // try an implicit conversion or given extension + if ctx.mode.is(Mode.ImplicitsEnabled) && qual.tpe.isValueType then + trace(i"try insert impl on qualifier $tree $pt") { + val selProto = selectionProto + inferView(qual, selProto) match + case SearchSuccess(found, _, _, isExtension) => + if isExtension then return found + else + checkImplicitConversionUseOK(found) + return typedSelect(tree, pt, found) + case failure: SearchFailure => + if failure.isAmbiguous then + return ( + if canDefineFurther(qual.tpe.widen) then + tryExtensionOrConversion(tree, pt, mbrProto, qual, locked, compat, privateOK) + else + err.typeMismatch(qual, selProto, failure.reason) // TODO: report NotAMember instead, but need to be aware of failure + ) + rememberSearchFailure(qual, failure) + } + EmptyTree + end tryExtensionOrConversion + /** Perform the following adaptations of expression, pattern or type `tree` wrt to * given prototype `pt`: * (1) Resolve overloading @@ -3461,7 +3535,7 @@ class Typer extends Namer case _ => tp } - def adaptToSubType(wtp: Type): Tree = { + def adaptToSubType(wtp: Type): Tree = // try converting a constant to the target type ConstFold(tree).tpe.widenTermRefExpr.normalized match case ConstantType(x) => @@ -3500,48 +3574,6 @@ class Typer extends Namer case _ => } - // try GADT approximation, but only if we're trying to select a member - // Member lookup cannot take GADTs into account b/c of cache, so we - // approximate types based on GADT constraints instead. For an example, - // see MemberHealing in gadt-approximation-interaction.scala. - pt match { - case pt: SelectionProto if ctx.gadt.nonEmpty => - gadts.println(i"Trying to heal member selection by GADT-approximating $wtp") - val gadtApprox = Inferencing.approximateGADT(wtp) - gadts.println(i"GADT-approximated $wtp ~~ $gadtApprox") - if pt.isMatchedBy(gadtApprox) then - gadts.println(i"Member selection healed by GADT approximation") - return tpd.Typed(tree, TypeTree(gadtApprox)) - case _ => ; - } - - // try an extension method in scope - pt match { - case selProto @ SelectionProto(selName: TermName, mbrType, _, _) => - - def tryExtension(using Context): Tree = - findRef(selName, WildcardType, ExtensionMethod, EmptyFlags, tree.srcPos) match - case ref: TermRef => - extMethodApply(untpd.ref(ref).withSpan(tree.span), tree, mbrType) - case _ => - EmptyTree - - try - val nestedCtx = ctx.fresh.setNewTyperState() - val app = tryExtension(using nestedCtx) - if !app.isEmpty && !nestedCtx.reporter.hasErrors then - nestedCtx.typerState.commit() - return ExtMethodApply(app) - else - for err <- nestedCtx.reporter.allErrors.take(1) do - rememberSearchFailure(tree, - SearchFailure(app.withType(FailedExtension(app, pt, err.msg)))) - catch case ex: TypeError => - rememberSearchFailure(tree, - SearchFailure(tree.withType(NestedFailure(ex.toMessage, pt)))) - case _ => - } - // try an Any -> Matchable conversion if pt.isMatchableBound && !wtp.derivesFrom(defn.MatchableClass) then checkMatchable(wtp, tree.srcPos, pattern = false) @@ -3549,36 +3581,45 @@ class Typer extends Namer if target <:< pt then return readapt(tree.cast(target)) - // try an implicit conversion - val prevConstraint = ctx.typerState.constraint def recover(failure: SearchFailureType) = - if (isFullyDefined(wtp, force = ForceDegree.all) && - ctx.typerState.constraint.ne(prevConstraint)) readapt(tree) + if canDefineFurther(wtp) then readapt(tree) else err.typeMismatch(tree, pt, failure) - if ctx.mode.is(Mode.ImplicitsEnabled) && tree.typeOpt.isValueType then - if pt.isRef(defn.AnyValClass, skipRefined = false) - || pt.isRef(defn.ObjectClass, skipRefined = false) - then - report.error(em"the result of an implicit conversion must be more specific than $pt", tree.srcPos) - inferView(tree, pt) match { - case SearchSuccess(found: ExtMethodApply, _, _) => - found // nothing to check or adapt for extension method applications - case SearchSuccess(found, _, _) => - checkImplicitConversionUseOK(found) - withoutMode(Mode.ImplicitsEnabled)(readapt(found)) - case failure: SearchFailure => - if (pt.isInstanceOf[ProtoType] && !failure.isAmbiguous) then - // don't report the failure but return the tree unchanged. This - // will cause a failure at the next level out, which usually gives - // a better error message. To compensate, store the encountered failure - // as an attachment, so that it can be reported later as an addendum. - rememberSearchFailure(tree, failure) - tree - else recover(failure.reason) - } - else recover(NoMatchingImplicits) - } + pt match + case pt: SelectionProto => + if ctx.gadt.nonEmpty then + // try GADT approximation if we're trying to select a member + // Member lookup cannot take GADTs into account b/c of cache, so we + // approximate types based on GADT constraints instead. For an example, + // see MemberHealing in gadt-approximation-interaction.scala. + gadts.println(i"Trying to heal member selection by GADT-approximating $wtp") + val gadtApprox = Inferencing.approximateGADT(wtp) + gadts.println(i"GADT-approximated $wtp ~~ $gadtApprox") + if pt.isMatchedBy(gadtApprox) then + gadts.println(i"Member selection healed by GADT approximation") + tpd.Typed(tree, TypeTree(gadtApprox)) + else tree + else tree // other adaptations for selections are handled in typedSelect + case _ if ctx.mode.is(Mode.ImplicitsEnabled) && tree.tpe.isValueType => + checkConversionsSpecific(pt, tree.srcPos) + inferView(tree, pt) match + case SearchSuccess(found, _, _, isExtension) => + if isExtension then found + else + checkImplicitConversionUseOK(found) + withoutMode(Mode.ImplicitsEnabled)(readapt(found)) + case failure: SearchFailure => + if (pt.isInstanceOf[ProtoType] && !failure.isAmbiguous) then + // don't report the failure but return the tree unchanged. This + // will cause a failure at the next level out, which usually gives + // a better error message. To compensate, store the encountered failure + // as an attachment, so that it can be reported later as an addendum. + rememberSearchFailure(tree, failure) + tree + else recover(failure.reason) + case _ => + recover(NoMatchingImplicits) + end adaptToSubType def adaptType(tp: Type): Tree = { val tree1 = @@ -3658,8 +3699,6 @@ class Typer extends Namer else adaptToArgs(wtp, pt) case pt: PolyProto => tryInsertApplyOrImplicit(tree, pt, locked)(tree) // error will be reported in typedTypeApply - case pt: SelectionProto if tree.isInstanceOf[ExtMethodApply] => - tree case _ => if (ctx.mode is Mode.Type) adaptType(tree.tpe) else adaptNoArgs(wtp) diff --git a/compiler/test/dotc/pos-test-pickling.blacklist b/compiler/test/dotc/pos-test-pickling.blacklist index 862a452f0bca..3df8675fbf15 100644 --- a/compiler/test/dotc/pos-test-pickling.blacklist +++ b/compiler/test/dotc/pos-test-pickling.blacklist @@ -55,3 +55,6 @@ annot-bootstrap.scala # interaction with Scala-2's implicitly i9793.scala +# lazy_implicit symbol has different position after pickling +i8182.scala + diff --git a/tests/neg/enum-values.check b/tests/neg/enum-values.check index 3b22071aa808..84df5889b500 100644 --- a/tests/neg/enum-values.check +++ b/tests/neg/enum-values.check @@ -20,8 +20,8 @@ | | example.Extensions.values(ListLike) failed with | - | Found: example.ListLike.type - | Required: Nothing + | Found: Array[example.Tag[?]] + | Required: Array[example.ListLike[?]] -- [E008] Not Found Error: tests/neg/enum-values.scala:34:52 ----------------------------------------------------------- 34 | val typeCtorsK: Array[TypeCtorsK[?]] = TypeCtorsK.values // error | ^^^^^^^^^^^^^^^^^ @@ -32,8 +32,8 @@ | | example.Extensions.values(TypeCtorsK) failed with | - | Found: example.TypeCtorsK.type - | Required: Nothing + | Found: Array[example.Tag[?]] + | Required: Array[example.TypeCtorsK[?[_$1]]] -- [E008] Not Found Error: tests/neg/enum-values.scala:36:6 ------------------------------------------------------------ 36 | Tag.valueOf("Int") // error | ^^^^^^^^^^^ diff --git a/tests/neg/i6779.check b/tests/neg/i6779.check index 4e1ebd5da972..b4e12d34b8aa 100644 --- a/tests/neg/i6779.check +++ b/tests/neg/i6779.check @@ -3,11 +3,16 @@ | ^^^^^^^^^^^^^^^^^^^^^^^^ | Found: F[T] | Required: F[G[T]] --- [E007] Type Mismatch Error: tests/neg/i6779.scala:12:31 ------------------------------------------------------------- +-- [E008] Not Found Error: tests/neg/i6779.scala:12:31 ----------------------------------------------------------------- 12 | def g2[T](x: T): F[G[T]] = x.f // error | ^^^ - | Found: F[T] - | Required: F[G[T]] + | value f is not a member of T. + | An extension method was tried, but could not be fully constructed: + | + | Test.f[G[T]](x)(given_Stuff) failed with + | + | Found: (x : T) + | Required: G[T] -- [E007] Type Mismatch Error: tests/neg/i6779.scala:14:38 ------------------------------------------------------------- 14 | def g3[T](x: T): F[G[T]] = this.f(x)(using summon[Stuff]) // error | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/neg/i8032.check b/tests/neg/i8032.scheck similarity index 100% rename from tests/neg/i8032.check rename to tests/neg/i8032.scheck diff --git a/tests/pos/i8182.scala b/tests/pos/i8182.scala new file mode 100644 index 000000000000..9acf2941c570 --- /dev/null +++ b/tests/pos/i8182.scala @@ -0,0 +1,10 @@ +package example + +trait Show[-A]: + extension (a: A) def show: String + +given (using rec: Show[String]): Show[String] = ??? // must be Show[String] as the argument + +given (using rec: => Show[String]): Show[Option[String]] = ??? // must be byname argument + +def test = Option("").show diff --git a/tests/semanticdb/expect/Enums.expect.scala b/tests/semanticdb/expect/Enums.expect.scala index 1e91f30acf4b..d74e49ccf98d 100644 --- a/tests/semanticdb/expect/Enums.expect.scala +++ b/tests/semanticdb/expect/Enums.expect.scala @@ -52,7 +52,7 @@ object Enums/*<-_empty_::Enums.*/: extension [A/*<-_empty_::Enums.unwrap().[A]*/, B/*<-_empty_::Enums.unwrap().[B]*/](opt/*<-_empty_::Enums.unwrap().(opt)*/: Option/*->scala::Option#*/[A/*->_empty_::Enums.unwrap().[A]*/]) def unwrap/*<-_empty_::Enums.unwrap().*/(using ev/*<-_empty_::Enums.unwrap().(ev)*/: A/*->_empty_::Enums.unwrap().[A]*/ <:_empty_::Enums.`<:<`#*/ Option/*->scala::Option#*/[B/*->_empty_::Enums.unwrap().[B]*/]): Option/*->scala::Option#*/[B/*->_empty_::Enums.unwrap().[B]*/] = ev/*->_empty_::Enums.unwrap().(ev)*/ match case Refl/*->_empty_::Enums.`<:<`.Refl.*//*->_empty_::Enums.`<:<`.Refl.unapply().*/() => opt/*->_empty_::Enums.unwrap().(opt)*/.flatMap/*->scala::Option#flatMap().*/(identity/*->scala::Predef.identity().*//*->local0*/[Option/*->scala::Option#*/[B/*->_empty_::Enums.unwrap().[B]*/]]) - val some1/*<-_empty_::Enums.some1.*/ = /*->_empty_::Enums.unwrap().*/Some/*->scala::Some.*//*->scala::Some.apply().*/(Some/*->scala::Some.*//*->scala::Some.apply().*/(1))/*->_empty_::Enums.`<:<`.given_T().*/.unwrap + val some1/*<-_empty_::Enums.some1.*/ = /*->_empty_::Enums.unwrap().*/Some/*->scala::Some.*//*->scala::Some.apply().*/(Some/*->scala::Some.*//*->scala::Some.apply().*/(1)).unwrap/*->_empty_::Enums.`<:<`.given_T().*/ enum Planet/*<-_empty_::Enums.Planet#*/(mass/*<-_empty_::Enums.Planet#mass.*/: Double/*->scala::Double#*/, radius/*<-_empty_::Enums.Planet#radius.*/: Double/*->scala::Double#*/) extends Enum/*->java::lang::Enum#*/[Planet/*->_empty_::Enums.Planet#*/]/*->java::lang::Enum#``().*/: private final val G/*<-_empty_::Enums.Planet#G.*/ = 6.67300E-11 diff --git a/tests/semanticdb/metac.expect b/tests/semanticdb/metac.expect index 77bdc43c3182..c13392fb7ebc 100644 --- a/tests/semanticdb/metac.expect +++ b/tests/semanticdb/metac.expect @@ -971,7 +971,7 @@ Occurrences: [54:18..54:18): -> scala/Some.apply(). [54:19..54:23): Some -> scala/Some. [54:23..54:23): -> scala/Some.apply(). -[54:27..54:27): -> _empty_/Enums.`<:<`.given_T(). +[54:34..54:34): -> _empty_/Enums.`<:<`.given_T(). [56:7..56:13): Planet <- _empty_/Enums.Planet# [56:13..56:13): <- _empty_/Enums.Planet#``(). [56:14..56:18): mass <- _empty_/Enums.Planet#mass.