@@ -629,15 +629,15 @@ object RefChecks {
629
629
// Verifying a concrete class has nothing unimplemented.
630
630
if (! clazz.isOneOf(AbstractOrTrait )) {
631
631
val abstractErrors = new mutable.ListBuffer [String ]
632
+ val concreteClassUnimplementedMethodError = new mutable.ListBuffer [Message ]
632
633
def abstractErrorMessage =
633
634
// a little formatting polish
634
635
if (abstractErrors.size <= 2 ) abstractErrors.mkString(" " )
635
636
else abstractErrors.tail.mkString(abstractErrors.head + " :\n " , " \n " , " " )
636
637
637
- def abstractClassError (mustBeMixin : Boolean , msg : String ): Unit = {
638
+ def abstractClassError (msg : String ): Unit = {
638
639
def prelude = (
639
640
if (clazz.isAnonymousClass || clazz.is(Module )) " object creation impossible"
640
- else if (mustBeMixin) s " $clazz needs to be a mixin "
641
641
else if clazz.is(Synthetic ) then " instance cannot be created"
642
642
else s " $clazz needs to be abstract "
643
643
) + " , since"
@@ -717,37 +717,22 @@ object RefChecks {
717
717
}
718
718
719
719
def stubImplementations : List [String ] = {
720
- // Grouping missing methods by the declaring class
721
- val regrouped = missingMethods.groupBy(_.owner).toList
722
720
def membersStrings (members : List [Symbol ]) =
723
721
members.sortBy(_.name.toString).map(_.asSeenFrom(clazz.thisType).showDcl + " = ???" )
724
722
725
- if (regrouped.tail.isEmpty)
726
- membersStrings(regrouped.head._2)
727
- else (regrouped.sortBy(_._1.name.toString()) flatMap {
728
- case (owner, members) =>
729
- (" // Members declared in " + owner.fullName) +: membersStrings(members) :+ " "
730
- }).init
723
+ membersStrings(missingMethods)
731
724
}
732
725
733
- // If there are numerous missing methods, we presume they are aware of it and
734
- // give them a nicely formatted set of method signatures for implementing.
735
- if (missingMethods.size > 1 ) {
736
- abstractClassError(false , " it has " + missingMethods.size + " unimplemented members." )
737
- val preface =
738
- """ |/** As seen from %s, the missing signatures are as follows.
739
- | * For convenience, these are usable as stub implementations.
740
- | */
741
- |""" .stripMargin.format(clazz)
742
- abstractErrors += stubImplementations.map(" " + _ + " \n " ).mkString(preface, " " , " " )
726
+ if (missingMethods.size >= 1 ) {
727
+ concreteClassUnimplementedMethodError += ConcreteClassHasUnimplementedMethods (clazz, missingMethods, createAddMissingMethodsAction(clazz, stubImplementations))
743
728
return
744
729
}
745
730
746
731
for (member <- missingMethods) {
747
732
def showDclAndLocation (sym : Symbol ) =
748
733
s " ${sym.showDcl} in ${sym.owner.showLocated}"
749
734
def undefined (msg : String ) =
750
- abstractClassError(false , s " ${showDclAndLocation(member)} is not defined $msg" )
735
+ abstractClassError(s " ${showDclAndLocation(member)} is not defined $msg" )
751
736
val underlying = member.underlyingSymbol
752
737
753
738
// Give a specific error message for abstract vars based on why it fails:
@@ -840,7 +825,7 @@ object RefChecks {
840
825
val impl1 = clazz.thisType.nonPrivateMember(decl.name) // DEBUG
841
826
report.log(i " ${impl1}: ${impl1.info}" ) // DEBUG
842
827
report.log(i " ${clazz.thisType.memberInfo(decl)}" ) // DEBUG
843
- abstractClassError(false , " there is a deferred declaration of " + infoString(decl) +
828
+ abstractClassError(" there is a deferred declaration of " + infoString(decl) +
844
829
" which is not implemented in a subclass" + err.abstractVarMessage(decl))
845
830
}
846
831
if (bc.asClass.superClass.is(Abstract ))
@@ -916,6 +901,8 @@ object RefChecks {
916
901
if (abstractErrors.nonEmpty)
917
902
report.error(abstractErrorMessage, clazz.srcPos)
918
903
904
+ concreteClassUnimplementedMethodError.foreach(report.error(_, clazz.srcPos))
905
+
919
906
checkMemberTypesOK()
920
907
checkCaseClassInheritanceInvariant()
921
908
}
@@ -1272,6 +1259,79 @@ object RefChecks {
1272
1259
case tp : NamedType if tp.prefix.typeSymbol != ctx.owner.enclosingClass =>
1273
1260
report.warning(UnqualifiedCallToAnyRefMethod (tree, tree.symbol), tree)
1274
1261
case _ => ()
1262
+
1263
+ private def createAddMissingMethodsAction (clazz : ClassSymbol , methods : List [String ])(using Context ): List [CodeAction ] = {
1264
+ import dotty .tools .dotc .rewrites .Rewrites .ActionPatch
1265
+ import dotty .tools .dotc .util .Spans
1266
+ import dotty .tools .dotc .ast .untpd
1267
+
1268
+ val untypedTree = NavigateAST
1269
+ .untypedPath(clazz.span)
1270
+ .head
1271
+ .asInstanceOf [untpd.Tree ]
1272
+
1273
+ val classSrcPos = clazz.srcPos
1274
+ val content = classSrcPos.sourcePos.source.content()
1275
+ val span = classSrcPos.endPos.span
1276
+
1277
+ val classText = new String (content.slice(untypedTree.span.start, untypedTree.span.end))
1278
+ val classHasBraces = classText.contains(" {" ) && classText.contains(" }" )
1279
+
1280
+ // Indentation for inserted methods
1281
+ val lineStart = content.lastIndexWhere(c => c == '\n ' , end = span.end - 1 ) + 1
1282
+ val baseIndent = new String (content.slice(lineStart, span.end).takeWhile(c => c == ' ' || c == '\t ' ))
1283
+ val indent = baseIndent + " "
1284
+
1285
+ val formattedMethods = methods.map(m => s " $indent$m" ).mkString(System .lineSeparator())
1286
+
1287
+ val isBracelessSyntax = untypedTree match
1288
+ case untpd.TypeDef (_, tmpl : untpd.Template ) =>
1289
+ ! classText.contains(" {" ) && tmpl.body.nonEmpty
1290
+ case _ => false
1291
+
1292
+ if (classHasBraces) {
1293
+ val insertBeforeBrace = untypedTree.sourcePos.withSpan(Span (untypedTree.span.end - 1 ))
1294
+ val braceStart = classText.indexOf('{' )
1295
+ val braceEnd = classText.lastIndexOf('}' )
1296
+ val bodyBetweenBraces = classText.slice(braceStart + 1 , braceEnd)
1297
+ val bodyIsEmpty = bodyBetweenBraces.forall(_.isWhitespace)
1298
+ val bodyContainsNewLine = bodyBetweenBraces.contains(System .lineSeparator())
1299
+
1300
+ val prefix = if (bodyContainsNewLine) " " else System .lineSeparator()
1301
+ val patchText =
1302
+ prefix +
1303
+ formattedMethods +
1304
+ System .lineSeparator()
1305
+
1306
+ val patch = ActionPatch (insertBeforeBrace, patchText)
1307
+ List (CodeAction (" Add missing methods" , None , List (patch)))
1308
+ } else if (isBracelessSyntax) {
1309
+ val insertAfterLastDef = untypedTree match
1310
+ case untpd.TypeDef (_, tmpl : untpd.Template ) if tmpl.body.nonEmpty =>
1311
+ val lastDef = tmpl.body.last
1312
+ lastDef.sourcePos.withSpan(Span (lastDef.span.end))
1313
+ case _ =>
1314
+ untypedTree.sourcePos.withSpan(Span (untypedTree.span.end))
1315
+
1316
+ val patchText = System .lineSeparator() + formattedMethods
1317
+
1318
+ val patch = ActionPatch (insertAfterLastDef, patchText)
1319
+ List (CodeAction (" Add missing methods" , None , List (patch)))
1320
+ } else {
1321
+ // Class has no body – add whole `{ ... }` after class header, same line
1322
+ val insertAfterHeader = untypedTree.sourcePos.withSpan(Span (untypedTree.span.end))
1323
+
1324
+ val patchText =
1325
+ " {" + System .lineSeparator() +
1326
+ formattedMethods + System .lineSeparator() +
1327
+ " }"
1328
+
1329
+ val patch = ActionPatch (insertAfterHeader, patchText)
1330
+ List (CodeAction (" Add missing methods" , None , List (patch)))
1331
+ }
1332
+ }
1333
+
1334
+
1275
1335
}
1276
1336
import RefChecks .*
1277
1337
0 commit comments