Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
梦境迷离
Scala Macro Tools
提交
258cfa46
S
Scala Macro Tools
项目概览
梦境迷离
/
Scala Macro Tools
上一次同步 大约 1 年
通知
8
Star
0
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
DevOps
流水线
流水线任务
计划
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
S
Scala Macro Tools
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
DevOps
DevOps
流水线
流水线任务
计划
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
流水线任务
提交
Issue看板
前往新版Gitcode,体验更适合开发者的 AI 搜索 >>
未验证
提交
258cfa46
编写于
6月 14, 2021
作者:
梦境迷离
提交者:
GitHub
6月 14, 2021
浏览文件
操作
浏览文件
下载
差异文件
Merge pull request #1 from bitlap/dev
support field name
上级
88c65b61
0bd530ec
变更
3
隐藏空白更改
内联
并排
Showing
3 changed file
with
274 addition
and
134 deletion
+274
-134
README.md
README.md
+31
-43
src/main/scala/io/github/liguobin/toString.scala
src/main/scala/io/github/liguobin/toString.scala
+86
-68
src/test/scala/io/github/liguobin/ToStringTest.scala
src/test/scala/io/github/liguobin/ToStringTest.scala
+157
-23
未找到文件。
README.md
浏览文件 @
258cfa46
...
...
@@ -4,59 +4,47 @@ scala macro and abstract syntax tree learning code.
# @toString
-
NOTE
-
Automatically ignore when use on
`case`
class
.
-
Contains constructor parameters which have
`val`
/
`var`
modifier and class internal fields
.
-
The existing custom
`toString`
method will fail to compile
.
-
Argument
-
`verbose`
Whether to enable detailed log
.
-
`withFieldName`
Whether to include the name of the field in the toString
.
-
`containsCtorParams`
Whether to include the fields of the primary constructor
.
-
source code1
```
scala
@toString
(
isContainsCtorParams
=
true
)
class
TestClass
(
val
i
:
Int
=
0
,
var
j
:
Int
)
{
val
y
:
Int
=
0
var
z
:
String
=
"hello"
var
x
:
String
=
"world"
}
// result of scalac
class
TestClass
extends
scala
.
AnyRef
{
<
paramaccessor
>
val
i
:
Int
=
_
;
<
paramaccessor
>
var
j
:
Int
=
_
;
def
<
init
>(
i
:
Int
=
0
,
j
:
Int
)
=
{
super
.<
init
>();
()
};
val
y
:
Int
=
0
;
var
z
:
String
=
"hello"
;
var
x
:
String
=
"world"
;
override
def
toString
()
:
String
=
scala
.
collection
.
immutable
.
List
(
i
,
j
,
y
,
z
,
x
).
toString
.
replace
(
"List"
,
"TestClass"
)
// a crude way, TODO refactor it.
}
//println(new TestClass(1, 2))
//TestClass(1, 2, 0, hello, world)
case
class
TestClass2
(
i
:
Int
=
0
,
var
j
:
Int
)
// No method body, only have primary constructor.
```
-
source code2
```
scala
@toString
class
TestClass
(
val
i
:
Int
=
0
,
var
j
:
Int
)
{
val
y
:
Int
=
0
var
z
:
String
=
"hello"
var
x
:
String
=
"world"
}
// result of scalac
class
TestClass
extends
scala
.
AnyRef
{
<
paramaccessor
>
val
i
:
Int
=
_
;
<
paramaccessor
>
var
j
:
Int
=
_
;
def
<
init
>(
i
:
Int
=
0
,
j
:
Int
)
=
{
super
.<
init
>();
()
};
val
y
:
Int
=
0
;
var
z
:
String
=
"hello"
;
var
x
:
String
=
"world"
;
override
def
toString
()
:
String
=
scala
.
collection
.
immutable
.
List
(
y
,
z
,
x
).
toString
.
replace
(
"List"
,
"TestClass"
)
// a crude way, TODO refactor it.
}
//println(new TestClass(1, 2))
//TestClass(0, hello, world)
-
when withFieldName=false containsCtorParams=false
```
println(new TestClass(1, 2))
TestClass(0, hello, world)
```
-
when withFieldName=false containsCtorParams=true
```
println(new TestClass(1, 2))
TestClass(1, 2, 0, hello, world)
```
-
when withFieldName=true containsCtorParams=false
```
println(new TestClass(1, 2))
TestClass(y=0, z=hello, x=world)
```
-
when withFieldName=true containsCtorParams=true
```
println(new TestClass(1, 2))
TestClass(i=1, j=2, y=0, z=hello, x=world)
```
\ No newline at end of file
src/main/scala/io/github/liguobin/toString.scala
浏览文件 @
258cfa46
package
io.github.liguobin
import
scala.annotation.
{
StaticAnnotation
,
compileTimeOnly
}
import
scala.annotation.
{
StaticAnnotation
,
compileTimeOnly
}
import
scala.language.experimental.macros
import
scala.reflect.macros.whitebox
...
...
@@ -8,110 +8,128 @@ import scala.reflect.macros.whitebox
* toString for class
*
* @author 梦境迷离
* @param verbose Whether to enable detailed log
* @param isContainsCtorParams Whether to include the fields in the constructor
* @param verbose Whether to enable detailed log.
* @param containsCtorParams Whether to include the fields of the primary constructor.
* @param withFieldName Whether to include the name of the field in the toString.
* @since 2021/6/13
* @version 1.0
*/
@compileTimeOnly
(
"enable macro to expand macro annotations"
)
class
toString
(
verbose
:
Boolean
=
false
,
isContainsCtorParams
:
Boolean
=
false
)
extends
StaticAnnotation
{
class
toString
(
verbose
:
Boolean
=
false
,
containsCtorParams
:
Boolean
=
true
,
withFieldName
:
Boolean
=
true
)
extends
StaticAnnotation
{
def
macroTransform
(
annottees
:
Any*
)
:
Any
=
macro
stringMacro
.
impl
}
case
class
Argument
(
verbose
:
Boolean
,
containsCtorParams
:
Boolean
,
withFieldName
:
Boolean
)
object
stringMacro
{
def
impl
(
c
:
whitebox.Context
)(
annottees
:
c.Expr
[
Any
]*)
:
c.Expr
[
Any
]
=
{
def
printField
(
c
:
whitebox.Context
)(
argument
:
Argument
,
lastParam
:
Option
[
String
],
field
:
c.universe.Tree
)
:
c.universe.Tree
=
{
import
c.universe._
// extract 'isVerbose' parameters of annotation
val
isVerbose
=
c
.
prefix
.
tree
match
{
case
Apply
(
_
,
q
"verbose = $foo"
::
Nil
)
=>
foo
match
{
case
Literal
(
Constant
(
verbose
:
Boolean
))
=>
verbose
case
_
=>
c
.
warning
(
c
.
enclosingPosition
,
"The value provided for 'verbose' must be a constant (true or false) and not an expression (e.g. 2 == 1 + 1). Verbose set to false."
)
false
// Print one field as <name of the field>+"="+fieldName
if
(
argument
.
withFieldName
)
{
lastParam
.
fold
(
q
"$field"
)
{
lp
=>
field
match
{
case
tree
@q
"$mods var $tname: $tpt = $expr"
=>
if
(
tname
.
toString
()
!=
lp
)
q
"""${tname.toString()}+${"="}+this.$tname+${", "}"""
else
q
"""${tname.toString()}+${"="}+this.$tname"""
case
tree
@q
"$mods val $tname: $tpt = $expr"
=>
if
(
tname
.
toString
()
!=
lp
)
q
"""${tname.toString()}+${"="}+this.$tname+${", "}"""
else
q
"""${tname.toString()}+${"="}+this.$tname"""
case
_
=>
q
"$field"
}
case
_
=>
false
}
}
else
{
lastParam
.
fold
(
q
"$field"
)
{
lp
=>
field
match
{
case
tree
@q
"$mods var $tname: $tpt = $expr"
=>
if
(
tname
.
toString
()
!=
lp
)
q
"""$tname+${", "}"""
else
q
"""$tname"""
case
tree
@q
"$mods val $tname: $tpt = $expr"
=>
if
(
tname
.
toString
()
!=
lp
)
q
"""$tname+${", "}"""
else
q
"""$tname"""
case
_
=>
if
(
field
.
toString
()
!=
lp
)
q
"""$field+${", "}"""
else
q
"""$field"""
}
}
}
}
// extract 'containsCtorParams' parameters of annotation
val
containsCtorParams
=
c
.
prefix
.
tree
match
{
case
Apply
(
_
,
q
"isContainsCtorParams = $foo"
::
Nil
)
=>
foo
match
{
case
Literal
(
Constant
(
isContainsCtorParams
:
Boolean
))
=>
isContainsCtorParams
case
_
=>
c
.
warning
(
c
.
enclosingPosition
,
"The value provided for 'isContainsCtorParams' must be a constant (true or false) and not an expression (e.g. 2 == 1 + 1). isContainsCtorParams set to false."
)
false
private
def
toStringTemplateImpl
(
c
:
whitebox.Context
)(
argument
:
Argument
,
annotateeClass
:
c.universe.ClassDef
)
:
c.universe.Tree
=
{
import
c.universe._
// For a given class definition, separate the components of the class
val
(
className
,
annotteeClassParams
,
annotteeClassDefinitions
)
=
{
annotateeClass
match
{
case
q
"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }"
=>
(
tpname
,
paramss
,
stats
)
}
}
// Check the type of the class, whether it already contains its own toString
val
annotteeClassFieldDefinitions
=
annotteeClassDefinitions
.
asInstanceOf
[
List
[
Tree
]].
filter
(
p
=>
p
match
{
case
_:
ValDef
=>
true
case
mem
:
MemberDef
=>
c
.
info
(
c
.
enclosingPosition
,
s
"MemberDef: ${mem.toString}"
,
true
)
if
(
mem
.
toString
().
startsWith
(
"override def toString"
))
{
c
.
abort
(
mem
.
pos
,
"'toString' method has already defined, please remove it or not use'@toString'"
)
}
false
case
m
:
DefDef
=>
false
case
_
=>
false
})
// For the parameters of a given constructor, separate the parameter components and extract the constructor parameters containing val and var
val
ctorParams
=
annotteeClassParams
.
asInstanceOf
[
List
[
List
[
Tree
]]].
flatten
.
map
{
case
tree
@q
"$mods val $tname: $tpt = $expr"
=>
tree
case
tree
@q
"$mods var $tname: $tpt = $expr"
=>
tree
}
c
.
info
(
c
.
enclosingPosition
,
s
"className: $className, ctorParams: ${ctorParams.toString()}"
,
force
=
true
)
c
.
info
(
c
.
enclosingPosition
,
s
"className: $className, fields: ${annotteeClassFieldDefinitions.toString()}"
,
force
=
true
)
val
member
=
if
(
argument
.
containsCtorParams
)
ctorParams
++
annotteeClassFieldDefinitions
else
annotteeClassFieldDefinitions
val
lastParam
=
member
.
lastOption
.
map
{
case
v
:
ValDef
=>
v
.
name
.
toTermName
.
decodedName
.
toString
case
c
=>
c
.
toString
}
val
paramsWithName
=
member
.
foldLeft
(
q
"${""}"
)((
res
,
acc
)
=>
q
"$res + ${printField(c)(argument, lastParam, acc)}"
)
//scala/bug https://github.com/scala/bug/issues/3967 not be 'Foo(i=1,j=2)' in standard library
q
"""override def toString: String = ${className.toString()} + ${"("} + $paramsWithName + ${")"}"""
}
def
impl
(
c
:
whitebox.Context
)(
annottees
:
c.Expr
[
Any
]*)
:
c.Expr
[
Any
]
=
{
import
c.universe._
// extract parameters of annotation
// extract 'isVerbose' parameters of annotation
val
arg
=
c
.
prefix
.
tree
match
{
case
q
"new toString($aa, $bb, $cc)"
=>
(
c
.
eval
[
Boolean
](
c
.
Expr
(
aa
)),
c
.
eval
[
Boolean
](
c
.
Expr
(
bb
)),
c
.
eval
[
Boolean
](
c
.
Expr
(
cc
)))
case
_
=>
c
.
abort
(
c
.
enclosingPosition
,
"unexpected annotation pattern!"
)
}
val
argument
=
Argument
(
arg
.
_1
,
arg
.
_2
,
arg
.
_3
)
// Check the type of the class, which can only be defined on the ordinary class
val
annotateeClass
:
ClassDef
=
annottees
.
map
(
_
.
tree
).
toList
match
{
case
(
claz
:
ClassDef
)
::
Nil
=>
claz
case
_
=>
c
.
abort
(
c
.
enclosingPosition
,
"Unexpected annottee. Only applicable to class definitions."
)
case
_
=>
c
.
abort
(
c
.
enclosingPosition
,
"Unexpected annottee. Only applicable to class definitions."
)
}
// For a given class definition, separate the components of the class
val
(
isCase
,
className
,
annotteeClassParams
,
annotteeClassDefinitions
)
=
{
val
isCase
:
Boolean
=
{
annotateeClass
match
{
case
q
"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }"
=>
val
isCase
=
if
(
mods
.
asInstanceOf
[
Modifiers
].
hasFlag
(
Flag
.
CASE
))
{
c
.
warning
(
c
.
enclosingPosition
,
"'toString' annotation is used on 'case class'. Ignore"
)
if
(
mods
.
asInstanceOf
[
Modifiers
].
hasFlag
(
Flag
.
CASE
))
{
c
.
warning
(
c
.
enclosingPosition
,
"'toString' annotation is used on 'case class'."
)
true
}
else
false
(
isCase
,
tpname
,
paramss
,
stats
)
}
}
// Check the type of the class, whether it already contains its own toString
annotteeClassDefinitions
.
asInstanceOf
[
List
[
Tree
]].
map
{
case
v
:
ValDef
=>
true
case
m
:
MemberDef
=>
c
.
abort
(
m
.
pos
,
"'toString' method has already defined, please remove it or not use'@toString'"
)
}
// Extract the fields in the class definition
val
fields
=
annotteeClassDefinitions
.
asInstanceOf
[
List
[
Tree
]].
map
{
case
v
:
ValDef
=>
v
.
name
}
// For the parameters of a given constructor, separate the parameter components and extract the constructor parameters containing val and var
val
ctorParams
=
annotteeClassParams
.
asInstanceOf
[
List
[
List
[
Tree
]]].
flatten
.
map
{
case
tree
@
q
"$mods val $tname: $tpt = $expr"
=>
TermName
.
apply
(
tname
.
toString
())
case
tree
@
q
"$mods var $tname: $tpt = $expr"
=>
TermName
.
apply
(
tname
.
toString
())
}
val
member
=
if
(
containsCtorParams
)
ctorParams
++
fields
else
fields
// Generate toString method TODO refactor code
val
method
=
q
"""
override def toString(): String = ($member).toString.replace("List", ${className.toString()})
"""
c
.
info
(
c
.
enclosingPosition
,
s
"impl argument: $argument, isCase: $isCase"
,
true
)
val
resMethod
=
toStringTemplateImpl
(
c
)(
argument
,
annotateeClass
)
val
resTree
=
annotateeClass
match
{
case
q
"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }"
=>
if
(!
isCase
)
{
q
"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..${stats.toList.:+(method)} }"
}
else
{
q
"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }"
}
q
"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..${stats.toList.:+(resMethod)} }"
}
// Print the ast
c
.
info
(
c
.
enclosingPosition
,
"\n###### Expanded macro ######\n"
+
resTree
.
toString
()
+
"\n###### Expanded macro ######\n"
,
force
=
isV
erbose
force
=
argument
.
v
erbose
)
c
.
Expr
[
Any
](
resTree
)
}
...
...
src/test/scala/io/github/liguobin/ToStringTest.scala
浏览文件 @
258cfa46
package
io.github.liguobin
import
org.scalatest.
{
FlatSpec
,
Matchers
}
import
org.scalatest.
{
FlatSpec
,
Matchers
}
/**
*
...
...
@@ -10,8 +10,8 @@ import org.scalatest.{ FlatSpec, Matchers }
*/
class
ToStringTest
extends
FlatSpec
with
Matchers
{
"toString1"
should
"
contains ContainsCtorParam
s"
in
{
@toString
"toString1"
should
"
not contains constructors parameter
s"
in
{
@toString
(
false
,
false
,
false
)
class
TestClass
(
val
i
:
Int
=
0
,
var
j
:
Int
)
{
val
y
:
Int
=
0
var
z
:
String
=
"hello"
...
...
@@ -22,8 +22,32 @@ class ToStringTest extends FlatSpec with Matchers {
assert
(
s
==
"TestClass(0, hello, world)"
)
}
"toString2"
should
"contains ContainsCtorParams"
in
{
@toString
(
isContainsCtorParams
=
true
)
"toString2"
should
"contains constructors parameters"
in
{
@toString
(
true
,
true
,
true
)
class
TestClass
(
val
i
:
Int
=
0
,
var
j
:
Int
)
{
val
y
:
Int
=
0
var
z
:
String
=
"hello"
var
x
:
String
=
"world"
}
val
s
=
new
TestClass
(
1
,
2
).
toString
println
(
s
)
assert
(
s
==
"TestClass(i=1, j=2, y=0, z=hello, x=world)"
)
}
"toString3"
should
"not contains constructors parameters but with name"
in
{
@toString
(
true
,
false
,
true
)
class
TestClass
(
val
i
:
Int
=
0
,
var
j
:
Int
)
{
val
y
:
Int
=
0
var
z
:
String
=
"hello"
var
x
:
String
=
"world"
}
val
s
=
new
TestClass
(
1
,
2
).
toString
println
(
s
)
assert
(
s
==
"TestClass(y=0, z=hello, x=world)"
)
}
"toString4"
should
"contains constructors parameters but without name"
in
{
@toString
(
true
,
true
,
false
)
class
TestClass
(
val
i
:
Int
=
0
,
var
j
:
Int
)
{
val
y
:
Int
=
0
var
z
:
String
=
"hello"
...
...
@@ -34,25 +58,135 @@ class ToStringTest extends FlatSpec with Matchers {
assert
(
s
==
"TestClass(1, 2, 0, hello, world)"
)
}
// "toString3" should "failed when toString already defined" in {
// @toString(isContainsCtorParams = true)
// class TestClass(val i: Int = 0, var j: Int) {
// val y: Int = 0
// var z: String = "hello"
// var x: String = "world"
//
// override def toString = s"TestClass($y, $z, $x, $i, $j)"
// }
// val s = new TestClass(1, 2).toString
// println(s)
// assert(s == "TestClass(1, 2, 0, hello world macro, hello world)")
// }
"toString4"
should
"case class"
in
{
@toString
(
isContainsCtorParams
=
true
)
case
class
TestClass
(
val
i
:
Int
=
0
,
var
j
:
Int
)
"toString5"
should
"case class without name"
in
{
@toString
(
true
,
false
,
false
)
case
class
TestClass
(
i
:
Int
=
0
,
var
j
:
Int
)
{
val
y
:
Int
=
0
var
z
:
String
=
"hello"
var
x
:
String
=
"world"
}
val
s
=
TestClass
(
1
,
2
).
toString
println
(
s
)
assert
(
s
==
"TestClass(0, hello, world)"
)
}
"toString6"
should
"case class with name"
in
{
@toString
(
true
,
false
,
true
)
case
class
TestClass
(
i
:
Int
=
0
,
var
j
:
Int
)
{
val
y
:
Int
=
0
var
z
:
String
=
"hello"
var
x
:
String
=
"world"
}
case
class
TestClass2
(
i
:
Int
=
0
,
var
j
:
Int
)
// No method body, use default toString
val
s
=
TestClass
(
1
,
2
).
toString
val
s2
=
TestClass2
(
1
,
2
).
toString
println
(
s
)
assert
(
s
==
"TestClass(1,2)"
)
println
(
s2
)
assert
(
s
==
"TestClass(y=0, z=hello, x=world)"
)
assert
(
s2
==
"TestClass2(1,2)"
)
}
"toString7"
should
"case class with name"
in
{
@toString
(
true
,
true
,
true
)
case
class
TestClass
(
i
:
Int
=
0
,
var
j
:
Int
)
{
val
y
:
Int
=
0
var
z
:
String
=
"hello"
var
x
:
String
=
"world"
}
val
s
=
TestClass
(
1
,
2
).
toString
println
(
s
)
assert
(
s
==
"TestClass(i=1, j=2, y=0, z=hello, x=world)"
)
}
"toString8"
should
"case class with name and itself"
in
{
@toString
(
true
,
true
,
true
)
case
class
TestClass
(
i
:
Int
=
0
,
var
j
:
Int
,
k
:
TestClass
)
{
val
y
:
Int
=
0
var
z
:
String
=
"hello"
var
x
:
String
=
"world"
}
val
s
=
TestClass
(
1
,
2
,
TestClass
(
1
,
2
,
null
)).
toString
println
(
s
)
assert
(
s
==
"TestClass(i=1, j=2, k=TestClass(i=1, j=2, k=null, y=0, z=hello, x=world), y=0, z=hello, x=world)"
)
}
"toString9"
should
"case class with name and itself2"
in
{
@toString
(
true
,
true
,
true
)
case
class
TestClass
(
i
:
Int
=
0
,
var
j
:
Int
)
{
val
y
:
Int
=
0
var
z
:
String
=
"hello"
var
x
:
String
=
"world"
val
t
:
TestClass
=
null
// if not null, will error
}
val
s
=
TestClass
(
1
,
2
).
toString
println
(
s
)
assert
(
s
==
"TestClass(i=1, j=2, y=0, z=hello, x=world, t=null)"
)
}
"toString10"
should
"case class with name and itself3"
in
{
@toString
(
true
,
true
,
true
)
case
class
TestClass
(
i
:
Int
=
0
,
var
j
:
Int
,
k
:
TestClass
)
{
val
y
:
Int
=
0
var
z
:
String
=
"hello"
var
x
:
String
=
"world"
}
val
s
=
TestClass
(
1
,
2
,
TestClass
(
1
,
2
,
TestClass
(
1
,
3
,
null
))).
toString
println
(
s
)
assert
(
s
==
"TestClass(i=1, j=2, k=TestClass(i=1, j=2, k=TestClass(i=1, j=3, k=null, y=0, z=hello, x=world), y=0, z=hello, x=world), y=0, z=hello, x=world)"
)
}
"toString11"
should
"class with name and code block contains method"
in
{
@toString
(
true
,
true
,
true
)
class
TestClass
(
i
:
Int
=
0
,
var
j
:
Int
)
{
def
helloWorld
:
String
=
i
+
""
println
(
helloWorld
)
// override def toString = s"TestClass(j=$j, i=$i)" // scalac override def toString = StringContext("TestClass(j=", ", i=", ")").s(j, i)
}
val
s
=
new
TestClass
(
1
,
2
).
toString
println
(
s
)
assert
(
s
==
"TestClass(i=1, j=2)"
)
}
"toString12"
should
"class with name and not code block"
in
{
@toString
(
true
,
false
,
true
)
class
TestClass
(
i
:
Int
=
0
,
var
j
:
Int
)
val
s
=
new
TestClass
(
1
,
2
).
toString
println
(
s
)
assert
(
s
==
"TestClass()"
)
@toString
(
true
,
true
,
true
)
class
TestClass2
(
i
:
Int
=
1
,
var
j
:
Int
=
2
)
val
s2
=
new
TestClass2
(
1
,
2
).
toString
println
(
s2
)
assert
(
s2
==
"TestClass2(i=1, j=2)"
)
@toString
(
true
,
true
,
false
)
class
TestClass3
(
i
:
Int
=
1
,
var
j
:
Int
=
3
)
val
s3
=
new
TestClass3
(
1
,
2
).
toString
println
(
s3
)
assert
(
s3
==
"TestClass3(1, 2)"
)
}
"toString13"
should
"case class with name and not code block"
in
{
@toString
(
true
,
true
,
false
)
case
class
TestClass
(
i
:
Int
=
1
,
var
j
:
Int
=
3
)
val
s
=
TestClass
(
1
,
2
).
toString
println
(
s
)
assert
(
s
==
"TestClass(1, 2)"
)
@toString
(
true
,
false
,
false
)
case
class
TestClass2
(
i
:
Int
=
1
,
var
j
:
Int
=
3
)
val
s2
=
TestClass2
(
1
,
2
).
toString
println
(
s2
)
assert
(
s2
==
"TestClass2()"
)
@toString
(
true
,
true
,
true
)
case
class
TestClass3
(
i
:
Int
=
1
,
var
j
:
Int
=
3
)
val
s3
=
TestClass3
(
1
,
2
).
toString
println
(
s3
)
assert
(
s3
==
"TestClass3(i=1, j=2)"
)
}
}
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录