@@ -3,7 +3,7 @@ package dotty.tools.tasty.besteffort
3
3
import java .util .UUID
4
4
5
5
import 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 }
7
7
8
8
/**
9
9
* The Best Effort Tasty Header consists of six fields:
@@ -33,7 +33,7 @@ sealed abstract case class BestEffortTastyHeader(
33
33
)
34
34
35
35
class BestEffortTastyHeaderUnpickler (config : UnpicklerConfig , reader : TastyReader ) {
36
- import TastyHeaderUnpickler ._
36
+ import BestEffortTastyHeaderUnpickler ._
37
37
import reader ._
38
38
39
39
def this (reader : TastyReader ) = this (UnpicklerConfig .generic, reader)
@@ -75,3 +75,101 @@ class BestEffortTastyHeaderUnpickler(config: UnpicklerConfig, reader: TastyReade
75
75
if (! cond) throw new UnpickleException (msg)
76
76
}
77
77
}
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