-
Notifications
You must be signed in to change notification settings - Fork 1k
/
RefChecks.scala
1962 lines (1776 loc) · 90.5 KB
/
RefChecks.scala
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
package dotty.tools
package dotc
package typer
import transform.*
import core.*
import Symbols.*, Types.*, Contexts.*, Flags.*, Names.*, NameOps.*, NameKinds.*
import StdNames.*, Denotations.*, Phases.*, SymDenotations.*
import NameKinds.DefaultGetterName
import util.Spans.*
import scala.collection.{mutable, immutable}
import ast.*
import MegaPhase.*
import config.Printers.{checks, noPrinter, capt}
import Decorators.*
import OverridingPairs.isOverridingPair
import typer.ErrorReporting.*
import config.Feature.{warnOnMigration, migrateTo3, sourceVersion}
import config.SourceVersion.`3.0`
import config.MigrationVersion
import config.Printers.refcheck
import reporting.*
import Constants.Constant
object RefChecks {
import tpd.*
val name: String = "refchecks"
val description: String = "checks related to abstract members and overriding"
private val defaultMethodFilter = new NameFilter {
def apply(pre: Type, name: Name)(using Context): Boolean = name.is(DefaultGetterName)
def isStable = true
}
/** Only one overloaded alternative is allowed to define default arguments */
private def checkOverloadedRestrictions(clazz: Symbol)(using Context): Unit = {
// Using the default getters (such as methodName$default$1) as a cheap way of
// finding methods with default parameters. This way, we can limit the members to
// those with the DEFAULTPARAM flag, and infer the methods. Looking for the methods
// directly requires inspecting the parameter list of every one. That modification
// shaved 95% off the time spent in this method.
for {
defaultGetterClass <- List(clazz, clazz.companionModule.moduleClass);
if defaultGetterClass.isClass
}
{
val defaultGetterNames = defaultGetterClass.asClass.memberNames(defaultMethodFilter)
val defaultMethodNames = defaultGetterNames map { _ replace {
case DefaultGetterName(methName, _) => methName
}}
for (name <- defaultMethodNames) {
val methods = clazz.thisType.member(name).alternatives.map(_.symbol)
val haveDefaults = methods.filter(_.hasDefaultParams)
if (haveDefaults.length > 1) {
val owners = haveDefaults map (_.owner)
// constructors of different classes are allowed to have defaults
if (haveDefaults.exists(x => !x.isConstructor) || owners.distinct.size < haveDefaults.size)
report.error(
em"in $clazz, multiple overloaded alternatives of ${haveDefaults.head} define default arguments${
if owners.forall(_ == clazz) then "."
else i".\nThe members with defaults are defined in ${owners.map(_.showLocated).mkString("", " and ", ".")}"}",
clazz.srcPos)
}
}
}
// Check for doomed attempt to overload applyDynamic
if (clazz derivesFrom defn.DynamicClass)
for (case (_, m1 :: m2 :: _) <- (clazz.info member nme.applyDynamic).alternatives groupBy (_.symbol.typeParams.length))
report.error("implementation restriction: applyDynamic cannot be overloaded except by methods with different numbers of type parameters, e.g. applyDynamic[T1](method: String)(arg: T1) and applyDynamic[T1, T2](method: String)(arg1: T1, arg2: T2)",
m1.symbol.srcPos)
}
/** The this-type of `cls` which should be used when looking at the types of
* inherited members. If `cls` has a non-trivial self type, this returns a skolem
* with the class type instead of the `this-type` of the class as usual.
* This is done because otherwise we want to understand inherited infos
* as they are written, whereas with the this-type they could be
* more special. A test where this makes a difference is pos/i1401.scala.
* This one used to succeed only if forwarding parameters is on.
* (Forwarding tends to hide problems by binding parameter names).
*/
private def upwardsThisType(cls: Symbol)(using Context) = cls.info match {
case ClassInfo(_, _, _, _, tp: Type) if (tp ne cls.typeRef) && !cls.isOneOf(FinalOrModuleClass) =>
SkolemType(cls.appliedRef).withName(nme.this_)
case _ =>
cls.thisType
}
/** - Check that self type of `cls` conforms to self types of all `parents` as seen from
* `cls.thisType`
* - If self type of `cls` is explicit, check that it conforms to the self types
* of all its class symbols.
* @param deep If true and a self type of a parent is not given explicitly, recurse to
* check against the parents of the parent. This is needed when capture checking,
* since we assume (& check) that the capture set of an inferred self type
* is the intersection of the capture sets of all its parents
*/
def checkSelfAgainstParents(cls: ClassSymbol, parents: List[Symbol])(using Context): Unit =
withMode(Mode.CheckBoundsOrSelfType) {
val cinfo = cls.classInfo
def checkSelfConforms(other: ClassSymbol) =
var otherSelf = other.declaredSelfTypeAsSeenFrom(cls.thisType)
if otherSelf.exists then
if !(cinfo.selfType <:< otherSelf) then
report.error(DoesNotConformToSelfType("illegal inheritance", cinfo.selfType, cls, otherSelf, "parent", other),
cls.srcPos)
for psym <- parents do
checkSelfConforms(psym.asClass)
}
end checkSelfAgainstParents
/** Check that self type of this class conforms to self types of parents
* and required classes. Also check that only `enum` constructs extend
* `java.lang.Enum` and no user-written class extends ContextFunctionN.
*/
def checkParents(cls: Symbol, parentTrees: List[Tree])(using Context): Unit = cls.info match {
case cinfo: ClassInfo =>
val psyms = cls.asClass.parentSyms
checkSelfAgainstParents(cls.asClass, psyms)
def isClassExtendingJavaEnum =
!cls.isOneOf(Enum | Trait) && psyms.contains(defn.JavaEnumClass)
// Prevent wrong `extends` of java.lang.Enum
if isClassExtendingJavaEnum then
if !migrateTo3 then // always error, only traits or enum-syntax is possible under scala 3.x
report.error(CannotExtendJavaEnum(cls), cls.sourcePos)
else
// conditionally error, we allow classes to extend java.lang.Enum in scala 2 migration mode,
// however the no-arg constructor is forbidden, we must look at the parent trees to see
// which overload is called.
val javaEnumCtor = defn.JavaEnumClass.primaryConstructor
parentTrees.exists {
case parent @ tpd.Apply(tpd.TypeApply(fn, _), _) if fn.tpe.termSymbol eq javaEnumCtor =>
// here we are simulating the error for missing arguments to a constructor.
report.error(JavaEnumParentArgs(parent.tpe), cls.sourcePos)
true
case _ =>
false
}
if psyms.exists(defn.isContextFunctionClass) then
report.error(CannotExtendContextFunction(cls), cls.sourcePos)
/** Check that arguments passed to trait parameters conform to the parameter types
* in the current class. This is necessary since parameter types might be narrowed
* through intersection with other parent traits. See neg/i11018.scala.
*/
def checkParamInits(app: Apply): Unit =
val parentCls = app.tpe.classSymbol
if parentCls.is(Trait) then
val params = parentCls.asClass.paramGetters
val args = termArgss(app).flatten
for (param, arg) <- params.lazyZip(args) do
if !param.is(Private) then // its type can be narrowed through intersection -> a check is needed
val paramType = cls.thisType.memberInfo(param)
if !(arg.tpe <:< paramType) then
val argTypes = args.tpes
// it could still be OK but we might need to substitute arguments for parameters
// to account for dependent parameters. See pos/i11993.scala
if !(arg.tpe.subst(params, argTypes) <:< paramType.subst(params, argTypes))
then
report.error(IllegalParameterInit(arg.tpe, paramType, param, cls), arg.srcPos)
for case app: Apply <- parentTrees do checkParamInits(app)
case _ =>
}
/** Disallow using trait parameters as prefix for its parents.
*
* The rationale is to ensure outer-related NPE never happen in Scala.
* Otherwise, outer NPE may happen, see tests/neg/i5083.scala
*/
private def checkParentPrefix(cls: Symbol, parent: Tree)(using Context): Unit =
parent.tpe.typeConstructor match {
case TypeRef(ref: TermRef, _) =>
val paramRefs = ref.namedPartsWith(ntp => ntp.symbol.enclosingClass == cls)
if (paramRefs.nonEmpty)
report.error(TraitParameterUsedAsParentPrefix(cls), parent.srcPos)
case _ =>
}
/** Check that a class and its companion object to not both define
* a class or module with same name
*/
private def checkCompanionNameClashes(cls: Symbol)(using Context): Unit =
if (!cls.owner.is(ModuleClass)) {
def clashes(sym: Symbol) =
sym.isClass &&
sym.name.stripModuleClassSuffix == cls.name.stripModuleClassSuffix
val others = cls.owner.linkedClass.info.decls.filter(clashes)
others.foreach { other =>
report.error(ClassAndCompanionNameClash(cls, other), cls.srcPos)
}
}
// Override checking ------------------------------------------------------------
/** A class for checking all overriding pairs of `class` with a given check function */
class OverridingPairsChecker(clazz: ClassSymbol, self: Type)(using Context) extends OverridingPairs.Cursor(clazz):
override def matches(sym1: Symbol, sym2: Symbol): Boolean =
isOverridingPair(sym1, sym2, self)
private def inLinearizationOrder(sym1: Symbol, sym2: Symbol, parent: Symbol): Boolean =
val owner1 = sym1.owner
val owner2 = sym2.owner
def precedesIn(bcs: List[ClassSymbol]): Boolean = (bcs: @unchecked) match
case bc :: bcs1 =>
if owner1 eq bc then true
else if owner2 eq bc then false
else precedesIn(bcs1)
case _ =>
false
precedesIn(parent.asClass.baseClasses)
/** We can exclude pairs safely from checking only under three additional conditions
* - their signatures also match in the parent class.
* See neg/i12828.scala for an example where this matters.
* - They overriding/overridden appear in linearization order.
* See neg/i5094.scala for an example where this matters.
* - They overriding/overridden appear in linearization order,
* or the parent is a Java class (because linearization does not apply to java classes).
* See neg/i5094.scala and pos/i18654.scala for examples where this matters.
* - The overridden symbol is not `abstract override`. For such symbols
* we need a more extensive test since the virtual super chain depends
* on the precise linearization order, which might be different for the
* subclass. See neg/i14415.scala.
*/
override def canBeHandledByParent(sym1: Symbol, sym2: Symbol, parent: Symbol): Boolean =
isOverridingPair(sym1, sym2, parent.thisType)
.showing(i"already handled ${sym1.showLocated}: ${sym1.asSeenFrom(parent.thisType).signature}, ${sym2.showLocated}: ${sym2.asSeenFrom(parent.thisType).signature} = $result", refcheck)
&& (inLinearizationOrder(sym1, sym2, parent) || parent.is(JavaDefined))
&& !sym2.is(AbsOverride)
/** Checks the subtype relationship tp1 <:< tp2.
* It is passed to the `checkOverride` operation in `checkAll`, to be used for
* compatibility checking.
*/
def checkSubType(tp1: Type, tp2: Type)(using Context): Boolean = tp1 frozen_<:< tp2
/** A hook that allows to omit override checks between `overriding` and `overridden`.
* Overridden in capture checking to handle non-capture checked classes leniently.
*/
def needsCheck(overriding: Symbol, overridden: Symbol)(using Context): Boolean = true
private val subtypeChecker: (Type, Type) => Context ?=> Boolean = this.checkSubType
def checkAll(checkOverride: ((Type, Type) => Context ?=> Boolean, Symbol, Symbol) => Unit) =
while hasNext do
if needsCheck(overriding, overridden) then
checkOverride(subtypeChecker, overriding, overridden)
next()
// The OverridingPairs cursor does assume that concrete overrides abstract
// We have to check separately for an abstract definition in a subclass that
// overrides a concrete definition in a superclass. E.g. the following (inspired
// from neg/i11130.scala) needs to be rejected as well:
//
// class A { type T = B }
// class B extends A { override type T }
for dcl <- clazz.info.decls.iterator do
if dcl.is(Deferred) then
for other <- dcl.allOverriddenSymbols do
if !other.is(Deferred) then
checkOverride(subtypeChecker, dcl, other)
end checkAll
// Disabled for capture checking since traits can get different parameter refinements
def checkInheritedTraitParameters: Boolean = true
end OverridingPairsChecker
/** 1. Check all members of class `clazz` for overriding conditions.
* That is for overriding member M and overridden member O:
*
* 1.1. M must have the same or stronger access privileges as O.
* 1.2. O must not be effectively final.
* 1.3. If M or O are extension methods, they must both be extension methods.
* (Should be before the check for the need of `override` modifier.)
* 1.4. O is deferred, or M has `override` modifier.
* 1.5. If O is stable, then so is M.
* 1.6. If O is a type alias, then M is an alias of O.
* 1.7. If O is an abstract type then
* 1.7.1 either M is an abstract type, and M's bounds are sharper than O's bounds.
* or M is a type alias or class which conforms to O's bounds.
* 1.7.2 higher-order type arguments must respect bounds on higher-order type parameters -- @M
* (explicit bounds and those implied by variance annotations) -- @see checkKindBounds
* 1.8. If O and M are values, then
* 1.8.1 M's type is a subtype of O's type, or
* 1.8.2 M is of type []S, O is of type ()T and S <: T, or
* 1.8.3 M is of type ()S, O is of type []T and S <: T, or
* 1.9. If M is erased, O is erased. If O is erased, M is erased or inline.
* 1.10. If O is inline (and deferred, otherwise O would be final), M must be inline
* 1.11. If O is a Scala-2 macro, M must be a Scala-2 macro.
* 1.12. Under -source future, if O is a val parameter, M must be a val parameter
* that passes its value on to O.
* 1.13. If O is non-experimental, M must be non-experimental.
* 1.14. If O has @publicInBinary, M must have @publicInBinary.
* 2. Check that only abstract classes have deferred members
* 3. Check that concrete classes do not have deferred definitions
* that are not implemented in a subclass.
* 4. Check that every member with an `override` modifier
* overrides some other member.
* TODO check that classes are not overridden
* TODO This still needs to be cleaned up; the current version is a straight port of what was there
* before, but it looks too complicated and method bodies are far too large.
*
* @param makeOverridingPairsChecker A function for creating a OverridePairsChecker instance
* from the class symbol and the self type
*/
def checkAllOverrides(clazz: ClassSymbol, makeOverridingPairsChecker: ((ClassSymbol, Type) => Context ?=> OverridingPairsChecker) | Null = null)(using Context): Unit = {
val self = clazz.thisType
val upwardsSelf = upwardsThisType(clazz)
var hasErrors = false
case class MixinOverrideError(member: Symbol, msg: Message)
val mixinOverrideErrors = new mutable.ListBuffer[MixinOverrideError]()
def printMixinOverrideErrors(): Unit =
mixinOverrideErrors.toList match {
case Nil =>
case List(MixinOverrideError(_, msg)) =>
report.error(msg, clazz.srcPos)
case MixinOverrideError(member, msg) :: others =>
val others1 = others.map(_.member).filter(_.name != member.name).distinct
def othersMsg = {
val others1 = others.map(_.member)
.filter(_.name != member.name)
.map(_.show).distinct
if (others1.isEmpty) ""
else i";\nother members with override errors are:: $others1%, %"
}
report.error(msg.append(othersMsg), clazz.srcPos)
}
def infoString(sym: Symbol) =
err.infoString(sym, self, showLocation = sym.owner != clazz)
def infoStringWithLocation(sym: Symbol) =
err.infoString(sym, self, showLocation = true)
def isInheritedAccessor(mbr: Symbol, other: Symbol): Boolean =
mbr.is(ParamAccessor)
&& {
val next = ParamForwarding.inheritedAccessor(mbr)
next == other || isInheritedAccessor(next, other)
}
/** Detect any param section where params in last position do not agree isRepeatedParam.
*/
def incompatibleRepeatedParam(member: Symbol, other: Symbol): Boolean =
def loop(mParamInfoss: List[List[Type]], oParamInfoss: List[List[Type]]): Boolean =
mParamInfoss match
case Nil => false
case h :: t =>
oParamInfoss match
case Nil => false
case h2 :: t2 => h.nonEmpty && h2.nonEmpty && h.last.isRepeatedParam != h2.last.isRepeatedParam
|| loop(t, t2)
member.is(Method, butNot = JavaDefined)
&& other.is(Method, butNot = JavaDefined)
&& atPhase(typerPhase):
loop(member.info.paramInfoss, other.info.paramInfoss)
/** A map of all occurrences of `into` in a member type.
* Key: number of parameter carrying `into` annotation(s)
* Value: A list of all depths of into annotations, where each
* function arrow increases the depth.
* Example:
* def foo(x: into A, y: => [X] => into (x: X) => into B): C
* produces the map
* (0 -> List(0), 1 -> List(1, 2))
*/
type IntoOccurrenceMap = immutable.Map[Int, List[Int]]
def intoOccurrences(tp: Type): IntoOccurrenceMap =
def traverseInfo(depth: Int, tp: Type): List[Int] = tp match
case AnnotatedType(tp, annot) if annot.symbol == defn.IntoParamAnnot =>
depth :: traverseInfo(depth, tp)
case AppliedType(tycon, arg :: Nil) if tycon.typeSymbol == defn.RepeatedParamClass =>
traverseInfo(depth, arg)
case defn.FunctionOf(_, resType, _) =>
traverseInfo(depth + 1, resType)
case RefinedType(parent, rname, mt: MethodOrPoly) =>
traverseInfo(depth, mt)
case tp: MethodOrPoly =>
traverseInfo(depth + 1, tp.resType)
case tp: ExprType =>
traverseInfo(depth, tp.resType)
case _ =>
Nil
def traverseParams(n: Int, formals: List[Type], acc: IntoOccurrenceMap): IntoOccurrenceMap =
if formals.isEmpty then acc
else
val occs = traverseInfo(0, formals.head)
traverseParams(n + 1, formals.tail, if occs.isEmpty then acc else acc + (n -> occs))
def traverse(n: Int, tp: Type, acc: IntoOccurrenceMap): IntoOccurrenceMap = tp match
case tp: PolyType =>
traverse(n, tp.resType, acc)
case tp: MethodType =>
traverse(n + tp.paramInfos.length, tp.resType, traverseParams(n, tp.paramInfos, acc))
case _ =>
acc
traverse(0, tp, immutable.Map.empty)
end intoOccurrences
val checker =
if makeOverridingPairsChecker == null then OverridingPairsChecker(clazz, self)
else makeOverridingPairsChecker(clazz, self)
/* Check that all conditions for overriding `other` by `member`
* of class `clazz` are met.
*/
def checkOverride(checkSubType: (Type, Type) => Context ?=> Boolean, member: Symbol, other: Symbol): Unit =
def memberTp(self: Type) =
if (member.isClass) TypeAlias(member.typeRef.etaExpand)
else self.memberInfo(member)
def otherTp(self: Type) =
self.memberInfo(other)
refcheck.println(i"check override ${infoString(member)} overriding ${infoString(other)}")
def noErrorType = !memberTp(self).isErroneous && !otherTp(self).isErroneous
def overrideErrorMsg(core: Context ?=> String, compareTypes: Boolean = false): Message =
val (mtp, otp) = if compareTypes then (memberTp(self), otherTp(self)) else (NoType, NoType)
OverrideError(core, self, member, other, mtp, otp)
def compatTypes(memberTp: Type, otherTp: Type): Boolean =
try
isOverridingPair(member, memberTp, other, otherTp,
fallBack = warnOnMigration(
overrideErrorMsg("no longer has compatible type"),
(if (member.owner == clazz) member else clazz).srcPos, version = `3.0`),
isSubType = checkSubType)
catch case ex: MissingType =>
// can happen when called with upwardsSelf as qualifier of memberTp and otherTp,
// because in that case we might access types that are not members of the qualifier.
false
/** Do types of term members `member` and `other` as seen from `self` match?
* If not we treat them as not a real override and don't issue override
* error messages. Also, bridges are not generated in this case.
* Type members are always assumed to match.
*/
def trueMatch: Boolean =
member.isType || withMode(Mode.IgnoreCaptures) {
// `matches` does not perform box adaptation so the result here would be
// spurious during capture checking.
//
// Instead of parameterizing `matches` with the function for subtype checking
// with box adaptation, we simply ignore capture annotations here.
// This should be safe since the compatibility under box adaptation is already
// checked.
memberTp(self).matches(otherTp(self))
}
def emitOverrideError(fullmsg: Message) =
if (!(hasErrors && member.is(Synthetic) && member.is(Module))) {
// suppress errors relating toi synthetic companion objects if other override
// errors (e.g. relating to the companion class) have already been reported.
if (member.owner == clazz) report.error(fullmsg, member.srcPos)
else mixinOverrideErrors += new MixinOverrideError(member, fullmsg)
hasErrors = true
}
def overrideError(msg: String, compareTypes: Boolean = false) =
if trueMatch && noErrorType then
emitOverrideError(overrideErrorMsg(msg, compareTypes))
def overrideDeprecation(what: String, member: Symbol, other: Symbol, fix: String): Unit =
report.deprecationWarning(
em"overriding $what${infoStringWithLocation(other)} is deprecated;\n ${infoString(member)} should be $fix.",
if member.owner == clazz then member.srcPos else clazz.srcPos)
def autoOverride(sym: Symbol) =
sym.is(Synthetic) && (
desugar.isDesugaredCaseClassMethodName(member.name) || // such names are added automatically, can't have an override preset.
sym.is(Module)) // synthetic companion
def overrideAccessError() = {
report.log(i"member: ${member.showLocated} ${member.flagsString}") // DEBUG
report.log(i"other: ${other.showLocated} ${other.flagsString}") // DEBUG
val otherAccess = (other.flags & AccessFlags).flagsString
overrideError("has weaker access privileges; it should be " +
(if (otherAccess == "") "public" else "at least " + otherAccess))
}
def overrideTargetNameError() =
val otherTargetName = i"@targetName(${other.targetName})"
if member.hasTargetName(member.name) then
overrideError(i"misses a target name annotation $otherTargetName")
else if other.hasTargetName(other.name) then
overrideError(i"should not have a @targetName annotation since the overridden member hasn't one either")
else
overrideError(i"has a different target name annotation; it should be $otherTargetName")
//Console.println(infoString(member) + " overrides " + infoString(other) + " in " + clazz);//DEBUG
/* Is the intersection between given two lists of overridden symbols empty? */
def intersectionIsEmpty(syms1: Iterator[Symbol], syms2: Iterator[Symbol]) =
!syms1.exists(syms2.toSet.contains)
// o: public | protected | package-protected (aka java's default access)
// ^-may be overridden by member with access privileges-v
// m: public | public/protected | public/protected/package-protected-in-same-package-as-o
if (member.is(Private)) // (1.1)
overrideError("has weaker access privileges; it should not be private")
// todo: align accessibility implication checking with isAccessible in Contexts
def isOverrideAccessOK =
val memberIsPublic = (member.flags & AccessFlags).isEmpty && !member.privateWithin.exists
def protectedOK = !other.is(Protected) || member.is(Protected) // if o is protected, so is m
def accessBoundaryOK =
val ob = other.accessBoundary(member.owner)
val mb = member.accessBoundary(member.owner)
// restriction isLocalToBlock because companionModule fails under -from-tasty (#14508)
def companionBoundaryOK = ob.isClass && !ob.isLocalToBlock && mb.is(Module) && (ob.companionModule eq mb.companionModule)
ob.isContainedIn(mb) || companionBoundaryOK // m relaxes o's access boundary,
def otherIsJavaProtected = other.isAllOf(JavaProtected) // or o is Java defined and protected (see #3946)
memberIsPublic || protectedOK && (accessBoundaryOK || otherIsJavaProtected)
end isOverrideAccessOK
if !member.hasTargetName(other.targetName) then
overrideTargetNameError()
else if !isOverrideAccessOK then
overrideAccessError()
else if (other.isClass)
// direct overrides were already checked on completion (see Checking.chckWellFormed)
// the test here catches indirect overriddes between two inherited base types.
overrideError("cannot be used here - class definitions cannot be overridden")
else if (other.isOpaqueAlias)
// direct overrides were already checked on completion (see Checking.chckWellFormed)
// the test here catches indirect overriddes between two inherited base types.
overrideError("cannot be used here - opaque type aliases cannot be overridden")
else if (!other.is(Deferred) && member.isClass)
overrideError("cannot be used here - classes can only override abstract types")
else if other.isEffectivelyFinal then // (1.2)
overrideError(i"cannot override final member ${other.showLocated}")
else if (member.is(ExtensionMethod) && !other.is(ExtensionMethod)) // (1.3)
overrideError("is an extension method, cannot override a normal method")
else if (other.is(ExtensionMethod) && !member.is(ExtensionMethod)) // (1.3)
overrideError("is a normal method, cannot override an extension method")
else if !other.is(Deferred)
&& !member.is(Deferred)
&& !other.name.is(DefaultGetterName)
&& !member.isAnyOverride
then
// Exclusion for default getters, fixes SI-5178. We cannot assign the Override flag to
// the default getter: one default getter might sometimes override, sometimes not. Example in comment on ticket.
// Also exclusion for implicit shortcut methods
// Also excluded under Scala2 mode are overrides of default methods of Java traits.
if (autoOverride(member) ||
other.owner.isAllOf(JavaInterface) &&
warnOnMigration(
em"`override` modifier required when a Java 8 default method is re-implemented",
member.srcPos, version = `3.0`))
member.setFlag(Override)
else if (member.isType && self.memberInfo(member) =:= self.memberInfo(other))
() // OK, don't complain about type aliases which are equal
else if member.owner != clazz
&& other.owner != clazz
&& !other.owner.derivesFrom(member.owner)
then
overrideError(
s"$clazz inherits conflicting members:\n "
+ infoStringWithLocation(other) + " and\n " + infoStringWithLocation(member)
+ "\n(Note: this can be resolved by declaring an override in " + clazz + ".)")
else if member.is(Exported) then
overrideError("cannot override since it comes from an export")
else if incompatibleRepeatedParam(member, other) then
report.error(DoubleDefinition(member, other, clazz), member.srcPos)
else
overrideError("needs `override` modifier")
else if (other.is(AbsOverride) && other.isIncompleteIn(clazz) && !member.is(AbsOverride))
overrideError("needs `abstract override` modifiers")
else if member.is(Override) && other.is(Mutable) then
overrideError("cannot override a mutable variable")
else if (member.isAnyOverride &&
!(member.owner.thisType.baseClasses exists (_ isSubClass other.owner)) &&
!member.is(Deferred) && !other.is(Deferred) &&
intersectionIsEmpty(member.extendedOverriddenSymbols, other.extendedOverriddenSymbols))
overrideError("cannot override a concrete member without a third member that's overridden by both " +
"(this rule is designed to prevent ``accidental overrides'')")
else if (other.isStableMember && !member.isStableMember) // (1.5)
overrideError("needs to be a stable, immutable value")
else if (member.is(ModuleVal) && !other.isRealMethod && !other.isOneOf(DeferredOrLazy))
overrideError("may not override a concrete non-lazy value")
else if (member.is(Lazy, butNot = Module) && !other.isRealMethod && !other.is(Lazy) &&
!warnOnMigration(overrideErrorMsg("may not override a non-lazy value"), member.srcPos, version = `3.0`))
overrideError("may not override a non-lazy value")
else if (other.is(Lazy) && !other.isRealMethod && !member.is(Lazy))
overrideError("must be declared lazy to override a lazy value")
else if (member.is(Erased) && !other.is(Erased)) // (1.9)
overrideError("is erased, cannot override non-erased member")
else if (other.is(Erased) && !member.isOneOf(Erased | Inline)) // (1.9)
overrideError("is not erased, cannot override erased member")
else if other.is(Inline) && !member.is(Inline) then // (1.10)
overrideError("is not inline, cannot implement an inline method")
else if (other.isScala2Macro && !member.isScala2Macro) // (1.11)
overrideError("cannot be used here - only Scala-2 macros can override Scala-2 macros")
else if (!compatTypes(memberTp(self), otherTp(self)) &&
!compatTypes(memberTp(upwardsSelf), otherTp(upwardsSelf)))
overrideError("has incompatible type", compareTypes = true)
else if (member.targetName != other.targetName)
if (other.targetName != other.name)
overrideError(i"needs to be declared with @targetName(${"\""}${other.targetName}${"\""}) so that external names match")
else
overrideError("cannot have a @targetName annotation since external names would be different")
else if intoOccurrences(memberTp(self)) != intoOccurrences(otherTp(self)) then
overrideError("has different occurrences of `into` modifiers", compareTypes = true)
else if other.is(ParamAccessor) && !isInheritedAccessor(member, other) then // (1.12)
report.errorOrMigrationWarning(
em"cannot override val parameter ${other.showLocated}",
member.srcPos,
MigrationVersion.OverrideValParameter)
else if !other.isExperimental && member.hasAnnotation(defn.ExperimentalAnnot) then // (1.13)
overrideError("may not override non-experimental member")
else if !member.hasAnnotation(defn.PublicInBinaryAnnot) && other.hasAnnotation(defn.PublicInBinaryAnnot) then // (1.14)
overrideError("also needs to be declared with @publicInBinary")
else if other.hasAnnotation(defn.DeprecatedOverridingAnnot) then
overrideDeprecation("", member, other, "removed or renamed")
end checkOverride
checker.checkAll(checkOverride)
printMixinOverrideErrors()
// Verifying a concrete class has nothing unimplemented.
if (!clazz.isOneOf(AbstractOrTrait)) {
val abstractErrors = new mutable.ListBuffer[String]
def abstractErrorMessage =
// a little formatting polish
if (abstractErrors.size <= 2) abstractErrors.mkString(" ")
else abstractErrors.tail.mkString(abstractErrors.head + ":\n", "\n", "")
def abstractClassError(mustBeMixin: Boolean, msg: String): Unit = {
def prelude = (
if (clazz.isAnonymousClass || clazz.is(Module)) "object creation impossible"
else if (mustBeMixin) s"$clazz needs to be a mixin"
else if clazz.is(Synthetic) then "instance cannot be created"
else s"$clazz needs to be abstract"
) + ", since"
if (abstractErrors.isEmpty) abstractErrors ++= List(prelude, msg)
else abstractErrors += msg
}
def hasJavaErasedOverriding(sym: Symbol): Boolean =
!erasurePhase.exists || // can't do the test, assume the best
atPhase(erasurePhase.next) {
clazz.info.nonPrivateMember(sym.name).hasAltWith { alt =>
alt.symbol.is(JavaDefined, butNot = Deferred) &&
!sym.owner.derivesFrom(alt.symbol.owner) &&
alt.matches(sym)
}
}
def ignoreDeferred(mbr: Symbol) =
mbr.isType
|| mbr.isSuperAccessor // not yet synthesized
|| mbr.is(JavaDefined) && hasJavaErasedOverriding(mbr)
def isImplemented(mbr: Symbol) =
val mbrDenot = mbr.asSeenFrom(clazz.thisType)
def isConcrete(sym: Symbol) = sym.exists && !sym.isOneOf(NotConcrete)
clazz.nonPrivateMembersNamed(mbr.name)
.filterWithPredicate(
impl => isConcrete(impl.symbol)
&& withMode(Mode.IgnoreCaptures)(mbrDenot.matchesLoosely(impl, alwaysCompareTypes = true)))
.exists
/** The term symbols in this class and its baseclasses that are
* abstract in this class. We can't use memberNames for that since
* a concrete member might have the same signature as an abstract
* member in a base class, yet might not override it.
*/
def missingTermSymbols: List[Symbol] =
val buf = new mutable.ListBuffer[Symbol]
for bc <- clazz.baseClasses; sym <- bc.info.decls.toList do
if sym.is(DeferredTerm) && !isImplemented(sym) && !ignoreDeferred(sym) then
buf += sym
buf.toList
// 2. Check that only abstract classes have deferred members
def checkNoAbstractMembers(): Unit = {
// Avoid spurious duplicates: first gather any missing members.
val missing = missingTermSymbols
// Group missing members by the name of the underlying symbol,
// to consolidate getters and setters.
val grouped = missing.groupBy(_.underlyingSymbol.name)
val missingMethods = grouped.toList flatMap {
case (name, syms) =>
syms.filterConserve(!_.isSetter)
.distinctBy(_.signature) // Avoid duplication for similar definitions (#19731)
}
def stubImplementations: List[String] = {
// Grouping missing methods by the declaring class
val regrouped = missingMethods.groupBy(_.owner).toList
def membersStrings(members: List[Symbol]) =
members.sortBy(_.name.toString).map(_.asSeenFrom(clazz.thisType).showDcl + " = ???")
if (regrouped.tail.isEmpty)
membersStrings(regrouped.head._2)
else (regrouped.sortBy(_._1.name.toString()) flatMap {
case (owner, members) =>
("// Members declared in " + owner.fullName) +: membersStrings(members) :+ ""
}).init
}
// If there are numerous missing methods, we presume they are aware of it and
// give them a nicely formatted set of method signatures for implementing.
if (missingMethods.size > 1) {
abstractClassError(false, "it has " + missingMethods.size + " unimplemented members.")
val preface =
"""|/** As seen from %s, the missing signatures are as follows.
| * For convenience, these are usable as stub implementations.
| */
|""".stripMargin.format(clazz)
abstractErrors += stubImplementations.map(" " + _ + "\n").mkString(preface, "", "")
return
}
for (member <- missingMethods) {
def showDclAndLocation(sym: Symbol) =
s"${sym.showDcl} in ${sym.owner.showLocated}"
def undefined(msg: String) =
abstractClassError(false, s"${showDclAndLocation(member)} is not defined $msg")
val underlying = member.underlyingSymbol
// Give a specific error message for abstract vars based on why it fails:
// It could be unimplemented, have only one accessor, or be uninitialized.
if (underlying.is(Mutable)) {
val isMultiple = grouped.getOrElse(underlying.name, Nil).size > 1
// If both getter and setter are missing, squelch the setter error.
if (member.isSetter && isMultiple) ()
else undefined(
if (member.isSetter) "\n(Note that an abstract var requires a setter in addition to the getter)"
else if (member.isGetter && !isMultiple) "\n(Note that an abstract var requires a getter in addition to the setter)"
else err.abstractVarMessage(member))
}
else if (underlying.is(Method)) {
// If there is a concrete method whose name matches the unimplemented
// abstract method, and a cursory examination of the difference reveals
// something obvious to us, let's make it more obvious to them.
val abstractParams = underlying.info.firstParamTypes
val matchingName = clazz.info.nonPrivateMember(underlying.name).alternatives
val matchingArity = matchingName filter { m =>
!m.symbol.is(Deferred) &&
m.info.firstParamTypes.length == abstractParams.length
}
matchingArity match {
// So far so good: only one candidate method
case concrete :: Nil =>
val mismatches =
abstractParams.zip(concrete.info.firstParamTypes)
.filterNot { case (x, y) => x =:= y }
mismatches match {
// Only one mismatched parameter: say something useful.
case (pa, pc) :: Nil =>
val abstractSym = pa.typeSymbol
val concreteSym = pc.typeSymbol
def subclassMsg(c1: Symbol, c2: Symbol) =
s"${c1.showLocated} is a subclass of ${c2.showLocated}, but method parameter types must match exactly."
val addendum =
if (abstractSym == concreteSym)
(pa.typeConstructor, pc.typeConstructor) match {
case (TypeRef(pre1, _), TypeRef(pre2, _)) =>
if (pre1 =:= pre2) "their type parameters differ"
else "their prefixes (i.e. enclosing instances) differ"
case _ =>
""
}
else if (abstractSym isSubClass concreteSym)
subclassMsg(abstractSym, concreteSym)
else if (concreteSym isSubClass abstractSym)
subclassMsg(concreteSym, abstractSym)
else ""
undefined(s"""
|(Note that
| parameter ${pa.show} in ${showDclAndLocation(underlying)} does not match
| parameter ${pc.show} in ${showDclAndLocation(concrete.symbol)}
| $addendum)""".stripMargin)
case xs =>
undefined(
if concrete.symbol.is(AbsOverride) then
s"\n(The class implements ${showDclAndLocation(concrete.symbol)} but that definition still needs an implementation)"
else
s"\n(The class implements a member with a different type: ${showDclAndLocation(concrete.symbol)})")
}
case Nil =>
undefined("")
case concretes =>
undefined(s"\n(The class implements members with different types: ${concretes.map(c => showDclAndLocation(c.symbol))}%\n %)")
}
}
else undefined("")
}
}
// 3. Check that concrete classes do not have deferred definitions
// that are not implemented in a subclass.
// Note that this is not the same as (2); In a situation like
//
// class C { def m: Int = 0}
// class D extends C { def m: Int }
//
// (3) is violated but not (2).
def checkNoAbstractDecls(bc: Symbol): Unit = {
for (decl <- bc.info.decls)
if (decl.is(Deferred)) {
val impl = withMode(Mode.IgnoreCaptures)(decl.matchingMember(clazz.thisType))
if (impl == NoSymbol || decl.owner.isSubClass(impl.owner))
&& !ignoreDeferred(decl)
then
val impl1 = clazz.thisType.nonPrivateMember(decl.name) // DEBUG
report.log(i"${impl1}: ${impl1.info}") // DEBUG
report.log(i"${clazz.thisType.memberInfo(decl)}") // DEBUG
abstractClassError(false, "there is a deferred declaration of " + infoString(decl) +
" which is not implemented in a subclass" + err.abstractVarMessage(decl))
}
if (bc.asClass.superClass.is(Abstract))
checkNoAbstractDecls(bc.asClass.superClass)
}
// Check that every term member of this concrete class has a symbol that matches the member's type
// Member types are computed by intersecting the types of all members that have the same name
// and signature. But a member selection will pick one particular implementation, according to
// the rules of overriding and linearization. This method checks that the implementation has indeed
// a type that subsumes the full member type.
def checkMemberTypesOK() = {
// First compute all member names we need to check in `membersToCheck`.
// We do not check
// - types
// - synthetic members or bridges
// - members in other concrete classes, since these have been checked before
// (this is done for efficiency)
// - members in a prefix of inherited parents that all come from Java or Scala2
// (this is done to avoid false positives since Scala2's rules for checking are different)
val membersToCheck = new util.HashSet[Name](4096)
val seenClasses = new util.HashSet[Symbol](256)
def addDecls(cls: Symbol): Unit =
if (!seenClasses.contains(cls)) {
seenClasses += cls
for (mbr <- cls.info.decls)
if (mbr.isTerm && !mbr.isOneOf(Synthetic | Bridge) && mbr.memberCanMatchInheritedSymbols &&
!membersToCheck.contains(mbr.name))
membersToCheck += mbr.name
cls.info.parents.map(_.classSymbol)
.filter(_.isOneOf(AbstractOrTrait))
.dropWhile(_.isOneOf(JavaDefined | Scala2x))
.foreach(addDecls)
}
addDecls(clazz)
// For each member, check that the type of its symbol, as seen from `self`
// can override the info of this member
withMode(Mode.IgnoreCaptures) {
for (name <- membersToCheck)
for (mbrd <- self.member(name).alternatives) {
val mbr = mbrd.symbol
val mbrType = mbr.info.asSeenFrom(self, mbr.owner)
if (!mbrType.overrides(mbrd.info, relaxedCheck = false, matchLoosely = true))
report.errorOrMigrationWarning(
em"""${mbr.showLocated} is not a legal implementation of `$name` in $clazz
| its type $mbrType
| does not conform to ${mbrd.info}""",
(if (mbr.owner == clazz) mbr else clazz).srcPos, MigrationVersion.Scala2to3)
}
}
}
/** Check that inheriting a case class does not constitute a variant refinement
* of a base type of the case class. It is because of this restriction that we
* can assume invariant refinement for case classes in `constrainPatternType`.
*/
def checkCaseClassInheritanceInvariant() =
for
caseCls <- clazz.info.baseClasses.tail.find(_.is(Case))
baseCls <- caseCls.info.baseClasses.tail
if baseCls.typeParams.exists(_.paramVarianceSign != 0)
problem <- variantInheritanceProblems(baseCls, caseCls, i"base $baseCls", "case ")
withExplain = problem.appendExplanation:
"""Refining a basetype of a case class is not allowed.
|This is a limitation that enables better GADT constraints in case class patterns""".stripMargin
do report.errorOrMigrationWarning(withExplain, clazz.srcPos, MigrationVersion.Scala2to3)
checkNoAbstractMembers()
if (abstractErrors.isEmpty)
checkNoAbstractDecls(clazz)
if (abstractErrors.nonEmpty)
report.error(abstractErrorMessage, clazz.srcPos)
checkMemberTypesOK()
checkCaseClassInheritanceInvariant()
}
if (!clazz.is(Trait) && checker.checkInheritedTraitParameters) {
// check that parameterized base classes and traits are typed in the same way as from the superclass
// I.e. say we have
//
// Sub extends Super extends* Base
//
// where `Base` has value parameters. Enforce that
//
// Sub.thisType.baseType(Base) =:= Sub.thisType.baseType(Super).baseType(Base)
//
// This is necessary because parameter values are determined directly or indirectly
// by `Super`. So we cannot pretend they have a different type when seen from `Sub`.
def checkParameterizedTraitsOK() = {
val mixins = clazz.mixins
for {
cls <- clazz.info.baseClasses.tail
if cls.paramAccessors.nonEmpty && !mixins.contains(cls)
problem <- variantInheritanceProblems(cls, clazz.asClass.superClass, i"parameterized base $cls", "super")
}
report.error(problem, clazz.srcPos)
}
checkParameterizedTraitsOK()
}
/** Check that `site` does not inherit conflicting generic instances of `baseCls`,
* when doing a direct base type or going via intermediate class `middle`. I.e, we require:
*
* site.baseType(baseCls) =:= site.baseType(middle).baseType(baseCls)
*
* Return an optional by name error message if this test fails.
*/
def variantInheritanceProblems(
baseCls: Symbol, middle: Symbol, baseStr: String, middleStr: String): Option[Message] = {
val superBT = self.baseType(middle)
val thisBT = self.baseType(baseCls)
val combinedBT = superBT.baseType(baseCls)
if (combinedBT =:= thisBT) None // ok
else
Some(
em"""illegal inheritance: $clazz inherits conflicting instances of $baseStr.
|
| Direct basetype: $thisBT
| Basetype via $middleStr$middle: $combinedBT""")
}
/* Returns whether there is a symbol declared in class `inclazz`
* (which must be different from `clazz`) whose name and type
* seen as a member of `class.thisType` matches `member`'s.
*/
def hasMatchingSym(inclazz: Symbol, member: Symbol): Boolean = {
def isSignatureMatch(sym: Symbol) = sym.isType || {
val self = clazz.thisType
sym.asSeenFrom(self).matches(member.asSeenFrom(self))
&& !incompatibleRepeatedParam(sym, member)
}
/* The rules for accessing members which have an access boundary are more
* restrictive in java than scala. Since java has no concept of package nesting,
* a member with "default" (package-level) access can only be accessed by members
* in the exact same package. Example:
*
* package a.b;
* public class JavaClass { void foo() { } }
*
* The member foo() can be accessed only from members of package a.b, and not
* nested packages like a.b.c. In the analogous scala class:
*
* package a.b
* class ScalaClass { private[b] def foo() = () }
*
* The member IS accessible to classes in package a.b.c. The javaAccessCheck logic
* is restricting the set of matching signatures according to the above semantics.
*/
def javaAccessCheck(sym: Symbol) = (
!inclazz.is(JavaDefined) // not a java defined member
|| !sym.privateWithin.exists // no access boundary
|| sym.is(Protected) // marked protected in java, thus accessible to subclasses
|| sym.privateWithin == member.enclosingPackageClass // exact package match
)
def classDecls = inclazz.info.nonPrivateDecl(member.name)
(inclazz != clazz) &&
classDecls.hasAltWith(d => isSignatureMatch(d.symbol) && javaAccessCheck(d.symbol))
}