From c6c06ce700543a2570ed05265be7068b4e5487d8 Mon Sep 17 00:00:00 2001 From: Kai <450507+neko-kai@users.noreply.github.com> Date: Mon, 15 Dec 2025 19:00:09 +0000 Subject: [PATCH 1/2] Fix #19745 type alias anonymous class instantiation When creating anonymous classes with `new T {}` where T is a type alias like `type T[X] = SomeClass[X]`, the compiler was failing to infer type parameters, resulting in "is not a class type" errors. The fix adds special handling for HKTypeLambda types in both Typer and Namer to create synthetic constructor applications for type parameter inference, similar to how class parents are handled. --- .../src/dotty/tools/dotc/typer/Namer.scala | 19 +++++++++++--- .../src/dotty/tools/dotc/typer/Typer.scala | 26 ++++++++++++++++++- tests/pos/i19745.scala | 15 +++++++++++ tests/pos/i19745b.scala | 10 +++++++ 4 files changed, 66 insertions(+), 4 deletions(-) create mode 100644 tests/pos/i19745.scala create mode 100644 tests/pos/i19745b.scala 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..e3ce2b522ef1 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1232,6 +1232,30 @@ 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 = + // First type with AnyTypeConstructorProto to check if it's a 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 _ => + inferTypeParams(typedType(tree), pt) + def typedNew(tree: untpd.New, pt: Type)(using Context): Tree = tree.tpt match { case templ: untpd.Template => @@ -1248,7 +1272,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 {} +} From 226e56ba9182dfee76cb2d7a419d6916f939d7c6 Mon Sep 17 00:00:00 2001 From: Kai Date: Mon, 15 Dec 2025 20:36:24 +0000 Subject: [PATCH 2/2] Try to avoid using `typedTree` twice by eta-expanding manually --- compiler/src/dotty/tools/dotc/typer/Typer.scala | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index e3ce2b522ef1..c13d3dff87c8 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1238,7 +1238,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer * how class parents are handled in Namer. */ def typedNewTemplateParent(tree: untpd.Tree, pt: Type)(using Context): Tree = - // First type with AnyTypeConstructorProto to check if it's a HKTypeLambda + // Type with AnyTypeConstructorProto to detect type aliases (HKTypeLambda) val parentTpt = typedType(tree, AnyTypeConstructorProto) val ptpe = parentTpt.tpe.dealias @@ -1254,7 +1254,13 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer val typedApp = typedExpr(app, pt) TypeTree(typedApp.tpe).withSpan(tree.span) case _ => - inferTypeParams(typedType(tree), pt) + // 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 {