diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index bdb3e5f28400..48f2d582c90a 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -1629,9 +1629,22 @@ class Namer { typer: Typer => def typedParentType(tree: untpd.Tree): tpd.Tree = val parentTpt = typer.typedType(parent, AnyTypeConstructorProto) val ptpe = parentTpt.tpe.dealias.etaCollapse - if ptpe.typeParams.nonEmpty - && ptpe.underlyingClassRef(refinementOK = false).exists - then + // Check if type params need to be inferred. This applies when: + // 1. The type has type params AND + // 2. Either it directly underlies a class (for class/trait refs), OR + // it's a HKTypeLambda whose result is a class application and params appear in result + // (the latter handles type aliases like `type Foo[T] = Bar[T]` but not + // type-lambda-as-result cases like `type Foo[X] = [Z] =>> Bar[X]`) + val needsTypeParamInference = ptpe.typeParams.nonEmpty && ( + ptpe.underlyingClassRef(refinementOK = false).exists + || (ptpe match + case tl: HKTypeLambda => + tl.paramRefs.exists(pref => tl.resType.existsPart(_ == pref)) + && !tl.resType.isInstanceOf[TypeLambda] + && tl.resType.underlyingClassRef(refinementOK = false).exists + case _ => false) + ) + if needsTypeParamInference then // Try to infer type parameters from a synthetic application. // This might yield new info if implicit parameters are resolved. // A test case is i16778.scala. diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 2cdb1b75bfaa..c13d3dff87c8 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1232,6 +1232,36 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer else tree1 } + /** Type a parent type reference in `new T { ... }` expressions. + * This handles type aliases that need type parameter inference + * by creating a synthetic constructor application, similar to + * how class parents are handled in Namer. + */ + def typedNewTemplateParent(tree: untpd.Tree, pt: Type)(using Context): Tree = + // Type with AnyTypeConstructorProto to detect type aliases (HKTypeLambda) + val parentTpt = typedType(tree, AnyTypeConstructorProto) + val ptpe = parentTpt.tpe.dealias + + ptpe match + case tl: HKTypeLambda + // Only handle type aliases where params appear in result (not type-lambda-as-result cases) + if tl.paramRefs.exists(pref => tl.resType.existsPart(_ == pref)) + && !tl.resType.isInstanceOf[TypeLambda] + && tl.resType.underlyingClassRef(refinementOK = false).exists => + // Found a type alias with type params whose result underlies a class. + // Create a synthetic constructor application to infer type arguments. + val app = untpd.Apply(untpd.Select(untpd.New(parentTpt), nme.CONSTRUCTOR), Nil) + val typedApp = typedExpr(app, pt) + TypeTree(typedApp.tpe).withSpan(tree.span) + case _ => + // For regular class/trait references, reuse the typed tree. + // If type has params but isn't a TypeLambda, eta-expand for inferTypeParams. + val resultTpe = parentTpt.tpe + if resultTpe.typeParams.nonEmpty && !resultTpe.isInstanceOf[TypeLambda] then + inferTypeParams(parentTpt.withType(resultTpe.etaExpand), pt) + else + inferTypeParams(parentTpt, pt) + def typedNew(tree: untpd.New, pt: Type)(using Context): Tree = tree.tpt match { case templ: untpd.Template => @@ -1248,7 +1278,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer then templ1 = cpy.Template(templ)(parents = untpd.TypeTree(pt) :: Nil) for case parent: RefTree <- templ1.parents do - typedAhead(parent, tree => inferTypeParams(typedType(tree), pt)) + typedAhead(parent, typedNewTemplateParent(_, pt)) val anon = tpnme.ANON_CLASS val clsDef = TypeDef(anon, templ1).withFlags(Final | Synthetic) typed( diff --git a/tests/pos/i19745.scala b/tests/pos/i19745.scala new file mode 100644 index 000000000000..df5e5273bdac --- /dev/null +++ b/tests/pos/i19745.scala @@ -0,0 +1,15 @@ +final abstract class ForcedRecompilationToken[T] +object ForcedRecompilationToken { + implicit def default: ForcedRecompilationToken["abc"] = null +} + +object x { + abstract class GoodNoParens[T](implicit ev: ForcedRecompilationToken[T]) +} +type BadNoParens[T] = x.GoodNoParens[T] + +object App extends App { + new BadNoParens {} + new BadNoParens() {} + new x.GoodNoParens {} +} diff --git a/tests/pos/i19745b.scala b/tests/pos/i19745b.scala new file mode 100644 index 000000000000..e780fab2e4d1 --- /dev/null +++ b/tests/pos/i19745b.scala @@ -0,0 +1,10 @@ +object x { + trait GoodNoParens[T] +} +export x.GoodNoParens as BadNoParens + +object App extends App { + new BadNoParens {} + new BadNoParens() {} + new x.GoodNoParens {} +}