@@ -3,6 +3,7 @@ package dotty.tools.tasty
3
3
import java .util .UUID
4
4
5
5
import TastyFormat .{MajorVersion , MinorVersion , ExperimentalVersion , header }
6
+ import TastyHeaderUnpickler .TastyVersion
6
7
7
8
/**
8
9
* The Tasty Header consists of four fields:
@@ -27,12 +28,99 @@ sealed abstract case class TastyHeader(
27
28
toolingVersion : String
28
29
)
29
30
30
- class TastyHeaderUnpickler (reader : TastyReader ) {
31
+ trait UnpicklerConfig {
32
+ /** The TASTy major version that this reader supports */
33
+ def majorVersion : Int
34
+ /** The TASTy minor version that this reader supports */
35
+ def minorVersion : Int
36
+ /** The TASTy experimental version that this reader supports */
37
+ def experimentalVersion : Int
38
+ /** The description of the upgraded tool that can read the given TASTy version */
39
+ def upgradedReaderTool (version : TastyVersion ): String
40
+ /** The description of the upgraded tool that can produce the given TASTy version */
41
+ def upgradedProducerTool (version : TastyVersion ): String
42
+ /** Additional information to help a user fix the outdated TASTy problem */
43
+ def recompileAdditionalInfo : String
44
+ /** Additional information to help a user fix the more recent TASTy problem */
45
+ def upgradeAdditionalInfo (fileVersion : TastyVersion ): String
46
+ }
47
+
48
+ object UnpicklerConfig {
49
+
50
+ /** A config where its major, minor and experimental versions are fixed to those in TastyFormat */
51
+ trait DefaultTastyVersion extends UnpicklerConfig {
52
+ override final def majorVersion : Int = MajorVersion
53
+ override final def minorVersion : Int = MinorVersion
54
+ override final def experimentalVersion : Int = ExperimentalVersion
55
+ }
56
+
57
+ trait Scala3Compiler extends UnpicklerConfig {
58
+ private def asScala3Compiler (version : TastyVersion ): String =
59
+ if (version.major == 28 ) {
60
+ // scala 3.x.y series
61
+ if (version.experimental > 0 )
62
+ // scenario here is someone using 3.4.0 to read 3.4.1-RC1-NIGHTLY, in this case, we should show 3.4 nightly.
63
+ s " the same nightly or snapshot Scala 3. ${version.minor - 1 } compiler "
64
+ else s " a Scala 3. ${version.minor}.0 compiler or newer "
65
+ }
66
+ else if (version.experimental > 0 ) " the same Scala compiler" // unknown major version, just say same
67
+ else " a more recent Scala compiler" // unknown major version, just say later
68
+
69
+ /** The description of the upgraded scala compiler that can read the given TASTy version */
70
+ final def upgradedReaderTool (version : TastyVersion ): String = asScala3Compiler(version)
71
+
72
+ /** The description of the upgraded scala compiler that can produce the given TASTy version */
73
+ final def upgradedProducerTool (version : TastyVersion ): String = asScala3Compiler(version)
74
+
75
+ final def recompileAdditionalInfo : String = """
76
+ | Usually this means that the library dependency containing this file should be updated.""" .stripMargin
77
+
78
+ final def upgradeAdditionalInfo (fileVersion : TastyVersion ): String =
79
+ if (fileVersion.isExperimental && experimentalVersion == 0 ) {
80
+ """
81
+ | Note that you are using a stable compiler, which can not read experimental TASTy.""" .stripMargin
82
+ }
83
+ else " "
84
+ }
85
+
86
+ trait Generic extends UnpicklerConfig {
87
+ final def upgradedProducerTool (version : TastyVersion ): String =
88
+ " a later version"
89
+
90
+ final def upgradedReaderTool (version : TastyVersion ): String =
91
+ if (version.isExperimental) s " the version of this tool compatible with TASTy ${version.show}"
92
+ else s " a newer version of this tool compatible with TASTy ${version.show}"
93
+
94
+ final def recompileAdditionalInfo : String = """
95
+ | Usually this means that the classpath entry of this file should be updated.""" .stripMargin
96
+
97
+ final def upgradeAdditionalInfo (fileVersion : TastyVersion ): String =
98
+ if (fileVersion.isExperimental && experimentalVersion == 0 ) {
99
+ """
100
+ | Note that this tool does not support reading experimental TASTy.""" .stripMargin
101
+ }
102
+ else " "
103
+ }
104
+
105
+ /** A config for the TASTy reader of a scala 3 compiler */
106
+ val scala3Compiler = new UnpicklerConfig with Scala3Compiler with DefaultTastyVersion {}
107
+
108
+ /** A config for the TASTy reader of a generic tool */
109
+ val generic = new UnpicklerConfig with Generic with DefaultTastyVersion {}
110
+ }
111
+
112
+ class TastyHeaderUnpickler (config : UnpicklerConfig , reader : TastyReader ) {
31
113
import TastyHeaderUnpickler ._
32
114
import reader ._
33
115
116
+ def this (config : UnpicklerConfig , bytes : Array [Byte ]) = this (config, new TastyReader (bytes))
117
+ def this (reader : TastyReader ) = this (UnpicklerConfig .generic, reader)
34
118
def this (bytes : Array [Byte ]) = this (new TastyReader (bytes))
35
119
120
+ private val toolMajor : Int = config.majorVersion
121
+ private val toolMinor : Int = config.minorVersion
122
+ private val toolExperimental : Int = config.experimentalVersion
123
+
36
124
/** reads and verifies the TASTy version, extracting the UUID */
37
125
def readHeader (): UUID =
38
126
readFullHeader().uuid
@@ -45,8 +133,11 @@ class TastyHeaderUnpickler(reader: TastyReader) {
45
133
val fileMajor = readNat()
46
134
if (fileMajor <= 27 ) { // old behavior before `tasty-core` 3.0.0-M4
47
135
val fileMinor = readNat()
48
- val signature = signatureString(fileMajor, fileMinor, 0 )
49
- throw new UnpickleException (signature + backIncompatAddendum + toolingAddendum)
136
+ val fileVersion = TastyVersion (fileMajor, fileMinor, 0 )
137
+ val toolVersion = TastyVersion (toolMajor, toolMinor, toolExperimental)
138
+ val signature = signatureString(fileVersion, toolVersion, what = " backward" , tool = None )
139
+ val fix = recompileFix(toolVersion.minStable)
140
+ throw new UnpickleException (signature + fix)
50
141
}
51
142
else {
52
143
val fileMinor = readNat()
@@ -63,20 +154,38 @@ class TastyHeaderUnpickler(reader: TastyReader) {
63
154
fileMajor = fileMajor,
64
155
fileMinor = fileMinor,
65
156
fileExperimental = fileExperimental,
66
- compilerMajor = MajorVersion ,
67
- compilerMinor = MinorVersion ,
68
- compilerExperimental = ExperimentalVersion
157
+ compilerMajor = toolMajor ,
158
+ compilerMinor = toolMinor ,
159
+ compilerExperimental = toolExperimental
69
160
)
70
161
71
162
check(validVersion, {
72
- val signature = signatureString(fileMajor, fileMinor, fileExperimental)
73
- val producedByAddendum = s " \n The TASTy file was produced by $toolingVersion. $toolingAddendum"
74
- val msg = (
75
- if (fileExperimental != 0 ) unstableAddendum
76
- else if (fileMajor < MajorVersion ) backIncompatAddendum
77
- else forwardIncompatAddendum
163
+ // failure means that the TASTy file is can not be read, therefore it is either:
164
+ // - backwards incompatible major, in which case the library should be recompiled by the minimum stable minor
165
+ // version supported by this compiler
166
+ // - any experimental in an older minor, in which case the library should be recompiled by the stable
167
+ // compiler in the same minor.
168
+ // - older experimental in the same minor, in which case the compiler is also experimental, and the library
169
+ // should be recompiled by the current compiler
170
+ // - forward incompatible, in which case the compiler must be upgraded to the same version as the file.
171
+ val fileVersion = TastyVersion (fileMajor, fileMinor, fileExperimental)
172
+ val toolVersion = TastyVersion (toolMajor, toolMinor, toolExperimental)
173
+
174
+ val compat = Compatibility .failReason(file = fileVersion, read = toolVersion)
175
+
176
+ val what = if (compat < 0 ) " backward" else " forward"
177
+ val signature = signatureString(fileVersion, toolVersion, what, tool = Some (toolingVersion))
178
+ val fix = (
179
+ if (compat < 0 ) {
180
+ val newCompiler =
181
+ if (compat == Compatibility .BackwardIncompatibleMajor ) toolVersion.minStable
182
+ else if (compat == Compatibility .BackwardIncompatibleExperimental ) fileVersion.nextStable
183
+ else toolVersion // recompile the experimental library with the current experimental compiler
184
+ recompileFix(newCompiler)
185
+ }
186
+ else upgradeFix(fileVersion)
78
187
)
79
- signature + msg + producedByAddendum
188
+ signature + fix
80
189
})
81
190
82
191
val uuid = new UUID (readUncompressedLong(), readUncompressedLong())
@@ -89,40 +198,71 @@ class TastyHeaderUnpickler(reader: TastyReader) {
89
198
private def check (cond : Boolean , msg : => String ): Unit = {
90
199
if (! cond) throw new UnpickleException (msg)
91
200
}
201
+
202
+ private def signatureString (
203
+ fileVersion : TastyVersion , toolVersion : TastyVersion , what : String , tool : Option [String ]) = {
204
+ val optProducedBy = tool.fold(" " )(t => s " produced by $t" )
205
+ s """ TASTy file $optProducedBy has a $what incompatible TASTy version ${fileVersion.show},
206
+ | expected ${toolVersion.validRange}.
207
+ | """ .stripMargin
208
+ }
209
+
210
+ private def recompileFix (producerVersion : TastyVersion ) = {
211
+ val addendum = config.recompileAdditionalInfo
212
+ val newTool = config.upgradedProducerTool(producerVersion)
213
+ s """ The source of this file should be recompiled by $newTool. $addendum""" .stripMargin
214
+ }
215
+
216
+ private def upgradeFix (fileVersion : TastyVersion ) = {
217
+ val addendum = config.upgradeAdditionalInfo(fileVersion)
218
+ val newTool = config.upgradedReaderTool(fileVersion)
219
+ s """ To read this ${fileVersion.kind} file, use $newTool. $addendum""" .stripMargin
220
+ }
92
221
}
93
222
94
223
object TastyHeaderUnpickler {
95
224
96
- private def toolingAddendum = (
97
- if ( ExperimentalVersion > 0 )
98
- " \n Note that your tooling is currently using an unstable TASTy version. "
99
- else
100
- " "
101
- )
102
-
103
- private def signatureString ( fileMajor : Int , fileMinor : Int , fileExperimental : Int ) = {
104
- def showMinorVersion ( min : Int , exp : Int ) = {
105
- val expStr = if (exp == 0 ) " " else s " [unstable release: $exp ] "
106
- s " $min$expStr "
107
- }
108
- val minorVersion = showMinorVersion( MinorVersion , ExperimentalVersion )
109
- val fileMinorVersion = showMinorVersion(fileMinor, fileExperimental )
110
- s """ TASTy signature has wrong version.
111
- | expected: {majorVersion: $MajorVersion , minorVersion: $minorVersion }
112
- | found : {majorVersion: $fileMajor , minorVersion: $fileMinorVersion }
113
- |
114
- | """ .stripMargin
225
+ private object Compatibility {
226
+ final val BackwardIncompatibleMajor = - 3
227
+ final val BackwardIncompatibleExperimental = - 2
228
+ final val ExperimentalRecompile = - 1
229
+ final val ExperimentalUpgrade = 1
230
+ final val ForwardIncompatible = 2
231
+
232
+ /** Given that file can't be read, extract the reason */
233
+ def failReason ( file : TastyVersion , read : TastyVersion ) : Int =
234
+ if (file.major == read.major && file.minor == read.minor && file.isExperimental && read.isExperimental) {
235
+ if (file.experimental < read.experimental) ExperimentalRecompile // recompile library as compiler is too new
236
+ else ExperimentalUpgrade // they should upgrade compiler as library is too new
237
+ }
238
+ else if (file.major < read.major )
239
+ BackwardIncompatibleMajor // pre 3.0.0
240
+ else if (file.isExperimental && file.major == read.major && file.minor <= read.minor)
241
+ // e.g. 3.4.0 reading 3.4.0-RC1-NIGHTLY, or 3.3.0 reading 3.0.2-RC1-NIGHTLY
242
+ BackwardIncompatibleExperimental
243
+ else ForwardIncompatible
115
244
}
116
245
117
- private def unstableAddendum =
118
- """ This TASTy file was produced by an unstable release.
119
- |To read this TASTy file, your tooling must be at the same version.""" .stripMargin
246
+ case class TastyVersion (major : Int , minor : Int , experimental : Int ) {
247
+ def isExperimental : Boolean = experimental > 0
248
+
249
+ def nextStable : TastyVersion = copy(experimental = 0 )
120
250
121
- private def backIncompatAddendum =
122
- """ This TASTy file was produced by an earlier release that is not supported anymore.
123
- |Please recompile this TASTy with a later version.""" .stripMargin
251
+ def minStable : TastyVersion = copy(minor = 0 , experimental = 0 )
124
252
125
- private def forwardIncompatAddendum =
126
- """ This TASTy file was produced by a more recent, forwards incompatible release.
127
- |To read this TASTy file, please upgrade your tooling.""" .stripMargin
253
+ def show : String = {
254
+ val suffix = if (isExperimental) s " -experimental- $experimental" else " "
255
+ s " $major. $minor$suffix"
256
+ }
257
+
258
+ def kind : String =
259
+ if (isExperimental) " experimental TASTy" else " TASTy"
260
+
261
+ def validRange : String = {
262
+ val min = TastyVersion (major, 0 , 0 )
263
+ val max = if (experimental == 0 ) this else TastyVersion (major, minor - 1 , 0 )
264
+ val extra = Option .when(experimental > 0 )(this )
265
+ s " stable TASTy from ${min.show} to ${max.show}${extra.fold(" " )(e => s " , or exactly ${e.show}" )}"
266
+ }
267
+ }
128
268
}
0 commit comments