-
Notifications
You must be signed in to change notification settings - Fork 1.1k
/
Copy pathChecking.scala
1533 lines (1417 loc) · 67.1 KB
/
Checking.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 core._
import ast._
import Contexts._
import Types._
import Flags._
import Names._
import StdNames._
import Symbols._
import Trees._
import ProtoTypes._
import Scopes._
import CheckRealizable._
import ErrorReporting.errorTree
import util.Spans.Span
import Phases.refchecksPhase
import Constants.Constant
import util.SrcPos
import util.Spans.Span
import rewrites.Rewrites.patch
import inlines.Inlines
import transform.SymUtils._
import transform.ValueClasses._
import Decorators._
import ErrorReporting.{err, errorType}
import config.Printers.{typr, patmatch}
import NameKinds.DefaultGetterName
import NameOps._
import SymDenotations.{NoCompleter, NoDenotation}
import Applications.unapplyArgs
import Inferencing.isFullyDefined
import transform.patmat.SpaceEngine.isIrrefutable
import config.Feature
import config.Feature.sourceVersion
import config.SourceVersion._
import printing.Formatting.hlAsKeyword
import transform.TypeUtils.*
import collection.mutable
import reporting._
object Checking {
import tpd._
/** Add further information for error messages involving applied types if the
* type is inferred:
* 1. the full inferred type is a TypeTree node
* 2. the applied type causing the error, if different from (1)
*/
private def showInferred(msg: Message, app: Type, tpt: Tree)(using Context): Message =
if tpt.isInstanceOf[TypeTree] then
def subPart = if app eq tpt.tpe then "" else i" subpart $app of"
msg.append(i" in$subPart inferred type ${tpt}")
.appendExplanation("\n\nTo fix the problem, provide an explicit type.")
else msg
/** A general checkBounds method that can be used for TypeApply nodes as
* well as for AppliedTypeTree nodes. Also checks that type arguments to
* *-type parameters are fully applied.
* @param tpt If bounds are checked for an AppliedType, the type tree representing
* or (in case it is inferred) containing the type.
* See TypeOps.boundsViolations for an explanation of the first four parameters.
*/
def checkBounds(args: List[tpd.Tree], boundss: List[TypeBounds],
instantiate: (Type, List[Type]) => Type, app: Type = NoType, tpt: Tree = EmptyTree)(using Context): Unit =
args.lazyZip(boundss).foreach { (arg, bound) =>
if !bound.isLambdaSub && !arg.tpe.hasSimpleKind then
errorTree(arg,
showInferred(MissingTypeParameterInTypeApp(arg.tpe), app, tpt))
}
for (arg, which, bound) <- TypeOps.boundsViolations(args, boundss, instantiate, app) do
report.error(
showInferred(DoesNotConformToBound(arg.tpe, which, bound), app, tpt),
arg.srcPos.focus)
/** Check that type arguments `args` conform to corresponding bounds in `tl`
* Note: This does not check the bounds of AppliedTypeTrees. These
* are handled by method checkAppliedType below.
*/
def checkBounds(args: List[tpd.Tree], tl: TypeLambda)(using Context): Unit =
checkBounds(args, tl.paramInfos, _.substParams(tl, _))
def checkGoodBounds(sym: Symbol)(using Context): Boolean =
val bad = findBadBounds(sym.typeRef)
if bad.exists then
report.error(em"$sym has possibly conflicting bounds $bad", sym.srcPos)
!bad.exists
/** If `tp` dealiases to a typebounds L..H where not L <:< H
* return the potentially conflicting bounds, otherwise return NoType.
*/
private def findBadBounds(tp: Type)(using Context): Type = tp.dealias match
case tp: TypeRef => findBadBounds(tp.info)
case tp @ TypeBounds(lo, hi) if !(lo <:< hi) => tp
case _ => NoType
/** Check applied type trees for well-formedness. This means
* - all arguments are within their corresponding bounds
* - if type is a higher-kinded application with wildcard arguments,
* check that it or one of its supertypes can be reduced to a normal application.
* Unreducible applications correspond to general existentials, and we
* cannot handle those.
* @param tree The applied type tree to check
* @param tpt If `tree` is synthesized from a type in a TypeTree,
* the original TypeTree, or EmptyTree otherwise.
*/
def checkAppliedType(tree: AppliedTypeTree, tpt: Tree = EmptyTree)(using Context): Unit = {
val AppliedTypeTree(tycon, args) = tree
// If `args` is a list of named arguments, return corresponding type parameters,
// otherwise return type parameters unchanged
val tparams = tycon.tpe.typeParams
val bounds = tparams.map(_.paramInfoAsSeenFrom(tree.tpe).bounds)
def instantiate(bound: Type, args: List[Type]) =
tparams match
case LambdaParam(lam, _) :: _ =>
HKTypeLambda.fromParams(tparams, bound).appliedTo(args)
case _ =>
bound // paramInfoAsSeenFrom already took care of instantiation in this case
if !ctx.mode.is(Mode.Pattern) // no bounds checking in patterns
&& tycon.symbol != defn.TypeBoxClass // TypeBox types are generated for capture
// conversion, may contain AnyKind as arguments
then
checkBounds(args, bounds, instantiate, tree.tpe, tpt)
def checkWildcardApply(tp: Type): Unit = tp match {
case tp @ AppliedType(tycon, _) =>
if tp.isUnreducibleWild then
report.errorOrMigrationWarning(
showInferred(UnreducibleApplication(tycon), tp, tpt),
tree.srcPos, from = `3.0`)
case _ =>
}
def checkValidIfApply(using Context): Unit =
checkWildcardApply(tycon.tpe.appliedTo(args.map(_.tpe)))
withMode(Mode.AllowLambdaWildcardApply)(checkValidIfApply)
}
/** Check all applied type trees in inferred type `tpt` for well-formedness */
def checkAppliedTypesIn(tpt: TypeTree)(using Context): Unit =
val checker = new TypeTraverser:
def traverse(tp: Type) =
tp match
case AppliedType(tycon, argTypes) =>
checkAppliedType(
untpd.AppliedTypeTree(TypeTree(tycon), argTypes.map(TypeTree(_)))
.withType(tp).withSpan(tpt.span.toSynthetic),
tpt)
case _ =>
traverseChildren(tp)
checker.traverse(tpt.tpe)
def checkNoWildcard(tree: Tree)(using Context): Tree = tree.tpe match {
case tpe: TypeBounds => errorTree(tree, "no wildcard type allowed here")
case _ => tree
}
/** Check that kind of `arg` has the same outline as the kind of paramBounds.
* E.g. if `paramBounds` has kind * -> *, `arg` must have that kind as well,
* and analogously for all other kinds. This kind checking does not take into account
* variances or bounds. The more detailed kind checking is done as part of checkBounds in PostTyper.
* The purpose of preCheckKind is to do a rough test earlier in Typer,
* in order to prevent scenarios that lead to self application of
* types. Self application needs to be avoided since it can lead to stack overflows.
* Test cases are neg/i2771.scala and neg/i2771b.scala.
* A NoType paramBounds is used as a sign that checking should be suppressed.
*/
def preCheckKind(arg: Tree, paramBounds: Type)(using Context): Tree =
if (arg.tpe.widen.isRef(defn.NothingClass) ||
!paramBounds.exists ||
arg.tpe.hasSameKindAs(paramBounds.bounds.hi)) arg
else errorTree(arg, em"Type argument ${arg.tpe} does not have the same kind as its bound $paramBounds")
def preCheckKinds(args: List[Tree], paramBoundss: List[Type])(using Context): List[Tree] = {
val args1 = args.zipWithConserve(paramBoundss)(preCheckKind)
args1 ++ args.drop(paramBoundss.length)
// add any arguments that do not correspond to a parameter back,
// so the wrong number of parameters is reported afterwards.
}
/** Check that `tp` refers to a nonAbstract class
* and that the instance conforms to the self type of the created class.
*/
def checkInstantiable(tp: Type, pos: SrcPos)(using Context): Unit =
tp.underlyingClassRef(refinementOK = false) match
case tref: TypeRef =>
val cls = tref.symbol
if (cls.isOneOf(AbstractOrTrait))
report.error(CantInstantiateAbstractClassOrTrait(cls, isTrait = cls.is(Trait)), pos)
if !cls.is(Module) then
// Create a synthetic singleton type instance, and check whether
// it conforms to the self type of the class as seen from that instance.
val stp = SkolemType(tp)
val selfType = cls.declaredSelfTypeAsSeenFrom(stp)
if selfType.exists && !(stp <:< selfType) then
report.error(DoesNotConformToSelfTypeCantBeInstantiated(tp, selfType), pos)
case _ =>
/** Check that type `tp` is realizable. */
def checkRealizable(tp: Type, pos: SrcPos, what: String = "path")(using Context): Unit = {
val rstatus = realizability(tp)
if (rstatus ne Realizable)
report.errorOrMigrationWarning(
em"$tp is not a legal $what\nsince it${rstatus.msg}", pos, from = `3.0`)
}
/** Given a parent `parent` of a class `cls`, if `parent` is a trait check that
* the superclass of `cls` derived from the superclass of `parent`.
*
* An exception is made if `cls` extends `Any`, and `parent` is `java.io.Serializable`
* or `java.lang.Comparable`. These two classes are treated by Scala as universal
* traits. E.g. the following is OK:
*
* ... extends Any with java.io.Serializable
*
* The standard library relies on this idiom.
*/
def checkTraitInheritance(parent: Symbol, cls: ClassSymbol, pos: SrcPos)(using Context): Unit =
parent match {
case parent: ClassSymbol if parent.is(Trait) =>
val psuper = parent.superClass
val csuper = cls.superClass
val ok = csuper.derivesFrom(psuper) ||
parent.is(JavaDefined) && csuper == defn.AnyClass &&
(parent == defn.JavaSerializableClass || parent == defn.ComparableClass)
if (!ok)
report.error(em"illegal trait inheritance: super$csuper does not derive from $parent's super$psuper", pos)
case _ =>
}
/** A type map which checks that the only cycles in a type are F-bounds
* and that protects all F-bounded references by LazyRefs.
*/
class CheckNonCyclicMap(sym: Symbol, reportErrors: Boolean)(using Context) extends TypeMap {
/** Set of type references whose info is currently checked */
private val locked = mutable.Set[TypeRef]()
/** Are cycles allowed within nested refinedInfos of currently checked type? */
private var nestedCycleOK = false
/** Are cycles allowed within currently checked type? */
private var cycleOK = false
/** A diagnostic output string that indicates the position of the last
* part of a type bounds checked by checkInfo. Possible choices:
* alias, lower bound, upper bound.
*/
var where: String = ""
/** The last type top-level type checked when a CyclicReference occurs. */
var lastChecked: Type = NoType
private def checkPart(tp: Type, w: String) =
try apply(tp)
finally {
where = w
lastChecked = tp
}
private def checkUpper(tp: Type, w: String) = {
val saved = nestedCycleOK
nestedCycleOK = true
try checkPart(tp, w)
finally nestedCycleOK = saved
}
/** Check info `tp` for cycles. Throw CyclicReference for illegal cycles,
* break direct cycle with a LazyRef for legal, F-bounded cycles.
*/
def checkInfo(tp: Type): Type = tp match {
case tp @ TypeAlias(alias) =>
tp.derivedAlias(checkPart(alias, "alias"))
case tp @ MatchAlias(alias) =>
tp.derivedAlias(checkUpper(alias, "match"))
case tp @ TypeBounds(lo, hi) =>
tp.derivedTypeBounds(checkPart(lo, "lower bound"), checkUpper(hi, "upper bound"))
case _ =>
tp
}
private def apply(tp: Type, cycleOK: Boolean, nestedCycleOK: Boolean): Type = {
val savedCycleOK = this.cycleOK
val savedNestedCycleOK = this.nestedCycleOK
this.cycleOK = cycleOK
this.nestedCycleOK = nestedCycleOK
try apply(tp)
finally {
this.cycleOK = savedCycleOK
this.nestedCycleOK = savedNestedCycleOK
}
}
def apply(tp: Type): Type = tp match {
case tp: TermRef =>
this(tp.info)
mapOver(tp)
case tp @ AppliedType(tycon, args) =>
tp.derivedAppliedType(this(tycon), args.mapConserve(this(_, nestedCycleOK, nestedCycleOK)))
case tp @ RefinedType(parent, name, rinfo) =>
tp.derivedRefinedType(this(parent), name, this(rinfo, nestedCycleOK, nestedCycleOK))
case tp: RecType =>
tp.rebind(this(tp.parent))
case tp @ TypeRef(pre, _) =>
try {
// A prefix is interesting if it might contain (transitively) a reference
// to symbol `sym` itself. We only check references with interesting
// prefixes for cycles. This pruning is done in order not to force
// global symbols when doing the cyclicity check.
def isInteresting(prefix: Type): Boolean = prefix.stripTypeVar match {
case NoPrefix => true
case prefix: ThisType =>
sym.owner.isClass && (
prefix.cls.isContainedIn(sym.owner) // sym reachable through outer references
|| sym.owner.isContainedIn(prefix.cls) // sym reachable through member references
)
case prefix: NamedType =>
(!sym.is(Private) && prefix.derivesFrom(sym.owner)) ||
(!prefix.symbol.moduleClass.isStaticOwner && isInteresting(prefix.prefix))
case SuperType(thistp, _) => isInteresting(thistp)
case AndType(tp1, tp2) => isInteresting(tp1) || isInteresting(tp2)
case OrType(tp1, tp2) => isInteresting(tp1) && isInteresting(tp2)
case _: RefinedOrRecType | _: AppliedType => true
case tp: AnnotatedType => isInteresting(tp.parent)
case _ => false
}
if (isInteresting(pre)) {
val pre1 = this(pre, false, false)
if (locked.contains(tp) || tp.symbol.infoOrCompleter.isInstanceOf[NoCompleter])
throw CyclicReference(tp.symbol)
locked += tp
try if (!tp.symbol.isClass) checkInfo(tp.info)
finally locked -= tp
tp.withPrefix(pre1)
}
else tp
}
catch {
case ex: CyclicReference =>
report.debuglog(i"cycle detected for $tp, $nestedCycleOK, $cycleOK")
if (cycleOK) LazyRef.of(tp)
else if (reportErrors) throw ex
else tp
}
case _ => mapOver(tp)
}
}
/** Under -Yrequire-targetName, if `sym` has an operator name, check that it has a
* @targetName annotation.
*/
def checkValidOperator(sym: Symbol)(using Context): Unit =
if ctx.settings.YrequireTargetName.value then
sym.name.toTermName match
case name: SimpleName
if name.isOperatorName
&& !name.isSetterName
&& !name.isConstructorName
&& !sym.getAnnotation(defn.TargetNameAnnot).isDefined
&& !sym.is(Synthetic) =>
report.warning(
i"$sym has an operator name; it should come with an @targetName annotation", sym.srcPos)
case _ =>
/** Check that `info` of symbol `sym` is not cyclic.
* @pre sym is not yet initialized (i.e. its type is a Completer).
* @return `info` where every legal F-bounded reference is proctected
* by a `LazyRef`, or `ErrorType` if a cycle was detected and reported.
*/
def checkNonCyclic(sym: Symbol, info: Type, reportErrors: Boolean)(using Context): Type = {
val checker = withMode(Mode.CheckCyclic)(new CheckNonCyclicMap(sym, reportErrors))
try checker.checkInfo(info)
catch {
case ex: CyclicReference =>
if (reportErrors)
errorType(IllegalCyclicTypeReference(sym, checker.where, checker.lastChecked), sym.srcPos)
else info
}
}
/** Check that refinement satisfies the following two conditions
* 1. No part of it refers to a symbol that's defined in the same refinement
* at a textually later point.
* 2. All references to the refinement itself via `this` are followed by
* selections.
* Note: It's not yet clear what exactly we want to allow and what we want to rule out.
* This depends also on firming up the DOT calculus. For the moment we only issue
* deprecated warnings, not errors.
*/
def checkRefinementNonCyclic(refinement: Tree, refineCls: ClassSymbol, seen: mutable.Set[Symbol])
(using Context): Unit = {
def flag(what: String, tree: Tree) =
report.warning(i"$what reference in refinement is deprecated", tree.srcPos)
def forwardRef(tree: Tree) = flag("forward", tree)
def selfRef(tree: Tree) = flag("self", tree)
val checkTree = new TreeAccumulator[Unit] {
def checkRef(tree: Tree, sym: Symbol) =
if (sym.maybeOwner == refineCls && !seen(sym)) forwardRef(tree)
def apply(x: Unit, tree: Tree)(using Context) = tree match {
case tree: MemberDef =>
foldOver(x, tree)
seen += tree.symbol
case tree @ Select(This(_), _) =>
checkRef(tree, tree.symbol)
case tree: RefTree =>
checkRef(tree, tree.symbol)
foldOver(x, tree)
case tree: This =>
selfRef(tree)
case tree: TypeTree =>
val checkType = new TypeAccumulator[Unit] {
def apply(x: Unit, tp: Type): Unit = tp match {
case tp: NamedType =>
checkRef(tree, tp.symbol)
tp.prefix match {
case pre: ThisType =>
case pre => foldOver(x, pre)
}
case tp: ThisType if tp.cls == refineCls =>
selfRef(tree)
case _ =>
foldOver(x, tp)
}
}
checkType((), tree.tpe)
case _ =>
foldOver(x, tree)
}
}
checkTree((), refinement)
}
/** Check type members inherited from different `parents` of `joint` type for cycles,
* unless a type with the same name already appears in `decls`.
* @return true iff no cycles were detected
*/
def checkNonCyclicInherited(joint: Type, parents: List[Type], decls: Scope, pos: SrcPos)(using Context): Unit = {
// If we don't have more than one parent, then there's nothing to check
if (parents.lengthCompare(1) <= 0)
return
def qualifies(sym: Symbol) = sym.name.isTypeName && !sym.is(Private)
withMode(Mode.CheckCyclic) {
val abstractTypeNames =
for (parent <- parents; mbr <- parent.abstractTypeMembers if qualifies(mbr.symbol))
yield mbr.name.asTypeName
for name <- abstractTypeNames do
try
val mbr = joint.member(name)
mbr.info match
case bounds: TypeBounds =>
!checkNonCyclic(mbr.symbol, bounds, reportErrors = true).isError
case _ =>
true
catch case _: RecursionOverflow | _: CyclicReference =>
report.error(em"cyclic reference involving type $name", pos)
false
}
}
/** Check that symbol's definition is well-formed. */
def checkWellFormed(sym: Symbol)(using Context): Unit = {
def fail(msg: Message) = report.error(msg, sym.srcPos)
def warn(msg: Message) = report.warning(msg, sym.srcPos)
def checkWithDeferred(flag: FlagSet) =
if (sym.isOneOf(flag))
fail(AbstractMemberMayNotHaveModifier(sym, flag))
def checkNoConflict(flag1: FlagSet, flag2: FlagSet, msg: => String) =
if (sym.isAllOf(flag1 | flag2)) fail(msg.toMessage)
def checkCombination(flag1: FlagSet, flag2: FlagSet) =
if sym.isAllOf(flag1 | flag2) then
fail(i"illegal combination of modifiers: `${flag1.flagsString}` and `${flag2.flagsString}` for: $sym".toMessage)
def checkApplicable(flag: Flag, ok: Boolean) =
if sym.is(flag, butNot = Synthetic) && !ok then
fail(ModifierNotAllowedForDefinition(flag))
sym.resetFlag(flag)
if (sym.is(Inline) &&
( sym.is(ParamAccessor) && sym.owner.isClass
|| sym.is(TermParam) && !sym.owner.isInlineMethod
))
fail(ParamsNoInline(sym.owner))
if sym.isInlineMethod && !sym.is(Deferred) && sym.allOverriddenSymbols.nonEmpty then
checkInlineOverrideParameters(sym)
if (sym.is(Implicit)) {
assert(!sym.owner.is(Package), s"top-level implicit $sym should be wrapped by a package after typer")
if sym.isType && (!sym.isClass || sym.is(Trait)) then
fail(TypesAndTraitsCantBeImplicit())
}
if sym.is(Transparent) then
if sym.isType then
if !sym.is(Trait) then fail(em"`transparent` can only be used for traits".toMessage)
else
if !sym.isInlineMethod then fail(em"`transparent` can only be used for inline methods".toMessage)
if (!sym.isClass && sym.is(Abstract))
fail(OnlyClassesCanBeAbstract(sym))
// note: this is not covered by the next test since terms can be abstract (which is a dual-mode flag)
// but they can never be one of ClassOnlyFlags
if !sym.isClass && sym.isOneOf(ClassOnlyFlags) then
fail(em"only classes can be ${(sym.flags & ClassOnlyFlags).flagsString}".toMessage)
if (sym.is(AbsOverride) && !sym.owner.is(Trait))
fail(AbstractOverrideOnlyInTraits(sym))
if sym.is(Trait) then
if sym.is(Final) then
fail(TraitsMayNotBeFinal(sym))
else if sym.is(Open) then
warn(RedundantModifier(Open))
if sym.isAllOf(Abstract | Open) then
warn(RedundantModifier(Open))
if sym.is(Open) && sym.isLocal then
warn(RedundantModifier(Open))
// Skip ModuleVal since the annotation will also be on the ModuleClass
if sym.hasAnnotation(defn.TailrecAnnot) then
if !sym.isOneOf(Method | ModuleVal) then
fail(TailrecNotApplicable(sym))
else if sym.is(Inline) then
fail("Inline methods cannot be @tailrec".toMessage)
if sym.hasAnnotation(defn.TargetNameAnnot) && sym.isClass && sym.isTopLevelClass then
fail(TargetNameOnTopLevelClass(sym))
if (sym.hasAnnotation(defn.NativeAnnot)) {
if (!sym.is(Deferred))
fail(NativeMembersMayNotHaveImplementation(sym))
else if(sym.owner.is(Trait))
fail(TraitMayNotDefineNativeMethod(sym))
}
else if (sym.is(Deferred, butNot = Param) && !sym.isType && !sym.isSelfSym) {
if (!sym.owner.isClass || sym.owner.is(Module) || sym.owner.isAnonymousClass)
fail(OnlyClassesCanHaveDeclaredButUndefinedMembers(sym))
checkWithDeferred(Private)
checkWithDeferred(Final)
}
if (sym.isValueClass && sym.is(Trait) && !sym.isRefinementClass)
fail(CannotExtendAnyVal(sym))
if (sym.isConstructor && !sym.isPrimaryConstructor && sym.owner.is(Trait, butNot = JavaDefined))
val addendum = if ctx.settings.Ydebug.value then s" ${sym.owner.flagsString}" else ""
fail(s"Traits cannot have secondary constructors$addendum".toMessage)
checkApplicable(Inline, sym.isTerm && !sym.isOneOf(Mutable | Module))
checkApplicable(Lazy, !sym.isOneOf(Method | Mutable))
if (sym.isType && !sym.isOneOf(Deferred | JavaDefined))
for (cls <- sym.allOverriddenSymbols.filter(_.isClass)) {
fail(CannotHaveSameNameAs(sym, cls, CannotHaveSameNameAs.CannotBeOverridden))
sym.setFlag(Private) // break the overriding relationship by making sym Private
}
checkApplicable(Erased,
!sym.isOneOf(MutableOrLazy, butNot = Given) && !sym.isType || sym.isClass)
checkCombination(Final, Open)
checkCombination(Sealed, Open)
checkCombination(Final, Sealed)
checkCombination(Private, Protected)
checkCombination(Abstract, Override)
checkCombination(Private, Override)
if sym.isType && !sym.isClass then checkCombination(Private, Opaque)
checkCombination(Lazy, Inline)
// The issue with `erased inline` is that the erased semantics get lost
// as the code is inlined and the reference is removed before the erased usage check.
checkCombination(Erased, Inline)
checkNoConflict(Lazy, ParamAccessor, s"parameter may not be `lazy`")
}
/** Check for illegal or redundant modifiers on modules. This is done separately
* from checkWellformed, since the original module modifiers don't surivive desugaring
*/
def checkWellFormedModule(mdef: untpd.ModuleDef)(using Context) =
val mods = mdef.mods
def flagSourcePos(flag: FlagSet) =
mods.mods.find(_.flags == flag).getOrElse(mdef).srcPos
if mods.is(Abstract) then
report.error(ModifierNotAllowedForDefinition(Abstract), flagSourcePos(Abstract))
if mods.is(Sealed) then
report.error(ModifierNotAllowedForDefinition(Sealed), flagSourcePos(Sealed))
if mods.is(Final, butNot = Synthetic) then
report.warning(RedundantModifier(Final), flagSourcePos(Final))
/** Check the type signature of the symbol `M` defined by `tree` does not refer
* to a private type or value which is invisible at a point where `M` is still
* visible.
*
* As an exception, we allow references to type aliases if the underlying
* type of the alias is not a leak, and if `sym` is not a type. The rationale
* for this is that the inferred type of a term symbol might contain leaky
* aliases which should be removed (see leak-inferred.scala for an example),
* but a type symbol definition will not contain leaky aliases unless the
* user wrote them, so we can ask the user to change his definition. The more
* practical reason for not transforming types is that `checkNoPrivateLeaks`
* can force a lot of denotations, and this restriction means that we never
* need to run `TypeAssigner#avoidPrivateLeaks` on type symbols when
* unpickling, which avoids some issues related to forcing order.
*
* See i997.scala for negative tests, and i1130.scala for a case where it
* matters that we transform leaky aliases away.
*
* @return The `info` of `sym`, with problematic aliases expanded away.
*/
def checkNoPrivateLeaks(sym: Symbol)(using Context): Type = {
class NotPrivate extends TypeMap {
var errors: List[() => String] = Nil
private var inCaptureSet: Boolean = false
def accessBoundary(sym: Symbol): Symbol =
if (sym.is(Private) || !sym.owner.isClass) sym.owner
else if (sym.privateWithin.exists) sym.privateWithin
else if (sym.is(Package)) sym
else accessBoundary(sym.owner)
val symBoundary = accessBoundary(sym)
/** Is `other` leaked outside its access boundary ?
* @pre The signature of `sym` refers to `other`
*/
def isLeaked(other: Symbol) =
other.is(Private, butNot = TypeParam)
&& {
val otherBoundary = other.owner
val otherLinkedBoundary = otherBoundary.linkedClass
!(symBoundary.isContainedIn(otherBoundary) ||
otherLinkedBoundary.exists && symBoundary.isContainedIn(otherLinkedBoundary))
}
&& !(inCaptureSet && other.isAllOf(LocalParamAccessor))
// class parameters in capture sets are not treated as leaked since in
// phase CheckCaptures these are treated as normal vals.
def apply(tp: Type): Type = tp match {
case tp: NamedType =>
val prevErrors = errors
var tp1 =
if (isLeaked(tp.symbol)) {
errors =
(() => em"non-private ${sym.showLocated} refers to private ${tp.symbol}\nin its type signature ${sym.info}")
:: errors
tp
}
else mapOver(tp)
if ((errors ne prevErrors) && tp.info.isTypeAlias) {
// try to dealias to avoid a leak error
val savedErrors = errors
errors = prevErrors
val tp2 = apply(tp.superType)
if (errors eq prevErrors) tp1 = tp2
else errors = savedErrors
}
tp1
case tp: ClassInfo =>
def transformedParent(tp: Type): Type = tp match {
case ref: TypeRef => ref
case ref: AppliedType => ref
case AnnotatedType(parent, annot) =>
AnnotatedType(transformedParent(parent), annot)
case _ => defn.ObjectType // can happen if class files are missing
}
tp.derivedClassInfo(
prefix = apply(tp.prefix),
declaredParents =
tp.declaredParents.map(p => transformedParent(apply(p)))
)
case tp @ AnnotatedType(underlying, annot)
if annot.symbol == defn.RetainsAnnot || annot.symbol == defn.RetainsByNameAnnot =>
val underlying1 = this(underlying)
val saved = inCaptureSet
inCaptureSet = true
val annot1 = annot.mapWith(this)
inCaptureSet = saved
derivedAnnotatedType(tp, underlying1, annot1)
case _ =>
mapOver(tp)
}
}
val notPrivate = new NotPrivate
val info = notPrivate(sym.info)
notPrivate.errors.foreach(error => report.errorOrMigrationWarning(error(), sym.srcPos, from = `3.0`))
info
}
/** Verify classes extending AnyVal meet the requirements */
def checkDerivedValueClass(clazz: Symbol, stats: List[Tree])(using Context): Unit = {
def checkValueClassMember(stat: Tree) = stat match {
case _: TypeDef if stat.symbol.isClass =>
report.error(ValueClassesMayNotDefineInner(clazz, stat.symbol), stat.srcPos)
case _: ValDef if !stat.symbol.is(ParamAccessor) =>
report.error(ValueClassesMayNotDefineNonParameterField(clazz, stat.symbol), stat.srcPos)
case _: DefDef if stat.symbol.isConstructor =>
report.error(ValueClassesMayNotDefineASecondaryConstructor(clazz, stat.symbol), stat.srcPos)
case _: MemberDef | _: Import | EmptyTree =>
// ok
case _ =>
report.error(ValueClassesMayNotContainInitalization(clazz), stat.srcPos)
}
if (isDerivedValueClass(clazz)) {
if (clazz.is(Trait))
report.error(CannotExtendAnyVal(clazz), clazz.srcPos)
if (clazz.is(Abstract))
report.error(ValueClassesMayNotBeAbstract(clazz), clazz.srcPos)
if (!clazz.isStatic)
report.error(ValueClassesMayNotBeContainted(clazz), clazz.srcPos)
if (isDerivedValueClass(underlyingOfValueClass(clazz.asClass).classSymbol))
report.error(ValueClassesMayNotWrapAnotherValueClass(clazz), clazz.srcPos)
else {
val clParamAccessors = clazz.asClass.paramAccessors.filter { param =>
param.isTerm && !param.is(Flags.Accessor)
}
clParamAccessors match {
case param :: params =>
if (param.is(Mutable))
report.error(ValueClassParameterMayNotBeAVar(clazz, param), param.srcPos)
if (param.info.isInstanceOf[ExprType])
report.error(ValueClassParameterMayNotBeCallByName(clazz, param), param.srcPos)
if (param.is(Erased))
report.error("value class first parameter cannot be `erased`", param.srcPos)
else
for (p <- params if !p.is(Erased))
report.error("value class can only have one non `erased` parameter", p.srcPos)
case Nil =>
report.error(ValueClassNeedsOneValParam(clazz), clazz.srcPos)
}
}
stats.foreach(checkValueClassMember)
}
}
/** Check the inline override methods only use inline parameters if they override an inline parameter. */
def checkInlineOverrideParameters(sym: Symbol)(using Context): Unit =
lazy val params = sym.paramSymss.flatten
for
sym2 <- sym.allOverriddenSymbols
(p1, p2) <- sym.paramSymss.flatten.lazyZip(sym2.paramSymss.flatten)
if p1.is(Inline) != p2.is(Inline)
do
report.error(
if p2.is(Inline) then "Cannot override inline parameter with a non-inline parameter"
else "Cannot override non-inline parameter with an inline parameter",
p1.srcPos)
def checkValue(tree: Tree)(using Context): Unit =
val sym = tree.tpe.termSymbol
if sym.isNoValue && !ctx.isJava then
report.error(JavaSymbolIsNotAValue(sym), tree.srcPos)
def checkValue(tree: Tree, proto: Type)(using Context): tree.type =
tree match
case tree: RefTree
if tree.name.isTermName
&& !proto.isInstanceOf[SelectionProto]
&& !proto.isInstanceOf[FunOrPolyProto] =>
checkValue(tree)
case _ =>
tree
/** Check that experimental language imports in `trees`
* are done only in experimental scopes, or in a top-level
* scope with only @experimental definitions.
*/
def checkExperimentalImports(trees: List[Tree])(using Context): Unit =
def nonExperimentalStat(trees: List[Tree]): Tree = trees match
case (_: Import | EmptyTree) :: rest =>
nonExperimentalStat(rest)
case (tree @ TypeDef(_, impl: Template)) :: rest if tree.symbol.isPackageObject =>
nonExperimentalStat(impl.body).orElse(nonExperimentalStat(rest))
case (tree: PackageDef) :: rest =>
nonExperimentalStat(tree.stats).orElse(nonExperimentalStat(rest))
case (tree: MemberDef) :: rest =>
if tree.symbol.isExperimental || tree.symbol.is(Synthetic) then
nonExperimentalStat(rest)
else
tree
case tree :: rest =>
tree
case Nil =>
EmptyTree
for case imp @ Import(qual, selectors) <- trees do
def isAllowedImport(sel: untpd.ImportSelector) =
val name = Feature.experimental(sel.name)
name == Feature.scala2macros || name == Feature.erasedDefinitions
languageImport(qual) match
case Some(nme.experimental)
if !ctx.owner.isInExperimentalScope && !selectors.forall(isAllowedImport) =>
def check(stable: => String) =
Feature.checkExperimentalFeature("features", imp.srcPos,
s"\n\nNote: the scope enclosing the import is not considered experimental because it contains the\nnon-experimental $stable")
if ctx.owner.is(Package) then
// allow top-level experimental imports if all definitions are @experimental
nonExperimentalStat(trees) match
case EmptyTree =>
case tree: MemberDef => check(i"${tree.symbol}")
case tree => check(i"expression ${tree}")
else Feature.checkExperimentalFeature("features", imp.srcPos)
case _ =>
end checkExperimentalImports
}
trait Checking {
import tpd._
def checkNonCyclic(sym: Symbol, info: TypeBounds, reportErrors: Boolean)(using Context): Type =
Checking.checkNonCyclic(sym, info, reportErrors)
def checkNonCyclicInherited(joint: Type, parents: List[Type], decls: Scope, pos: SrcPos)(using Context): Unit =
Checking.checkNonCyclicInherited(joint, parents, decls, pos)
/** Check that type `tp` is stable. */
def checkStable(tp: Type, pos: SrcPos, kind: String)(using Context): Unit =
if !tp.isStable then report.error(NotAPath(tp, kind), pos)
/** Check that all type members of `tp` have realizable bounds */
def checkRealizableBounds(cls: Symbol, pos: SrcPos)(using Context): Unit = {
val rstatus = boundsRealizability(cls.thisType)
if (rstatus ne Realizable)
report.error(ex"$cls cannot be instantiated since it${rstatus.msg}", pos)
}
/** Check that pattern `pat` is irrefutable for scrutinee type `sel.tpe`.
* This means `sel` is either marked @unchecked or `sel.tpe` conforms to the
* pattern's type. If pattern is an UnApply, also check that the extractor is
* irrefutable, and do the check recursively.
*/
def checkIrrefutable(sel: Tree, pat: Tree, isPatDef: Boolean)(using Context): Boolean = {
val pt = sel.tpe
enum Reason:
case NonConforming, RefutableExtractor
def fail(pat: Tree, pt: Type, reason: Reason): Boolean = {
import Reason._
val message = reason match
case NonConforming =>
var reportedPt = pt.dropAnnot(defn.UncheckedAnnot)
if !pat.tpe.isSingleton then reportedPt = reportedPt.widen
val problem = if pat.tpe <:< reportedPt then "is more specialized than" else "does not match"
ex"pattern's type ${pat.tpe} $problem the right hand side expression's type $reportedPt"
case RefutableExtractor =>
val extractor =
val UnApply(fn, _, _) = pat: @unchecked
tpd.funPart(fn) match
case Select(id, _) => id
case _ => EmptyTree
if extractor.isEmpty then
em"pattern binding uses refutable extractor"
else
em"pattern binding uses refutable extractor `$extractor`"
val fix =
if isPatDef then "adding `: @unchecked` after the expression"
else "adding the `case` keyword before the full pattern"
val addendum =
if isPatDef then "may result in a MatchError at runtime"
else "will result in a filtering for expression (using `withFilter`)"
val usage = reason match
case NonConforming => "the narrowing"
case RefutableExtractor => "this usage"
val pos =
if isPatDef then reason match
case NonConforming => sel.srcPos
case RefutableExtractor => pat.source.atSpan(pat.span union sel.span)
else pat.srcPos
def rewriteMsg = Message.rewriteNotice("This patch", `3.2-migration`)
report.gradualErrorOrMigrationWarning(
em"""$message
|
|If $usage is intentional, this can be communicated by $fix,
|which $addendum.$rewriteMsg""",
pos, warnFrom = `3.2`, errorFrom = `future`)
false
}
def check(pat: Tree, pt: Type): Boolean = (pt <:< pat.tpe) || fail(pat, pt, Reason.NonConforming)
def recur(pat: Tree, pt: Type): Boolean =
!sourceVersion.isAtLeast(`3.2`)
|| pt.hasAnnotation(defn.UncheckedAnnot)
|| {
patmatch.println(i"check irrefutable $pat: ${pat.tpe} against $pt")
pat match
case Bind(_, pat1) =>
recur(pat1, pt)
case UnApply(fn, _, pats) =>
check(pat, pt) &&
(isIrrefutable(fn, pats.length) || fail(pat, pt, Reason.RefutableExtractor)) && {
val argPts = unapplyArgs(fn.tpe.widen.finalResultType, fn, pats, pat.srcPos)
pats.corresponds(argPts)(recur)
}
case Alternative(pats) =>
pats.forall(recur(_, pt))
case Typed(arg, tpt) =>
check(pat, pt) && recur(arg, pt)
case Ident(nme.WILDCARD) =>
true
case _ =>
check(pat, pt)
}
recur(pat, pt)
}
private def checkLegalImportOrExportPath(path: Tree, kind: String)(using Context): Unit = {
checkStable(path.tpe, path.srcPos, kind)
if (!ctx.isAfterTyper) Checking.checkRealizable(path.tpe, path.srcPos)
if !isIdempotentExpr(path) then
report.error(em"import prefix is not a pure expression", path.srcPos)
}
/** Check that `path` is a legal prefix for an import clause */
def checkLegalImportPath(path: Tree)(using Context): Unit =
checkLegalImportOrExportPath(path, "import prefix")
languageImport(path) match
case Some(prefix) =>
val required =
if prefix == nme.experimental then defn.LanguageExperimentalModule
else if prefix == nme.deprecated then defn.LanguageDeprecatedModule
else defn.LanguageModule
if path.symbol != required then
report.error(em"import looks like a language import, but refers to something else: ${path.symbol.showLocated}", path.srcPos)
case None =>
val foundClasses = path.tpe.classSymbols
if foundClasses.contains(defn.LanguageModule.moduleClass)
|| foundClasses.contains(defn.LanguageExperimentalModule.moduleClass)
then
report.error(em"no aliases can be used to refer to a language import", path.srcPos)
/** Check that `path` is a legal prefix for an export clause */
def checkLegalExportPath(path: Tree, selectors: List[untpd.ImportSelector])(using Context): Unit =
checkLegalImportOrExportPath(path, "export prefix")
if
selectors.exists(_.isWildcard)
&& path.tpe.classSymbol.is(PackageClass)
then
// we restrict wildcard export from package as incremental compilation does not yet
// register a dependency on "all members of a package" - see https://github.com/sbt/zinc/issues/226
report.error(
em"Implementation restriction: ${path.tpe.classSymbol} is not a valid prefix " +
"for a wildcard export, as it is a package.", path.srcPos)
/** Check that module `sym` does not clash with a class of the same name
* that is concurrently compiled in another source file.
*/
def checkNoModuleClash(sym: Symbol)(using Context): Unit =
val effectiveOwner = sym.effectiveOwner
if effectiveOwner.is(Package)
&& effectiveOwner.info.member(sym.name.moduleClassName).symbol.isAbsent()
then
val conflicting = effectiveOwner.info.member(sym.name.toTypeName).symbol
if conflicting.exists then
report.error(AlreadyDefined(sym.name, effectiveOwner, conflicting), sym.srcPos)
/** Check that `tp` is a class type.
* Also, if `traitReq` is true, check that `tp` is a trait.
* Also, if `stablePrefixReq` is true and phase is not after RefChecks,
* check that class prefix is stable.
* @return `tp` itself if it is a class or trait ref, ObjectType if not.
*/
def checkClassType(tp: Type, pos: SrcPos, traitReq: Boolean, stablePrefixReq: Boolean)(using Context): Type =
tp.underlyingClassRef(refinementOK = false) match {
case tref: TypeRef =>
if (traitReq && !tref.symbol.is(Trait)) report.error(TraitIsExpected(tref.symbol), pos)
if (stablePrefixReq && ctx.phase <= refchecksPhase) checkStable(tref.prefix, pos, "class prefix")
tp
case _ =>
report.error(NotClassType(tp), pos)
defn.ObjectType
}
/** If `sym` is an old-style implicit conversion, check that implicit conversions are enabled.
* @pre sym.is(GivenOrImplicit)
*/
def checkImplicitConversionDefOK(sym: Symbol)(using Context): Unit =
if sym.isOldStyleImplicitConversion(directOnly = true) then
checkFeature(
nme.implicitConversions,
i"Definition of implicit conversion $sym",
ctx.owner.topLevelClass,
sym.srcPos)
/** If `tree` is an application of a new-style implicit conversion (using the apply
* method of a `scala.Conversion` instance), check that implicit conversions are
* enabled.
*/
def checkImplicitConversionUseOK(tree: Tree)(using Context): Unit =
val sym = tree.symbol
if sym.name == nme.apply
&& sym.owner.derivesFrom(defn.ConversionClass)
&& !sym.info.isErroneous
then
def conv = methPart(tree) match
case Select(qual, _) => qual.symbol.orElse(sym.owner)
case _ => sym.owner
checkFeature(nme.implicitConversions,
i"Use of implicit conversion ${conv.showLocated}", NoSymbol, tree.srcPos)
private def infixOKSinceFollowedBy(tree: untpd.Tree): Boolean = tree match {
case _: untpd.Block | _: untpd.Match => true
case _ => false
}