1
- // ALWAYS KEEP THIS FILE IN src-bootstrapped, DO NOT MOVE TO src
2
-
3
- package dotty .internal
4
-
5
- import scala .quoted ._
6
-
7
- object StringContextMacro {
8
-
9
- /** Implementation of scala.StringContext.f used in Dotty */
10
- inline def f (inline sc : StringContext )(inline args : Any * ): String = $ { interpolate(' sc , ' args ) }
1
+ package dotty .tools .dotc
2
+ package transform .localopt
3
+
4
+ import dotty .tools .dotc .ast .Trees ._
5
+ import dotty .tools .dotc .ast .tpd
6
+ import dotty .tools .dotc .core .Decorators ._
7
+ import dotty .tools .dotc .core .Constants .Constant
8
+ import dotty .tools .dotc .core .Contexts ._
9
+ import dotty .tools .dotc .core .StdNames ._
10
+ import dotty .tools .dotc .core .NameKinds ._
11
+ import dotty .tools .dotc .core .Symbols ._
12
+ import dotty .tools .dotc .core .Types ._
13
+
14
+ // Ported from old dotty.internal.StringContextMacro
15
+ // TODO: port Scala 2 logic? (see https://github.com/scala/scala/blob/2.13.x/src/compiler/scala/tools/reflect/FormatInterpolator.scala#L74)
16
+ object StringContextChecker {
17
+ import tpd ._
11
18
12
19
/** This trait defines a tool to report errors/warnings that do not depend on Position. */
13
20
trait Reporter {
@@ -51,53 +58,52 @@ object StringContextMacro {
51
58
def restoreReported () : Unit
52
59
}
53
60
54
- /** Interpolates the arguments to the formatting String given inside a StringContext
55
- *
56
- * @param strCtxExpr the Expr that holds the StringContext which contains all the chunks of the formatting string
57
- * @param args the Expr that holds the sequence of arguments to interpolate to the String in the correct format
58
- * @return the Expr containing the formatted and interpolated String or an error/warning if the parameters are not correct
59
- */
60
- private def interpolate (strCtxExpr : Expr [StringContext ], argsExpr : Expr [Seq [Any ]])(using qctx : QuoteContext ): Expr [String ] = {
61
- import qctx .tasty ._
62
- val sourceFile = strCtxExpr.unseal.pos.sourceFile
63
-
64
- val (partsExpr, parts) = strCtxExpr match {
65
- case Expr .StringContext (p1 as Consts (p2)) => (p1.toList, p2.toList)
66
- case _ => report.throwError(" Expected statically known String Context" , strCtxExpr)
61
+ /** Check the format of the parts of the f".." arguments and returns the string parts of the StringContext */
62
+ def checkedParts (strContext_f : Tree , args0 : Tree )(using Context ): String = {
63
+
64
+ val (partsExpr, parts) = strContext_f match {
65
+ case TypeApply (Select (Apply (_, (parts : SeqLiteral ) :: Nil ), _), _) =>
66
+ (parts.elems, parts.elems.map { case Literal (Constant (str : String )) => str } )
67
+ case _ =>
68
+ report.error(" Expected statically known String Context" , strContext_f.srcPos)
69
+ return " "
67
70
}
68
71
69
- val args = argsExpr match {
70
- case Varargs (args) => args
71
- case _ => report.throwError(" Expected statically known argument list" , argsExpr)
72
+ val args = args0 match {
73
+ case args : SeqLiteral => args.elems
74
+ case _ =>
75
+ report.error(" Expected statically known argument list" , args0.srcPos)
76
+ return " "
72
77
}
73
78
74
79
val reporter = new Reporter {
75
80
private [this ] var reported = false
76
81
private [this ] var oldReported = false
77
82
def partError (message : String , index : Int , offset : Int ) : Unit = {
78
83
reported = true
79
- val positionStart = partsExpr(index).unseal.pos.start + offset
80
- error(message, sourceFile, positionStart, positionStart)
84
+ val pos = partsExpr(index).sourcePos
85
+ val posOffset = pos.withSpan(pos.span.shift(offset))
86
+ report.error(message, posOffset)
81
87
}
82
88
def partWarning (message : String , index : Int , offset : Int ) : Unit = {
83
89
reported = true
84
- val positionStart = partsExpr(index).unseal.pos.start + offset
85
- warning(message, sourceFile, positionStart, positionStart)
90
+ val pos = partsExpr(index).sourcePos
91
+ val posOffset = pos.withSpan(pos.span.shift(offset))
92
+ report.warning(message, posOffset)
86
93
}
87
94
88
95
def argError (message : String , index : Int ) : Unit = {
89
96
reported = true
90
- error(message, args(index).unseal.pos )
97
+ report. error(message, args(index).srcPos )
91
98
}
92
99
93
100
def strCtxError (message : String ) : Unit = {
94
101
reported = true
95
- val positionStart = strCtxExpr.unseal.pos.start
96
- error(message, sourceFile, positionStart, positionStart)
102
+ report.error(message, strContext_f.srcPos)
97
103
}
98
104
def argsError (message : String ) : Unit = {
99
105
reported = true
100
- error(message, argsExpr.unseal.pos )
106
+ report. error(message, args0.srcPos )
101
107
}
102
108
103
109
def hasReported () : Boolean = {
@@ -114,18 +120,11 @@ object StringContextMacro {
114
120
}
115
121
}
116
122
117
- interpolate (parts, args, argsExpr , reporter)
123
+ checked (parts, args, reporter)
118
124
}
119
125
120
- /** Helper function for the interpolate function above
121
- *
122
- * @param partsExpr the list of parts enumerated as Expr
123
- * @param args the list of arguments enumerated as Expr
124
- * @param reporter the reporter to return any error/warning when a problem is encountered
125
- * @return the Expr containing the formatted and interpolated String or an error/warning report if the parameters are not correct
126
- */
127
- def interpolate (parts0 : List [String ], args : Seq [Expr [Any ]], argsExpr : Expr [Seq [Any ]], reporter : Reporter )(using qctx : QuoteContext ) : Expr [String ] = {
128
- import qctx .tasty ._
126
+ def checked (parts0 : List [String ], args : List [Tree ], reporter : Reporter )(using Context ): String = {
127
+
129
128
130
129
/** Checks if the number of arguments are the same as the number of formatting strings
131
130
*
@@ -585,21 +584,21 @@ object StringContextMacro {
585
584
* nothing otherwise
586
585
*/
587
586
def checkTypeWithArgs (argument : (Type , Int ), conversionChar : Char , partIndex : Int , flags : List [(Char , Int )]) = {
588
- val booleans = List (Type .of[ Boolean ], Type .of[ Null ] )
589
- val dates = List (Type .of[ Long ], Type .of[ java.util.Calendar ], Type .of[ java.util.Date ] )
590
- val floatingPoints = List (Type .of[ Double ], Type .of[ Float ], Type .of[ java.math.BigDecimal ] )
591
- val integral = List (Type .of[ Int ], Type .of[ Long ], Type .of[ Short ], Type .of[ Byte ], Type .of[ java.math.BigInteger ] )
592
- val character = List (Type .of[ Char ], Type .of[ Byte ], Type .of[ Short ], Type .of[ Int ] )
587
+ val booleans = List (defn. BooleanType , defn. NullType )
588
+ val dates = List (defn. LongType , requiredClass( " java.util.Calendar" ).typeRef, requiredClass( " java.util.Date" ).typeRef )
589
+ val floatingPoints = List (defn. DoubleType , defn. FloatType , requiredClass( " java.math.BigDecimal" ).typeRef )
590
+ val integral = List (defn. IntType , defn. LongType , defn. ShortType , defn. ByteType , requiredClass( " java.math.BigInteger" ).typeRef )
591
+ val character = List (defn. CharType , defn. ByteType , defn. ShortType , defn. IntType )
593
592
594
593
val (argType, argIndex) = argument
595
594
conversionChar match {
596
595
case 'c' | 'C' => checkSubtype(argType, " Char" , argIndex, character : _* )
597
596
case 'd' | 'o' | 'x' | 'X' => {
598
597
checkSubtype(argType, " Int" , argIndex, integral : _* )
599
598
if (conversionChar != 'd' ) {
600
- val notAllowedFlagOnCondition = List (('+' , ! (argType <:< Type .of[ java.math.BigInteger ] ), " only use '+' for BigInt conversions to o, x, X" ),
601
- (' ' , ! (argType <:< Type .of[ java.math.BigInteger ] ), " only use ' ' for BigInt conversions to o, x, X" ),
602
- ('(' , ! (argType <:< Type .of[ java.math.BigInteger ] ), " only use '(' for BigInt conversions to o, x, X" ),
599
+ val notAllowedFlagOnCondition = List (('+' , ! (argType <:< requiredClass( " java.math.BigInteger" ).typeRef ), " only use '+' for BigInt conversions to o, x, X" ),
600
+ (' ' , ! (argType <:< requiredClass( " java.math.BigInteger" ).typeRef ), " only use ' ' for BigInt conversions to o, x, X" ),
601
+ ('(' , ! (argType <:< requiredClass( " java.math.BigInteger" ).typeRef ), " only use '(' for BigInt conversions to o, x, X" ),
603
602
(',' , true , " ',' only allowed for d conversion of integral types" ))
604
603
checkFlags(partIndex, flags, notAllowedFlagOnCondition : _* )
605
604
}
@@ -608,7 +607,7 @@ object StringContextMacro {
608
607
case 't' | 'T' => checkSubtype(argType, " Date" , argIndex, dates : _* )
609
608
case 'b' | 'B' => checkSubtype(argType, " Boolean" , argIndex, booleans : _* )
610
609
case 'h' | 'H' | 'S' | 's' =>
611
- if ( ! (argType <:< Type .of[ java.util.Formattable ]))
610
+ if ! (argType <:< requiredClass( " java.util.Formattable" ).typeRef) then
612
611
for {flag <- flags ; if (flag._1 == '#' )}
613
612
reporter.argError(" type mismatch;\n found : " + argType.widen.show.stripPrefix(" scala.Predef." ).stripPrefix(" java.lang." ).stripPrefix(" scala." ) + " \n required: java.util.Formattable" , argIndex)
614
613
case 'n' | '%' =>
@@ -647,7 +646,7 @@ object StringContextMacro {
647
646
* @param maxArgumentIndex an Option containing the maximum argument index possible, None if no args are specified
648
647
* @return a list with all the elements of the conversion per formatting string
649
648
*/
650
- def checkPart (part : String , start : Int , argument : Option [(Int , Expr [ Any ] )], maxArgumentIndex : Option [Int ]) : List [(Option [(Type , Int )], Char , List [(Char , Int )])] = {
649
+ def checkPart (part : String , start : Int , argument : Option [(Int , Tree )], maxArgumentIndex : Option [Int ]) : List [(Option [(Type , Int )], Char , List [(Char , Int )])] = {
651
650
reporter.resetReported()
652
651
val hasFormattingSubstring = getFormattingSubstring(part, part.size, start)
653
652
if (hasFormattingSubstring.nonEmpty) {
@@ -658,7 +657,7 @@ object StringContextMacro {
658
657
case Some (argIndex, arg) => {
659
658
val (hasArgumentIndex, argumentIndex, flags, hasWidth, width, hasPrecision, precision, hasRelative, relativeIndex, conversion) = getFormatSpecifiers(part, argIndex, argIndex + 1 , false , formattingStart)
660
659
if (! reporter.hasReported()){
661
- val conversionWithType = checkFormatSpecifiers(argIndex + 1 , hasArgumentIndex, argumentIndex, Some (argIndex + 1 ), start == 0 , maxArgumentIndex, hasRelative, hasWidth, width, hasPrecision, precision, flags, conversion, Some (arg.unseal. tpe), part)
660
+ val conversionWithType = checkFormatSpecifiers(argIndex + 1 , hasArgumentIndex, argumentIndex, Some (argIndex + 1 ), start == 0 , maxArgumentIndex, hasRelative, hasWidth, width, hasPrecision, precision, flags, conversion, Some (arg.tpe), part)
662
661
nextStart = conversion + 1
663
662
conversionWithType :: checkPart(part, nextStart, argument, maxArgumentIndex)
664
663
} else checkPart(part, conversion + 1 , argument, maxArgumentIndex)
@@ -710,7 +709,6 @@ object StringContextMacro {
710
709
}
711
710
}
712
711
713
- // macro expansion
714
- ' {($ {Expr (parts.mkString)}).format($ {argsExpr}: _* )}
712
+ parts.mkString
715
713
}
716
714
}
0 commit comments