This repository has been archived by the owner on Apr 5, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 41
/
changes.rs
1723 lines (1551 loc) · 60.8 KB
/
changes.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
//! Change representation.
//!
//! This module provides data types to represent, store and record changes found in various
//! analysis passes. We distinguish between path changes and regular changes, which represent
//! changes to the export structure of the crate and to specific items, respectively. The
//! ordering of changes and output generation is performed using the span information contained
//! in these data structures. This means that we try to use the old span only when no other span
//! is available, which leads to (complete) removals being displayed first. Matters are further
//! complicated by the fact that we still group changes by the item they refer to, even if it's
//! path changes.
use rustc_hir::def_id::DefId;
use rustc_middle::ty::{error::TypeError, Predicate};
use rustc_session::Session;
use rustc_span::symbol::Symbol;
use rustc_span::{FileName, Span};
use semver::{BuildMetadata, Prerelease, Version};
use std::{
cmp::Ordering,
collections::{BTreeMap, BTreeSet, HashMap},
fmt,
};
use serde::ser::{SerializeSeq, SerializeStruct, Serializer};
use serde::Serialize;
/// The categories we use when analyzing changes between crate versions.
///
/// These directly correspond to the semantic versioning spec, with the exception that some
/// breaking changes are categorized as "technically breaking" - that is, [1] defines them as
/// non-breaking when introduced to the standard libraries, because they only cause breakage in
/// exotic and/or unlikely scenarios, while we have a separate category for them.
///
/// [1]: https://github.com/rust-lang/rfcs/blob/master/text/1105-api-evolution.md
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize)]
pub enum ChangeCategory {
/// A patch-level change - no change to the public API of a crate.
Patch,
/// A non-breaking, backwards-compatible change.
NonBreaking,
/// A breaking change that only causes breakage in well-known exotic cases.
TechnicallyBreaking,
/// A breaking, backwards-incompatible change.
Breaking,
}
pub use self::ChangeCategory::*;
impl<'a> Default for ChangeCategory {
fn default() -> Self {
Patch
}
}
impl<'a> fmt::Display for ChangeCategory {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let desc = match *self {
Patch => "patch",
NonBreaking => "non-breaking",
TechnicallyBreaking => "technically breaking",
Breaking => "breaking",
};
write!(f, "{}", desc)
}
}
pub struct RSymbol(pub Symbol);
impl Serialize for RSymbol {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&format!("{}", self.0))
}
}
struct RSpan<'a>(&'a Session, &'a Span);
impl<'a> Serialize for RSpan<'a> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let lo = self.0.source_map().lookup_char_pos(self.1.lo());
let hi = self.0.source_map().lookup_char_pos(self.1.hi());
assert!(lo.file.name == hi.file.name);
let file_name = if let FileName::Real(ref name) = lo.file.name {
name.local_path().map(|p| format!("{}", p.display()))
} else {
None
}
.unwrap_or_else(|| "no file name".to_owned());
let mut state = serializer.serialize_struct("Span", 5)?;
state.serialize_field("file", &file_name)?;
state.serialize_field("line_lo", &lo.line)?;
state.serialize_field("line_hi", &hi.line)?;
state.serialize_field("col_lo", &lo.col.0)?;
state.serialize_field("col_hi", &hi.col.0)?;
state.end()
}
}
/// Different ways to refer to a changed item.
///
/// Used in the header of a change description to identify an item that was subject to change.
pub enum Name {
/// The changed item's name.
Symbol(RSymbol),
/// A textutal description of the item, used for trait impls.
ImplDesc(String),
}
impl Name {
pub fn symbol(symbol: Symbol) -> Self {
Self::Symbol(RSymbol(symbol))
}
}
impl fmt::Display for Name {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Self::Symbol(ref name) => write!(f, "`{}`", name.0),
Self::ImplDesc(ref desc) => write!(f, "`{}`", desc),
}
}
}
impl Serialize for Name {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match *self {
Self::Symbol(ref name) => serializer.serialize_str(&format!("{}", name.0)),
Self::ImplDesc(ref desc) => serializer.serialize_str(desc),
}
}
}
/// A change record of newly introduced or removed paths to an item.
///
/// NB: `Eq` and `Ord` instances are constructed to only regard the span of the associated item
/// definition. All other spans are only present for later display of the change record.
pub struct PathChange {
/// The name of the item - this doesn't use `Name` because this change structure only gets
/// generated for removals and additions of named items, not impls.
name: RSymbol,
/// The definition span of the item.
def_span: Span,
/// The set of spans of added exports of the item.
additions: BTreeSet<Span>,
/// The set of spans of removed exports of the item.
removals: BTreeSet<Span>,
}
impl PathChange {
/// Construct a new empty path change record for an item.
fn new(name: Symbol, def_span: Span) -> Self {
Self {
name: RSymbol(name),
def_span,
additions: BTreeSet::new(),
removals: BTreeSet::new(),
}
}
/// Insert a new span addition or deletion into an existing path change record.
fn insert(&mut self, span: Span, add: bool) {
if add {
self.additions.insert(span);
} else {
self.removals.insert(span);
}
}
/// Get the change's category.
pub fn to_category(&self) -> ChangeCategory {
if !self.removals.is_empty() {
Breaking
} else if self.additions.is_empty() {
Patch
} else {
TechnicallyBreaking
}
}
/// Get the change item's definition span.
pub fn span(&self) -> &Span {
&self.def_span
}
/// Report the change in a structured manner, using rustc's error reporting capabilities.
fn report(&self, session: &Session) {
let cat = self.to_category();
if cat == Patch {
return;
}
let msg = format!("path changes to `{}`", self.name.0);
let mut builder = if cat == Breaking {
session.struct_span_err(self.def_span, &msg)
} else {
session.struct_span_warn(self.def_span, &msg)
};
for removed_span in &self.removals {
if *removed_span == self.def_span {
builder.warn("removed definition (breaking)");
} else {
builder.span_warn(*removed_span, "removed path (breaking)");
}
}
for added_span in &self.additions {
if *added_span == self.def_span {
builder.note("added definition (technically breaking)");
} else {
builder.span_note(*added_span, "added path (technically breaking)");
}
}
builder.emit();
}
}
impl PartialEq for PathChange {
fn eq(&self, other: &Self) -> bool {
self.span() == other.span()
}
}
impl Eq for PathChange {}
impl PartialOrd for PathChange {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
self.span().partial_cmp(other.span())
}
}
impl Ord for PathChange {
fn cmp(&self, other: &Self) -> Ordering {
self.span().cmp(other.span())
}
}
struct RPathChange<'a>(&'a Session, &'a PathChange);
impl<'a> Serialize for RPathChange<'a> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut state = serializer.serialize_struct("PathChange", 4)?;
state.serialize_field("name", &self.1.name)?;
state.serialize_field("def_span", &RSpan(self.0, &self.1.def_span))?;
let additions: Vec<_> = self.1.additions.iter().map(|s| RSpan(self.0, s)).collect();
state.serialize_field("additions", &additions)?;
let removals: Vec<_> = self.1.removals.iter().map(|s| RSpan(self.0, s)).collect();
state.serialize_field("removals", &removals)?;
state.end()
}
}
/// The types of changes we identify between items present in both crate versions.
#[derive(Clone, Debug)]
pub enum ChangeType<'tcx> {
/// An item has been made public.
ItemMadePublic,
/// An item has been made private.
ItemMadePrivate,
/// An item has changed it's kind.
KindDifference,
/// A `static` item changed it's mutablity.
StaticMutabilityChanged { now_mut: bool },
/// The variance of a type or region parameter has gone from invariant to co- or
/// contravariant or to bivariant.
VarianceLoosened,
/// The variance of a type or region parameter has gone from bivariant to co- or
/// contravariant or to invariant.
VarianceTightened,
/// The variance of a type or region parameter has changed from covariant to contravariant
/// or vice-versa.
VarianceChanged { now_contravariant: bool },
/// A region parameter has been added to an item.
RegionParameterAdded,
/// A region parameter has been removed from an item.
RegionParameterRemoved,
/// A possibly defaulted type parameter has been added to an item.
TypeParameterAdded { defaulted: bool },
/// A possibly defaulted type parameter has been removed from an item.
TypeParameterRemoved { defaulted: bool },
/// A variant has been added to an enum.
VariantAdded,
/// A variant has been removed from an enum.
VariantRemoved,
/// A possibly public field has been added to a variant or struct.
///
/// This also records whether all fields are public were public before the change.
VariantFieldAdded {
public: bool,
total_public: bool,
is_enum: bool,
},
/// A possibly public field has been removed from a variant or struct.
///
/// This also records whether all fields were public before the change.
VariantFieldRemoved {
public: bool,
total_public: bool,
is_enum: bool,
},
/// A variant or struct has changed it's style.
///
/// The style could have been changed from a tuple variant/struct to a regular
/// struct/struct variant or vice versa. Whether all fields were private prior to the change
/// is also recorded.
VariantStyleChanged {
now_struct: bool,
total_private: bool,
is_enum: bool,
},
/// A function has changed it's constness.
FnConstChanged { now_const: bool },
/// A method either gained or lost a `self` parameter.
MethodSelfChanged { now_self: bool },
/// A trait's definition added a possibly defaulted item.
TraitItemAdded { defaulted: bool, sealed_trait: bool },
/// A trait's definition removed a possibly defaulted item.
TraitItemRemoved { defaulted: bool },
/// A trait's definition changed it's unsafety.
TraitUnsafetyChanged { now_unsafe: bool },
/// An item's type has changed.
TypeChanged { error: TypeError<'tcx> },
/// An item's (trait) bounds have been tightened.
BoundsTightened { pred: Predicate<'tcx> },
/// An item's (trait) bounds have been loosened.
///
/// This includes information on whether the affected item is a trait definition, since
/// removing trait bounds on those is *breaking* (as it invalidates the assumption that a
/// supertrait is implemented for each type implementing the traits).
BoundsLoosened {
pred: Predicate<'tcx>,
trait_def: bool,
},
/// A trait impl has been specialized or removed for some type(s).
TraitImplTightened,
/// A trait impl has been generalized or newly added for some type(s).
TraitImplLoosened,
/// An associated item has been newly added to some inherent impls.
AssociatedItemAdded,
/// An associated item has been removed from some inherent impls.
AssociatedItemRemoved,
/// An unknown change we don't yet explicitly handle.
Unknown,
}
pub use self::ChangeType::*;
impl<'tcx> ChangeType<'tcx> {
/// Get the change type's category.
pub fn to_category(&self) -> ChangeCategory {
// TODO: slightly messy and unreadable.
match *self {
ItemMadePrivate |
KindDifference |
StaticMutabilityChanged { now_mut: false } |
VarianceTightened |
VarianceChanged { .. } |
RegionParameterAdded |
RegionParameterRemoved |
TypeParameterAdded { defaulted: false } |
TypeParameterRemoved { .. } |
VariantAdded |
VariantRemoved |
VariantFieldAdded { public: true, .. } |
VariantFieldAdded { public: false, total_public: true, .. } |
VariantFieldRemoved { public: true, .. } |
VariantFieldRemoved { public: false, is_enum: true, .. } |
VariantStyleChanged { .. } |
TypeChanged { .. } |
FnConstChanged { now_const: false } |
MethodSelfChanged { now_self: false } |
TraitItemAdded { defaulted: false, sealed_trait: false } |
TraitItemRemoved { .. } |
TraitUnsafetyChanged { .. } |
BoundsTightened { .. } |
BoundsLoosened { trait_def: true, .. } |
TraitImplTightened |
AssociatedItemRemoved |
Unknown => Breaking,
MethodSelfChanged { now_self: true } |
TraitItemAdded { .. } | // either defaulted or sealed
BoundsLoosened { trait_def: false, .. } |
TraitImplLoosened |
AssociatedItemAdded |
ItemMadePublic => TechnicallyBreaking,
StaticMutabilityChanged { now_mut: true } |
VarianceLoosened |
TypeParameterAdded { defaulted: true } |
VariantFieldAdded { public: false, .. } |
VariantFieldRemoved { public: false, .. } |
FnConstChanged { now_const: true } => NonBreaking,
}
}
/// Get a detailed explanation of a change, and why it is categorized as-is.
fn explanation(&self) -> &'static str {
match *self {
ItemMadePublic => {
"Adding an item to a module's public interface is generally a non-breaking
change, except in the special case of wildcard imports in user code, where
they can cause nameclashes. Thus, the change is classified as \"technically
breaking\"."
}
ItemMadePrivate => {
"Removing an item from a module's public interface is a breaking change."
}
KindDifference => {
"Changing the \"kind\" of an item between versions is a breaking change,
because the usage of the old and new version of the item need not be
compatible."
}
StaticMutabilityChanged { now_mut: true } => {
"Making a static item mutable is a non-breaking change, because any (old)
user code is guaranteed to use them in a read-only fashion."
}
StaticMutabilityChanged { now_mut: false } => {
"Making a static item immutable is a breaking change, because any (old)
user code that tries to mutate them will break."
}
VarianceLoosened => {
"The variance of a type or region parameter in an item loosens if an invariant
parameter becomes co-, contra- or bivariant, or a co- or contravariant parameter becomes
bivariant. See https://doc.rust-lang.org/nomicon/subtyping.html for an explanation of the
concept of variance in Rust."
}
VarianceTightened => {
"The variance of a type or region parameter in an item tightens if a variant
parameter becomes co-, contra- or invariant, or a co- or contravairant parameter becomes
invariant. See https://doc.rust-lang.org/nomicon/subtyping.html for an explanation of the
concept of variance in Rust."
}
VarianceChanged { .. } => {
"Switching the variance of a type or region parameter is breaking if it is
changed from covariant to contravariant, or vice-versa.
See https://doc.rust-lang.org/nomicon/subtyping.html for an explanation of the concept of
variance in Rust."
}
RegionParameterAdded => {
"Adding a new region parameter is a breaking change, because it can break
explicit type annotations, as well as prevent region inference working as
before."
}
RegionParameterRemoved => {
"Removing a region parameter is a breaking change, because it can break
explicit type annotations, as well as prevent region inference working as
before."
}
TypeParameterAdded { defaulted: true } => {
"Adding a new defaulted type parameter is a non-breaking change, because
all old references to the item are still valid, provided that no type
errors appear."
}
TypeParameterAdded { defaulted: false } => {
"Adding a new non-defaulted type parameter is a breaking change, because
old references to the item become invalid in cases where the type parameter
can't be inferred."
}
TypeParameterRemoved { .. } => {
"Removing any type parameter, defaulted or not, is a breaking change,
because old references to the item are become invalid if the type parameter
is instantiated in a manner not compatible with the new type of the item."
}
VariantAdded => {
"Adding a new enum variant is a breaking change, because a match expression
on said enum can become non-exhaustive."
}
VariantRemoved => {
"Removing an enum variant is a braking change, because every old reference
to the removed variant is rendered invalid."
}
VariantFieldAdded { .. } => {
"Adding a field to an enum variant or struct is breaking, as matches on the
variant or struct are invalidated. In case of structs, this only holds for
public fields, or the first private field being added."
}
VariantFieldRemoved { .. } => {
"Removing a field from an enum variant or struct is breaking, as matches on the
variant are invalidated. In case of structs, this only holds for public fields."
}
VariantStyleChanged { .. } => {
"Changing the style of a variant is a breaking change, since most old
references to it are rendered invalid: pattern matches and value
construction needs to use the other constructor syntax, respectively."
}
FnConstChanged { now_const: true } => {
"Making a function const is a non-breaking change, because a const function
can appear anywhere a regular function is expected."
}
FnConstChanged { now_const: false } => {
"Making a const function non-const is a breaking change, because values
assigned to constants can't be determined by expressions containing
non-const functions."
}
MethodSelfChanged { now_self: true } => {
"Adding a self parameter to a method is a breaking change in some specific
situations: When user code implements it's own trait on the type the
method is implemented on, the new method could cause a nameclash with a
trait method, thus breaking user code. Because this is a rather special
case, this change is classified as \"technically breaking\"."
}
MethodSelfChanged { now_self: false } => {
"Removing a self parameter from a method is a breaking change, because
all method invocations using the method syntax become invalid."
}
TraitItemAdded {
defaulted: true, ..
} => {
"Adding a new defaulted trait item is a breaking change in some specific
situations: The new trait item could cause a name clash with traits
defined in user code. Because this is a rather special case, this change
is classified as \"technically breaking\"."
}
TraitItemAdded {
sealed_trait: true, ..
} => {
"Adding a new trait item is a non-breaking change, when user code can't
provide implementations of the trait, i.e. if the trait is sealed by
inheriting from an unnamable (crate-local) item."
}
TraitItemAdded { .. } =>
// neither defaulted or sealed
{
"Adding a new non-defaulted trait item is a breaking change, because all
implementations of the trait in user code become invalid."
}
TraitItemRemoved { .. } => {
"Removing a trait item is a breaking change, because all old references
to the item become invalid."
}
TraitUnsafetyChanged { .. } => {
"Changing the unsafety of a trait is a breaking change, because all
implementations become invalid."
}
TypeChanged { .. } => {
"Changing the type of an item is a breaking change, because user code
using the item becomes type-incorrect."
}
BoundsTightened { .. } => {
"Tightening the bounds of a lifetime or type parameter is a breaking
change, because all old references instantiating the parameter with a
type or lifetime not fulfilling the bound are rendered invalid."
}
BoundsLoosened {
trait_def: true, ..
} => {
"Loosening the bounds of a lifetime or type parameter in a trait
definition is a breaking change, because the assumption in user code
that the bound in question hold is violated, potentially invalidating
trait implementation or usage."
}
BoundsLoosened {
trait_def: false, ..
} => {
"Loosening the bounds of a lifetime or type parameter in a non-trait
definition is a non-breaking change, because all old references to the
item would remain valid."
}
TraitImplTightened => {
"Effectively removing a trait implementation for a (possibly
parametrized) type is a breaking change, as all old references to trait
methods on the type become invalid."
}
TraitImplLoosened => {
"Effectively adding a trait implementation for a (possibly
parametrized) type is a breaking change in some specific situations,
as name clashes with other trait implementations in user code can be
caused."
}
AssociatedItemAdded => {
"Adding a new item to an inherent impl is a breaking change in some
specific situations, for example if this causes name clashes with a trait
method. This is rare enough to only be considered \"technically
breaking\"."
}
AssociatedItemRemoved => {
"Removing an item from an inherent impl is a breaking change, as all old
references to it become invalid."
}
Unknown => "No explanation for unknown changes.",
}
}
}
impl<'a> fmt::Display for ChangeType<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let desc = match *self {
ItemMadePublic => "item made public",
ItemMadePrivate => "item made private",
KindDifference => "item kind changed",
StaticMutabilityChanged { now_mut: true } => "static item made mutable",
StaticMutabilityChanged { now_mut: false } => "static item made immutable",
VarianceLoosened => "variance loosened",
VarianceTightened => "variance tightened",
VarianceChanged {
now_contravariant: true,
} => "variance changed from co- to contravariant",
VarianceChanged {
now_contravariant: false,
} => "variance changed from contra- to covariant",
RegionParameterAdded => "region parameter added",
RegionParameterRemoved => "region parameter removed",
TypeParameterAdded { defaulted: true } => "defaulted type parameter added",
TypeParameterAdded { defaulted: false } => "type parameter added",
TypeParameterRemoved { defaulted: true } => "defaulted type parameter removed",
TypeParameterRemoved { defaulted: false } => "type parameter removed",
VariantAdded => "enum variant added",
VariantRemoved => "enum variant removed",
VariantFieldAdded {
public: true,
total_public: true,
is_enum: true,
} => "public field added to variant with no private fields",
VariantFieldAdded {
public: true,
total_public: true,
is_enum: false,
} => "public field added to struct with no private fields",
VariantFieldAdded {
public: true,
total_public: false,
is_enum: true,
} => "public field added to variant with private fields",
VariantFieldAdded {
public: true,
total_public: false,
is_enum: false,
} => "public field added to struct with private fields",
VariantFieldAdded {
public: false,
total_public: true,
is_enum: true,
} => "private field added to variant with no private fields",
VariantFieldAdded {
public: false,
total_public: true,
is_enum: false,
} => "private field added to struct with no private fields",
VariantFieldAdded {
public: false,
total_public: false,
is_enum: true,
} => "private field added to variant with private fields",
VariantFieldAdded {
public: false,
total_public: false,
is_enum: false,
} => "private field added to struct with private fields",
VariantFieldRemoved {
public: true,
total_public: true,
is_enum: true,
} => "public field removed from variant with no private fields",
VariantFieldRemoved {
public: true,
total_public: true,
is_enum: false,
} => "public field removed from struct with no private fields",
VariantFieldRemoved {
public: true,
total_public: false,
is_enum: true,
} => "public field removed from variant with private fields",
VariantFieldRemoved {
public: true,
total_public: false,
is_enum: false,
} => "public field removed from struct with private fields",
VariantFieldRemoved {
public: false,
total_public: true,
is_enum: true,
} => "private field removed from variant with no private fields",
VariantFieldRemoved {
public: false,
total_public: true,
is_enum: false,
} => "private field removed from struct with no private fields",
VariantFieldRemoved {
public: false,
total_public: false,
is_enum: true,
} => "private field removed from variant with private fields",
VariantFieldRemoved {
public: false,
total_public: false,
is_enum: false,
} => "private field removed from struct with private fields",
VariantStyleChanged {
now_struct: true,
total_private: true,
is_enum: true,
} => "variant with no public fields changed to a struct variant",
VariantStyleChanged {
now_struct: true,
total_private: true,
is_enum: false,
} => "tuple struct with no public fields changed to a regular struct",
VariantStyleChanged {
now_struct: true,
total_private: false,
is_enum: true,
} => "variant with public fields changed to a struct variant",
VariantStyleChanged {
now_struct: true,
total_private: false,
is_enum: false,
} => "tuple struct with public fields changed to a regular struct",
VariantStyleChanged {
now_struct: false,
total_private: true,
is_enum: true,
} => "variant with no public fields changed to a tuple variant",
VariantStyleChanged {
now_struct: false,
total_private: true,
is_enum: false,
} => "struct with no public fields changed to a tuple struct",
VariantStyleChanged {
now_struct: false,
total_private: false,
is_enum: true,
} => "variant with public fields changed to a tuple variant",
VariantStyleChanged {
now_struct: false,
total_private: false,
is_enum: false,
} => "struct with public fields changed to a tuple struct",
FnConstChanged { now_const: true } => "fn item made const",
FnConstChanged { now_const: false } => "fn item made non-const",
MethodSelfChanged { now_self: true } => "added self-argument to method",
MethodSelfChanged { now_self: false } => "removed self-argument from method",
TraitItemAdded {
defaulted: true, ..
} => "added defaulted item to trait",
TraitItemAdded {
defaulted: false,
sealed_trait: true,
} => "added item to sealed trait",
TraitItemAdded { .. } => "added item to trait",
TraitItemRemoved { defaulted: true } => "removed defaulted item from trait",
TraitItemRemoved { defaulted: false } => "removed item from trait",
TraitUnsafetyChanged { now_unsafe: true } => "trait made unsafe",
TraitUnsafetyChanged { now_unsafe: false } => "trait no longer unsafe",
TypeChanged { ref error } => return write!(f, "type error: {}", error),
BoundsTightened { ref pred } => return write!(f, "added bound: `{}`", pred),
BoundsLoosened {
ref pred,
trait_def,
} => {
if trait_def {
return write!(f, "removed bound on trait definition: `{}`", pred);
} else {
return write!(f, "removed bound: `{}`", pred);
}
}
TraitImplTightened => "trait impl specialized or removed",
TraitImplLoosened => "trait impl generalized or newly added",
AssociatedItemAdded => "added item in inherent impl",
AssociatedItemRemoved => "removed item in inherent impl",
Unknown => "unknown change",
};
write!(f, "{}", desc)
}
}
impl<'tcx> Serialize for ChangeType<'tcx> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&format!("{}", self))
}
}
/// A change record of an item present in both crate versions.
///
/// NB: `Eq` and `Ord` instances are constucted to only regard the *new* span of the associated
/// item definition. This allows us to sort them by appearance in the *new* source.
pub struct Change<'tcx> {
/// The types of changes affecting the item, with optional subspans.
changes: Vec<(ChangeType<'tcx>, Option<Span>)>,
/// The most severe change category already recorded for the item.
max: ChangeCategory,
/// The name of the item.
name: Name,
/// The new definition span of the item.
new_span: Span,
/// Whether to output changes. Used to distinguish all-private items.
output: bool,
}
impl<'tcx> Change<'tcx> {
/// Construct a new empty change record for an item.
fn new(name: Name, span: Span, output: bool) -> Change<'tcx> {
Change {
changes: Vec::new(),
max: ChangeCategory::default(),
name,
new_span: span,
output,
}
}
/// Insert another change type into an existing path change record.
fn insert(&mut self, type_: ChangeType<'tcx>, span: Option<Span>) {
let cat = type_.to_category();
if cat > self.max {
self.max = cat;
}
self.changes.push((type_, span));
}
/// Check whether a trait item contains breaking changes preventing further analysis of it's
/// child items.
///
/// NB: The invariant that the item in question is actually a trait item isn't checked.
fn trait_item_breaking(&self) -> bool {
for change in &self.changes {
match change.0 {
ItemMadePrivate
| KindDifference
| RegionParameterRemoved
| TypeParameterRemoved { .. }
| VariantAdded
| VariantRemoved
| VariantFieldAdded { .. }
| VariantFieldRemoved { .. }
| VariantStyleChanged { .. }
| TypeChanged { .. }
| FnConstChanged { now_const: false }
| MethodSelfChanged { now_self: false }
| Unknown => return true,
StaticMutabilityChanged { .. }
| RegionParameterAdded
| MethodSelfChanged { now_self: true }
| TraitItemAdded { .. }
| TraitItemRemoved { .. }
| ItemMadePublic
| VarianceLoosened
| VarianceTightened
| VarianceChanged { .. }
| TypeParameterAdded { .. }
| TraitUnsafetyChanged { .. }
| FnConstChanged { now_const: true }
| BoundsTightened { .. }
| BoundsLoosened { .. }
| TraitImplTightened
| TraitImplLoosened
| AssociatedItemAdded
| AssociatedItemRemoved => (),
}
}
false
}
/// Get the change's category.
fn to_category(&self) -> ChangeCategory {
self.max
}
/// Get the new span of the change item.
fn new_span(&self) -> &Span {
&self.new_span
}
/// Report the change in a structured manner, using rustc's error reporting capabilities.
fn report(&self, session: &Session, verbose: bool) {
if self.max == Patch || !self.output {
return;
}
let msg = format!("{} changes in {}", self.max, self.name);
let mut builder = if self.max == Breaking {
session.struct_span_err(self.new_span, &msg)
} else {
session.struct_span_warn(self.new_span, &msg)
};
for change in &self.changes {
let cat = change.0.to_category();
let sub_msg = if verbose {
format!("{} ({}):\n{}", change.0, cat, change.0.explanation())
} else {
format!("{} ({})", change.0, cat)
};
if let Some(span) = change.1 {
if cat == Breaking {
builder.span_warn(span, &sub_msg);
} else {
builder.span_note(span, &sub_msg);
}
} else if cat == Breaking {
// change.1 == None from here on.
builder.warn(&sub_msg);
} else {
builder.note(&sub_msg);
}
}
builder.emit();
}
}
impl<'tcx> PartialEq for Change<'tcx> {
fn eq(&self, other: &Change) -> bool {
self.new_span() == other.new_span()
}
}
impl<'tcx> Eq for Change<'tcx> {}
impl<'tcx> PartialOrd for Change<'tcx> {
fn partial_cmp(&self, other: &Change<'tcx>) -> Option<Ordering> {
self.new_span().partial_cmp(other.new_span())
}
}
impl<'tcx> Ord for Change<'tcx> {
fn cmp(&self, other: &Change<'tcx>) -> Ordering {
self.new_span().cmp(other.new_span())
}
}
struct RChange<'a, 'tcx>(&'a Session, &'a Change<'tcx>);
impl<'a, 'tcx> Serialize for RChange<'a, 'tcx> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut state = serializer.serialize_struct("Change", 4)?;
state.serialize_field("name", &self.1.name)?;
state.serialize_field("max_category", &self.1.max)?;
state.serialize_field("new_span", &RSpan(self.0, &self.1.new_span))?;
let changes: Vec<_> = self
.1
.changes
.iter()
.map(|(t, s)| (t, s.as_ref().map(|s| RSpan(self.0, s))))
.collect();
state.serialize_field("changes", &changes)?;
state.end()
}
}
/// The total set of changes recorded for two crate versions.
#[derive(Default)]
pub struct ChangeSet<'tcx> {
/// The set of currently recorded path changes.
path_changes: HashMap<DefId, PathChange>,
/// The set of currently recorded regular changes.
changes: HashMap<DefId, Change<'tcx>>,
/// The mapping of spans to changes, for ordering purposes.
spans: BTreeMap<Span, DefId>,
/// The most severe change category already recorded.
max: ChangeCategory,
}
impl<'tcx> ChangeSet<'tcx> {
/// Add a new path change entry for the given item.
pub fn new_path_change(&mut self, old: DefId, name: Symbol, def_span: Span) {
self.spans.entry(def_span).or_insert_with(|| old);
self.path_changes
.entry(old)
.or_insert_with(|| PathChange::new(name, def_span));
}
/// Add a new path addition to an already existing entry.
pub fn add_path_addition(&mut self, old: DefId, span: Span) {
self.add_path(old, span, true);
}
/// Add a new path removal to an already existing entry.
pub fn add_path_removal(&mut self, old: DefId, span: Span) {