Unintuitive meaning of some recursive type aliases

Hello,
I’ve been writing an interpreter for Toi, a language based on set theory
I therefore wanted a type like this: Set[Set[Set[...]]], so I tried the obvious thing:

type NestedSet1 = Set[NestedSet1] 
// illegal cyclic type reference: alias Set[Playground.NestedSet1] of type NestedSet1 refers back to the type itself

Q1: Could a type like this exist/work in the compiler ? (regardless of the semantics of defining it)
I believe there are already types that get unrolled as needed in the compiler ?
(I can’t think of a way it could cause an infinite loop during compilation)

Okay, fair enough, so I switched to case class S(w: Set[S])
Which works fine, but is very annoying to use, since I have to manually create a lot of alias methods
And an export wouldn’t work, as methods like union will still take a Set[S], and not an S, as parameter
(Also there’s some boxing) (I haven’t tried implicit conversions yet, but some say they should always be a last resort)

With a bit more digging, I found out this compiles: (but not in scala 2)

type _NestedSet2[_] = Set[_NestedSet2[?]]
type NestedSet2 = _NestedSet2[Nothing]

This however seems to create Set[Any] instead of the wanted type !
See this scastie

I am not the first one to get confused by this: stack overflow - Recursive type in Scala 3 - Stack Overflow

I couldn’t find any info about those kind of recursive type aliases, this page of the scala 2 spec doesn’t mention this exact case, but seems to indicate it shouldn’t compile

Q2 Is NestedSet2 supposed to compile ?
Q3 If yes, is it supposed to alias Set[Any] ?
P4 If yes, something should notify the user the alias isn’t a fixed point of
_NestedSet2[X] =:= Set[_NestedSet2[<some type>]]
(note that a warning is emitted for val self = Set(self) with -Ysafe-init)

1 Like

So currently recursive type aliases are not supposed to be legal in Scala.

There are various workarounds. For examples I’ve seen people use match types to do it:

type NestedSet1[A] = A match
  case Nothing => Nothing // used to turn of the cycle detector...
  case String => Set[NestedSet1[A]]

type S = NestedSet1[String]

// expected:
val s0: S = Set()       ; s0
val s1: S = Set(Set())  ; s1
val s2: S = Set(s0, s1) ; s2

// unexpected:
// val sAny: S = (Set("hello"): Set[Any]) ; sAny // now an error

val unsound: S = s1.head // works

One can also probably tie such indirect recursive knots through intersection types/refinements.

This looks like a bug. I don’t see why Scala 3 accepts the first recursive type alias definition. And it also accepts a type alias with wildcard type argument, which it shouldn’t.

This looks like a couple of other bugs!

  1. I think it shouldn’t let you upcast a Set[Any] into an S;
  2. It should probably let you type s1.head as an S. In any case there should be no Set[Any] in there. Looks like an avoidance/wildcard bug (cc @smarter).

Very cool trick !
My question is then, since they work, why are they not supposed to be supported ?

My guess is that _NestedSet2[?] is replaced by Any instead of something like _NestedSet2[Any], hence why S = Set[Any]
(I tried to poke around and that really seems to be the case, not just an upcast)
This would cleanly explain both 1. and 2.

After some testing, the following doesn’t work:

type NestedSet1[A] = A match
  case String => Nothing // used to turn of the cycle detector...
  case Nothing => Set[NestedSet1[A]]

type S = NestedSet1[Nothing]

This is really weird behavior ^^’
on scastie:

If you are interested you can check out this issue. It’s closed but supposedly can be reopened if someone cares enough to work on it.

Nice, thank you !
The question then becomes: should we allow the following ?

type Rec[F[_], A] = A match {
  case _ => A | F[Rec[F, A]]
}

If yes, then we probably can desugar recursive aliases to match-types relatively easily

IIUC then there is something in the compiler that converts these seemingly trivial match types into simple type aliases. So then you would just have to disable that conversion, which should be pretty easy.

Enabling the conversion in the other direction for recursive aliases may be a bit harder.

This program should have been rejected since Scala does not have recursive types.

1 Like

Good to know, thank you for filling the issue !

Maybe we should move away from the alias/match divide and to a non-recursive/recursive one ?

That way the compiler could also be reckless with non-recursive match types, as those are safe

It would also force us to have a really strong cycle detection, which doesn’t seem to be the case currently
Or a special modifier like rec type or nonrec type so that users manually do the cycle detection

While refactoring from my case class to this recursive alias idea, I have enountered 2 kinds of errors.
Those would alternate depending on whether and where I put type hints
They are the first two below

While minimising those, I encountered 2 more bugs

They all begin with:

type _Setr[A] = A match
  case Nothing => Nothing // used to turn of the cycle detector...
  case String => Set[_Setr[A]]

type Setr = _Setr[String]

Attempt to commit TS[…] into already committed TS[…]

append:

val op: Option[Setr] = None
op.map( b => (b, b) )

error:

java.lang.AssertionError: assertion failed: Attempt to commit TS[12556404X, 12556397X, 12556395, 12556369, 12556368, 12556367] into already committed TS[12556397X, 12556395, 12556369, 12556368, 12556367]
full trace:
	at scala.runtime.Scala3RunTime$.assertFailed(Scala3RunTime.scala:8)
	at dotty.tools.dotc.core.TyperState.commit(TyperState.scala:154)
	at dotty.tools.dotc.typer.Inferencing$.isFullyDefined(Inferencing.scala:40)
	at dotty.tools.dotc.core.TypeOps$.simplify(TypeOps.scala:145)
	at dotty.tools.dotc.core.TypeOps$SimplifyMap.apply(TypeOps.scala:189)
	at dotty.tools.dotc.core.Types$TypeMap.mapOver$$anonfun$1(Types.scala:5609)
	at dotty.tools.dotc.core.Types$LazyRef.ref(Types.scala:2887)
	at dotty.tools.dotc.core.Types$TypeMap.mapOver$$anonfun$1(Types.scala:5608)
	at dotty.tools.dotc.core.Types$LazyRef.ref(Types.scala:2887)
	at dotty.tools.dotc.core.Types$TypeMap.mapOver$$anonfun$1(Types.scala:5608)
	at dotty.tools.dotc.core.Types$LazyRef.ref(Types.scala:2887)
	at dotty.tools.dotc.core.Types$TypeAccumulator.foldOver(Types.scala:6070)
	at dotty.tools.dotc.typer.Inferencing$accu$2$.apply(Inferencing.scala:477)
	at dotty.tools.dotc.typer.Inferencing$accu$2$.apply(Inferencing.scala:469)
	at dotty.tools.dotc.core.Types$TypeAccumulator.op$proxy24$1(Types.scala:6000)
	at dotty.tools.dotc.core.Types$TypeAccumulator.foldArgs$3(Types.scala:6000)
	at dotty.tools.dotc.core.Types$TypeAccumulator.foldOver(Types.scala:6004)
	at dotty.tools.dotc.typer.Inferencing$accu$2$.apply(Inferencing.scala:477)
	at dotty.tools.dotc.typer.Inferencing$accu$2$.apply(Inferencing.scala:469)
	at dotty.tools.dotc.core.Types$TypeAccumulator.op$proxy24$1(Types.scala:6000)
	at dotty.tools.dotc.core.Types$TypeAccumulator.foldArgs$3(Types.scala:6000)
	at dotty.tools.dotc.core.Types$TypeAccumulator.foldOver(Types.scala:6004)
	at dotty.tools.dotc.typer.Inferencing$accu$2$.apply(Inferencing.scala:477)
	at dotty.tools.dotc.typer.Inferencing$.traverse$1(Inferencing.scala:493)
	at dotty.tools.dotc.typer.Inferencing$.propagate$1$$anonfun$1(Inferencing.scala:500)
	at scala.runtime.function.JProcedure2.apply(JProcedure2.java:15)
	at scala.runtime.function.JProcedure2.apply(JProcedure2.java:10)
	at dotty.tools.dotc.util.SimpleIdentityMap$Map1.foreachBinding(SimpleIdentityMap.scala:62)
	at dotty.tools.dotc.typer.Inferencing$.propagate$1(Inferencing.scala:505)
	at dotty.tools.dotc.typer.Inferencing$.dotty$tools$dotc$typer$Inferencing$$$variances(Inferencing.scala:510)
	at dotty.tools.dotc.typer.Inferencing.interpolateTypeVars(Inferencing.scala:591)
	at dotty.tools.dotc.typer.Inferencing.interpolateTypeVars$(Inferencing.scala:546)
	at dotty.tools.dotc.typer.Typer.interpolateTypeVars(Typer.scala:119)
	at dotty.tools.dotc.typer.Typer.simplify(Typer.scala:2888)
	at dotty.tools.dotc.typer.Typer.typedUnadapted(Typer.scala:2874)
	at dotty.tools.dotc.typer.Typer.typed(Typer.scala:2937)
	at dotty.tools.dotc.typer.Typer.typed(Typer.scala:2941)
	at dotty.tools.dotc.typer.Typer.typedExpr(Typer.scala:3057)
	at dotty.tools.dotc.typer.Namer.typedAheadExpr$$anonfun$1(Namer.scala:1473)
	at dotty.tools.dotc.typer.Namer.typedAhead(Namer.scala:1463)
	at dotty.tools.dotc.typer.Namer.typedAheadExpr(Namer.scala:1473)
	at dotty.tools.dotc.typer.Namer.typedAheadRhs$1$$anonfun$1(Namer.scala:1710)
	at dotty.tools.dotc.typer.PrepareInlineable$.dropInlineIfError(PrepareInlineable.scala:238)
	at dotty.tools.dotc.typer.Namer.typedAheadRhs$1(Namer.scala:1710)
	at dotty.tools.dotc.typer.Namer.rhsType$1(Namer.scala:1718)
	at dotty.tools.dotc.typer.Namer.cookedRhsType$1(Namer.scala:1736)
	at dotty.tools.dotc.typer.Namer.lhsType$1(Namer.scala:1737)
	at dotty.tools.dotc.typer.Namer.inferredResultType(Namer.scala:1748)
	at dotty.tools.dotc.typer.Namer.inferredType$1(Namer.scala:1512)
	at dotty.tools.dotc.typer.Namer.valOrDefDefSig(Namer.scala:1519)
	at dotty.tools.dotc.typer.Namer$Completer.typeSig(Namer.scala:778)
	at dotty.tools.dotc.typer.Namer$Completer.completeInCreationContext(Namer.scala:914)
	at dotty.tools.dotc.typer.Namer$Completer.complete(Namer.scala:806)
	at dotty.tools.dotc.core.SymDenotations$SymDenotation.completeFrom(SymDenotations.scala:168)
	at dotty.tools.dotc.core.Denotations$Denotation.completeInfo$1(Denotations.scala:188)
	at dotty.tools.dotc.core.Denotations$Denotation.info(Denotations.scala:190)
	at dotty.tools.dotc.core.SymDenotations$SymDenotation.ensureCompleted(SymDenotations.scala:370)
	at dotty.tools.dotc.typer.Typer.retrieveSym(Typer.scala:2751)
	at dotty.tools.dotc.typer.Typer.typedNamed$1(Typer.scala:2776)
	at dotty.tools.dotc.typer.Typer.typedUnadapted(Typer.scala:2871)
	at dotty.tools.dotc.typer.Typer.typed(Typer.scala:2937)
	at dotty.tools.dotc.typer.Typer.typed(Typer.scala:2941)
	at dotty.tools.dotc.typer.Typer.traverse$1(Typer.scala:2963)
	at dotty.tools.dotc.typer.Typer.typedStats(Typer.scala:3013)
	at dotty.tools.dotc.typer.Typer.typedBlockStats(Typer.scala:1069)
	at dotty.tools.dotc.typer.Typer.typedBlock(Typer.scala:1073)
	at dotty.tools.dotc.typer.Typer.typedUnnamed$1(Typer.scala:2817)
	at dotty.tools.dotc.typer.Typer.typedUnadapted(Typer.scala:2872)
	at dotty.tools.dotc.typer.ProtoTypes$FunProto.$anonfun$7(ProtoTypes.scala:462)
	at dotty.tools.dotc.typer.ProtoTypes$FunProto.cacheTypedArg(ProtoTypes.scala:386)
	at dotty.tools.dotc.typer.ProtoTypes$FunProto.typedArg(ProtoTypes.scala:463)
	at dotty.tools.dotc.typer.Applications$ApplyToUntyped.typedArg(Applications.scala:853)
	at dotty.tools.dotc.typer.Applications$ApplyToUntyped.typedArg(Applications.scala:853)
	at dotty.tools.dotc.typer.Applications$Application.addTyped$1(Applications.scala:544)
	at dotty.tools.dotc.typer.Applications$Application.matchArgs(Applications.scala:609)
	at dotty.tools.dotc.typer.Applications$Application.init(Applications.scala:447)
	at dotty.tools.dotc.typer.Applications$TypedApply.<init>(Applications.scala:735)
	at dotty.tools.dotc.typer.Applications$ApplyToUntyped.<init>(Applications.scala:852)
	at dotty.tools.dotc.typer.Applications.ApplyTo(Applications.scala:1052)
	at dotty.tools.dotc.typer.Applications.ApplyTo$(Applications.scala:317)
	at dotty.tools.dotc.typer.Typer.ApplyTo(Typer.scala:119)
	at dotty.tools.dotc.typer.Applications.simpleApply$1(Applications.scala:898)
	at dotty.tools.dotc.typer.Applications.realApply$1$$anonfun$3(Applications.scala:978)
	at dotty.tools.dotc.typer.Typer.tryEither(Typer.scala:3081)
	at dotty.tools.dotc.typer.Applications.realApply$1(Applications.scala:989)
	at dotty.tools.dotc.typer.Applications.typedApply(Applications.scala:1027)
	at dotty.tools.dotc.typer.Applications.typedApply$(Applications.scala:317)
	at dotty.tools.dotc.typer.Typer.typedApply(Typer.scala:119)
	at dotty.tools.dotc.typer.Typer.typedUnnamed$1(Typer.scala:2809)
	at dotty.tools.dotc.typer.Typer.typedUnadapted(Typer.scala:2872)
	at dotty.tools.dotc.typer.Typer.typed(Typer.scala:2937)
	at dotty.tools.dotc.typer.Typer.typed(Typer.scala:2941)
	at dotty.tools.dotc.typer.Typer.traverse$1(Typer.scala:2990)
	at dotty.tools.dotc.typer.Typer.typedStats(Typer.scala:3013)
	at dotty.tools.dotc.typer.Typer.typedClassDef(Typer.scala:2454)
	at dotty.tools.dotc.typer.Typer.typedTypeOrClassDef$1(Typer.scala:2797)
	at dotty.tools.dotc.typer.Typer.typedNamed$1(Typer.scala:2801)
	at dotty.tools.dotc.typer.Typer.typedUnadapted(Typer.scala:2871)
	at dotty.tools.dotc.typer.Typer.typed(Typer.scala:2937)
	at dotty.tools.dotc.typer.Typer.typed(Typer.scala:2941)
	at dotty.tools.dotc.typer.Typer.traverse$1(Typer.scala:2963)
	at dotty.tools.dotc.typer.Typer.typedStats(Typer.scala:3013)
	at dotty.tools.dotc.typer.Typer.typedPackageDef(Typer.scala:2581)
	at dotty.tools.dotc.typer.Typer.typedUnnamed$1(Typer.scala:2842)
	at dotty.tools.dotc.typer.Typer.typedUnadapted(Typer.scala:2872)
	at dotty.tools.dotc.typer.Typer.typed(Typer.scala:2937)
	at dotty.tools.dotc.typer.Typer.typed(Typer.scala:2941)
	at dotty.tools.dotc.typer.Typer.typedExpr(Typer.scala:3057)
	at dotty.tools.dotc.typer.TyperPhase.typeCheck$$anonfun$1(TyperPhase.scala:47)
	at dotty.tools.dotc.core.Phases$Phase.monitor(Phases.scala:411)
	at dotty.tools.dotc.typer.TyperPhase.typeCheck(TyperPhase.scala:54)
	at dotty.tools.dotc.typer.TyperPhase.runOn$$anonfun$3(TyperPhase.scala:88)
	at scala.runtime.function.JProcedure1.apply(JProcedure1.java:15)
	at scala.runtime.function.JProcedure1.apply(JProcedure1.java:10)
	at scala.collection.immutable.List.foreach(List.scala:333)
	at dotty.tools.dotc.typer.TyperPhase.runOn(TyperPhase.scala:88)
	at dotty.tools.dotc.Run.runPhases$1$$anonfun$1(Run.scala:259)
	at scala.runtime.function.JProcedure1.apply(JProcedure1.java:15)
	at scala.runtime.function.JProcedure1.apply(JProcedure1.java:10)
	at scala.collection.ArrayOps$.foreach$extension(ArrayOps.scala:1328)
	at dotty.tools.dotc.Run.runPhases$1(Run.scala:270)
	at dotty.tools.dotc.Run.compileUnits$$anonfun$1(Run.scala:278)
	at scala.runtime.java8.JFunction0$mcV$sp.apply(JFunction0$mcV$sp.scala:18)
	at dotty.tools.dotc.util.Stats$.maybeMonitored(Stats.scala:68)
	at dotty.tools.dotc.Run.compileUnits(Run.scala:287)
	at dotty.tools.dotc.Run.compileSources(Run.scala:220)
	at dotty.tools.dotc.Run.compile(Run.scala:204)
	at dotty.tools.dotc.Driver.doCompile(Driver.scala:39)
	at dotty.tools.xsbt.CompilerBridgeDriver.run(CompilerBridgeDriver.java:88)
	at dotty.tools.xsbt.CompilerBridge.run(CompilerBridge.java:22)
	at sbt.internal.inc.AnalyzingCompiler.compile(AnalyzingCompiler.scala:91)
	at sbt.internal.inc.MixedAnalyzingCompiler.$anonfun$compile$7(MixedAnalyzingCompiler.scala:192)
	at scala.runtime.java8.JFunction0$mcV$sp.apply(JFunction0$mcV$sp.java:23)
	at sbt.internal.inc.MixedAnalyzingCompiler.timed(MixedAnalyzingCompiler.scala:247)
	at sbt.internal.inc.MixedAnalyzingCompiler.$anonfun$compile$4(MixedAnalyzingCompiler.scala:182)
	at sbt.internal.inc.MixedAnalyzingCompiler.$anonfun$compile$4$adapted(MixedAnalyzingCompiler.scala:163)
	at sbt.internal.inc.JarUtils$.withPreviousJar(JarUtils.scala:239)
	at sbt.internal.inc.MixedAnalyzingCompiler.compileScala$1(MixedAnalyzingCompiler.scala:163)
	at sbt.internal.inc.MixedAnalyzingCompiler.compile(MixedAnalyzingCompiler.scala:210)
	at sbt.internal.inc.IncrementalCompilerImpl.$anonfun$compileInternal$1(IncrementalCompilerImpl.scala:528)
	at sbt.internal.inc.IncrementalCompilerImpl.$anonfun$compileInternal$1$adapted(IncrementalCompilerImpl.scala:528)
	at sbt.internal.inc.Incremental$.$anonfun$apply$5(Incremental.scala:177)
	at sbt.internal.inc.Incremental$.$anonfun$apply$5$adapted(Incremental.scala:175)
	at sbt.internal.inc.Incremental$$anon$2.run(Incremental.scala:461)
	at sbt.internal.inc.IncrementalCommon$CycleState.next(IncrementalCommon.scala:116)
	at sbt.internal.inc.IncrementalCommon$$anon$1.next(IncrementalCommon.scala:56)
	at sbt.internal.inc.IncrementalCommon$$anon$1.next(IncrementalCommon.scala:52)
	at sbt.internal.inc.IncrementalCommon.cycle(IncrementalCommon.scala:263)
	at sbt.internal.inc.Incremental$.$anonfun$incrementalCompile$8(Incremental.scala:416)
	at sbt.internal.inc.Incremental$.withClassfileManager(Incremental.scala:503)
	at sbt.internal.inc.Incremental$.incrementalCompile(Incremental.scala:403)
	at sbt.internal.inc.Incremental$.apply(Incremental.scala:169)
	at sbt.internal.inc.IncrementalCompilerImpl.compileInternal(IncrementalCompilerImpl.scala:528)
	at sbt.internal.inc.IncrementalCompilerImpl.$anonfun$compileIncrementally$1(IncrementalCompilerImpl.scala:482)
	at sbt.internal.inc.IncrementalCompilerImpl.handleCompilationError(IncrementalCompilerImpl.scala:332)
	at sbt.internal.inc.IncrementalCompilerImpl.compileIncrementally(IncrementalCompilerImpl.scala:420)
	at sbt.internal.inc.IncrementalCompilerImpl.compile(IncrementalCompilerImpl.scala:137)
	at sbt.Defaults$.compileIncrementalTaskImpl(Defaults.scala:2366)
	at sbt.Defaults$.$anonfun$compileIncrementalTask$2(Defaults.scala:2316)
	at sbt.internal.server.BspCompileTask$.$anonfun$compute$1(BspCompileTask.scala:30)
	at sbt.internal.io.Retry$.apply(Retry.scala:46)
	at sbt.internal.io.Retry$.apply(Retry.scala:28)
	at sbt.internal.io.Retry$.apply(Retry.scala:23)
	at sbt.internal.server.BspCompileTask$.compute(BspCompileTask.scala:30)
	at sbt.Defaults$.$anonfun$compileIncrementalTask$1(Defaults.scala:2314)
	at scala.Function1.$anonfun$compose$1(Function1.scala:49)
	at sbt.internal.util.$tilde$greater.$anonfun$$u2219$1(TypeFunctions.scala:62)
	at sbt.std.Transform$$anon$4.work(Transform.scala:68)
	at sbt.Execute.$anonfun$submit$2(Execute.scala:282)
	at sbt.internal.util.ErrorHandling$.wideConvert(ErrorHandling.scala:23)
	at sbt.Execute.work(Execute.scala:291)
	at sbt.Execute.$anonfun$submit$1(Execute.scala:282)
	at sbt.ConcurrentRestrictions$$anon$4.$anonfun$submitValid$1(ConcurrentRestrictions.scala:265)
	at sbt.CompletionService$$anon$2.call(CompletionService.scala:64)
	at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
	at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
	at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1135)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
	at java.base/java.lang.Thread.run(Thread.java:831)

scastie

Found: Setr Required: Set[…Set[Setr]…]

append:

val wrappedA: Setr = ???
val (containsA, notContainsA) = (??? : (Setr, Setr)) 
containsA - wrappedA

error:

Found:    (Playground.wrappedA : Playground.Setr)
Required: Set[Set[Set[Set[Set[Set[Set[Set[Set[Set[Playground._Setr[String]]]]]]]]]]]

scastie

Found: quoted.Type[Set[…]] Required: quoted.Type[Set[…]]

append:

val (containsA, notContainsA) = (??? : (Setr, Setr))
containsA - notContainsA

error:

Found:    quoted.Type[
  Set[Set[Set[Set[Set[Set[Set[Set[Set[Set[Set[Set[Set[Set[Set[...]]]]]]]]]]]]]]]
]
Required: quoted.Type[
  Set[Set[Set[Set[Set[Set[Set[Set[Set[Set[Set[Set[Set[Set[Set[...]]]]]]]]]]]]]]]
]

scastie

Exception occurred while executing macro expansion

append:

val wrappedA: Setr = ???
val containsA: Setr = ???
containsA - wrappedA

error:

Exception occurred while executing macro expansion.
scala.MatchError: LazyRef(AppliedType(TypeRef(ThisType(TypeRef(NoPrefix,module class immutable)),trait Set),List(LazyRef(AppliedType(TypeRef(ThisType(TypeRef(NoPrefix,module class immutable)),trait Set),List(LazyRef(AppliedType(TypeRef(ThisType(TypeRef(NoPrefix,module class immutable)),trait Set),List(LazyRef(AppliedType(TypeRef(ThisType(TypeRef(NoPrefix,module class immutable)),trait Set),List(LazyRef(AppliedType(TypeRef(ThisType(TypeRef(ThisType(TypeRef(NoPrefix,module class <empty>)),module class Playground$)),type _Setr),List(TypeRef(TermRef(ThisType(TypeRef(NoPrefix,module class scala)),object Predef),type String)))))))))))))))) (of class dotty.tools.dotc.core.Types$LazyRef)
full trace:
	at scala.quoted.runtime.impl.printers.Extractors$ExtractorsPrinter.visitType(Extractors.scala:242)
	at scala.quoted.runtime.impl.printers.Extractors$.showType(Extractors.scala:12)
	at scala.quoted.runtime.impl.QuotesImpl$$anon$18.show(QuotesImpl.scala:2988)
	at scala.quoted.runtime.impl.QuotesImpl$$anon$18.show(QuotesImpl.scala:2987)
	at scala.quoted.runtime.impl.QuotesImpl$reflect$TypeReprMethods$.show(QuotesImpl.scala:1709)
	at scala.quoted.runtime.impl.QuotesImpl$reflect$TypeReprMethods$.show(QuotesImpl.scala:1709)
	at scala.quoted.runtime.impl.printers.SourceCode$SourceCodePrinter.printType(SourceCode.scala:1238)
	at scala.quoted.runtime.impl.printers.SourceCode$SourceCodePrinter.printSeparated$9(SourceCode.scala:730)
	at scala.quoted.runtime.impl.printers.SourceCode$SourceCodePrinter.printTypesOrBounds(SourceCode.scala:736)
	at scala.quoted.runtime.impl.printers.SourceCode$SourceCodePrinter.printType$$anonfun$2(SourceCode.scala:1141)
	at scala.quoted.runtime.impl.printers.SourceCode$SourceCodePrinter.inSquare(SourceCode.scala:88)
	at scala.quoted.runtime.impl.printers.SourceCode$SourceCodePrinter.printType(SourceCode.scala:1141)
	at scala.quoted.runtime.impl.printers.SourceCode$.showType(SourceCode.scala:13)
	at scala.quoted.runtime.impl.QuotesImpl$$anon$15.show(QuotesImpl.scala:2976)
	at scala.quoted.runtime.impl.QuotesImpl$$anon$15.show(QuotesImpl.scala:2975)
	at scala.quoted.runtime.impl.QuotesImpl$reflect$TypeReprMethods$.show(QuotesImpl.scala:1709)
	at scala.quoted.runtime.impl.QuotesImpl$reflect$TypeReprMethods$.show(QuotesImpl.scala:1709)
	at com.olegych.scastie.api.runtime.Runtime$._render$$anonfun$2(Runtime.scala:12)
	at com.olegych.scastie.api.runtime.Runtime$._render$$anonfun$adapted$2(Runtime.scala:12)
	at dotty.tools.dotc.quoted.PickledQuotes$$anon$1.transform(PickledQuotes.scala:81)
	at dotty.tools.dotc.ast.Trees$Instance$TreeMap.transform(Trees.scala:1423)
	at dotty.tools.dotc.quoted.PickledQuotes$$anon$1.transform(PickledQuotes.scala:101)
	at dotty.tools.dotc.ast.Trees$Instance$TreeMap.transform$$anonfun$1(Trees.scala:1493)
	at scala.collection.immutable.List.mapConserve(List.scala:472)
	at dotty.tools.dotc.ast.Trees$Instance$TreeMap.transform(Trees.scala:1493)
	at dotty.tools.dotc.ast.Trees$Instance$TreeMap.transform(Trees.scala:1389)
	at dotty.tools.dotc.quoted.PickledQuotes$$anon$1.transform(PickledQuotes.scala:101)
	at dotty.tools.dotc.quoted.PickledQuotes$.spliceTerms(PickledQuotes.scala:116)
	at dotty.tools.dotc.quoted.PickledQuotes$.unpickleTerm(PickledQuotes.scala:60)
	at scala.quoted.runtime.impl.QuotesImpl.unpickleExpr(QuotesImpl.scala:3003)
	at com.olegych.scastie.api.runtime.Runtime$._render(Runtime.scala:12)
	at com.olegych.scastie.api.runtime.Runtime$.inline$_render(Runtime.scala:9)

scastie

“Upcast” here is just a way of saying that something with a given type is used at a wider type. The problem is that this upcast is totally unsound, but since the type in question should be illegal anyway it’s probably not a problem.

Type matches have very subtle semantics based on the disjointness of types. Matching on Nothing will probably never work as you wanted.

1 Like

I think the whole idea of using match types to simulate recursive types is very fragile. Any loophole you find is likely to be closed at some point. You need to make the recursion explicit with a recursive class hierarchy and explicit wrappings and unwrappings.

1 Like

I’m not sure I understand this, do you mean something like this: (which I ended up doing)
case class Setr(s: Set[Setr])
With apply and unapply methods as needed

yes, exactly.

1 Like