@@ -3,7 +3,7 @@ package dotty.tools.tasty.besteffort
33import java .util .UUID
44
55import BestEffortTastyFormat .{MajorVersion , MinorVersion , ExperimentalVersion , bestEffortHeader , header }
6- import dotty .tools .tasty .{UnpicklerConfig , TastyHeaderUnpickler , TastyReader , UnpickleException , TastyFormat }
6+ import dotty .tools .tasty .{UnpicklerConfig , TastyHeaderUnpickler , TastyReader , UnpickleException , TastyFormat , TastyVersion }
77
88/**
99 * The Best Effort Tasty Header consists of six fields:
@@ -33,7 +33,7 @@ sealed abstract case class BestEffortTastyHeader(
3333)
3434
3535class BestEffortTastyHeaderUnpickler (config : UnpicklerConfig , reader : TastyReader ) {
36- import TastyHeaderUnpickler ._
36+ import BestEffortTastyHeaderUnpickler ._
3737 import reader ._
3838
3939 def this (reader : TastyReader ) = this (UnpicklerConfig .generic, reader)
@@ -75,3 +75,101 @@ class BestEffortTastyHeaderUnpickler(config: UnpicklerConfig, reader: TastyReade
7575 if (! cond) throw new UnpickleException (msg)
7676 }
7777}
78+
79+ // Copy pasted from dotty.tools.tasty.TastyHeaderUnpickler
80+ // Since that library has strong compatibility guarantees, we do not want
81+ // to add any more methods just to support an experimental feature
82+ // (like best-effort compilation options).
83+ object BestEffortTastyHeaderUnpickler {
84+
85+ private def check (cond : Boolean , msg : => String ): Unit = {
86+ if (! cond) throw new UnpickleException (msg)
87+ }
88+
89+ private def checkValidVersion (fileMajor : Int , fileMinor : Int , fileExperimental : Int , toolingVersion : String , config : UnpicklerConfig ) = {
90+ val toolMajor : Int = config.majorVersion
91+ val toolMinor : Int = config.minorVersion
92+ val toolExperimental : Int = config.experimentalVersion
93+ val validVersion = TastyFormat .isVersionCompatible(
94+ fileMajor = fileMajor,
95+ fileMinor = fileMinor,
96+ fileExperimental = fileExperimental,
97+ compilerMajor = toolMajor,
98+ compilerMinor = toolMinor,
99+ compilerExperimental = toolExperimental
100+ )
101+ check(validVersion, {
102+ // failure means that the TASTy file cannot be read, therefore it is either:
103+ // - backwards incompatible major, in which case the library should be recompiled by the minimum stable minor
104+ // version supported by this compiler
105+ // - any experimental in an older minor, in which case the library should be recompiled by the stable
106+ // compiler in the same minor.
107+ // - older experimental in the same minor, in which case the compiler is also experimental, and the library
108+ // should be recompiled by the current compiler
109+ // - forward incompatible, in which case the compiler must be upgraded to the same version as the file.
110+ val fileVersion = TastyVersion (fileMajor, fileMinor, fileExperimental)
111+ val toolVersion = TastyVersion (toolMajor, toolMinor, toolExperimental)
112+
113+ val compat = Compatibility .failReason(file = fileVersion, read = toolVersion)
114+
115+ val what = if (compat < 0 ) " Backward" else " Forward"
116+ val signature = signatureString(fileVersion, toolVersion, what, tool = Some (toolingVersion))
117+ val fix = (
118+ if (compat < 0 ) {
119+ val newCompiler =
120+ if (compat == Compatibility .BackwardIncompatibleMajor ) toolVersion.minStable
121+ else if (compat == Compatibility .BackwardIncompatibleExperimental ) fileVersion.nextStable
122+ else toolVersion // recompile the experimental library with the current experimental compiler
123+ recompileFix(newCompiler, config)
124+ }
125+ else upgradeFix(fileVersion, config)
126+ )
127+ signature + fix + tastyAddendum
128+ })
129+ }
130+
131+ private def signatureString (
132+ fileVersion : TastyVersion , toolVersion : TastyVersion , what : String , tool : Option [String ]) = {
133+ val optProducedBy = tool.fold(" " )(t => s " , produced by $t" )
134+ s """ $what incompatible TASTy file has version ${fileVersion.show}$optProducedBy,
135+ | expected ${toolVersion.validRange}.
136+ | """ .stripMargin
137+ }
138+
139+ private def recompileFix (producerVersion : TastyVersion , config : UnpicklerConfig ) = {
140+ val addendum = config.recompileAdditionalInfo
141+ val newTool = config.upgradedProducerTool(producerVersion)
142+ s """ The source of this file should be recompiled by $newTool. $addendum""" .stripMargin
143+ }
144+
145+ private def upgradeFix (fileVersion : TastyVersion , config : UnpicklerConfig ) = {
146+ val addendum = config.upgradeAdditionalInfo(fileVersion)
147+ val newTool = config.upgradedReaderTool(fileVersion)
148+ s """ To read this ${fileVersion.kind} file, use $newTool. $addendum""" .stripMargin
149+ }
150+
151+ private def tastyAddendum : String = """
152+ | Please refer to the documentation for information on TASTy versioning:
153+ | https://docs.scala-lang.org/scala3/reference/language-versions/binary-compatibility.html""" .stripMargin
154+
155+ private object Compatibility {
156+ final val BackwardIncompatibleMajor = - 3
157+ final val BackwardIncompatibleExperimental = - 2
158+ final val ExperimentalRecompile = - 1
159+ final val ExperimentalUpgrade = 1
160+ final val ForwardIncompatible = 2
161+
162+ /** Given that file can't be read, extract the reason */
163+ def failReason (file : TastyVersion , read : TastyVersion ): Int =
164+ if (file.major == read.major && file.minor == read.minor && file.isExperimental && read.isExperimental) {
165+ if (file.experimental < read.experimental) ExperimentalRecompile // recompile library as compiler is too new
166+ else ExperimentalUpgrade // they should upgrade compiler as library is too new
167+ }
168+ else if (file.major < read.major)
169+ BackwardIncompatibleMajor // pre 3.0.0
170+ else if (file.isExperimental && file.major == read.major && file.minor <= read.minor)
171+ // e.g. 3.4.0 reading 3.4.0-RC1-NIGHTLY, or 3.3.0 reading 3.0.2-RC1-NIGHTLY
172+ BackwardIncompatibleExperimental
173+ else ForwardIncompatible
174+ }
175+ }
0 commit comments