Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
梦境迷离
Scala Macro Tools
提交
d265f5bd
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,发现更多精彩内容 >>
未验证
提交
d265f5bd
编写于
7月 10, 2021
作者:
梦境迷离
提交者:
GitHub
7月 10, 2021
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
support Currying (#56)
上级
3575a35e
变更
11
显示空白变更内容
内联
并排
Showing
11 changed file
with
133 addition
and
43 deletion
+133
-43
.idea/artifacts/intellij_plugin.xml
.idea/artifacts/intellij_plugin.xml
+0
-12
README.md
README.md
+3
-2
README_EN.md
README_EN.md
+3
-1
build.sbt
build.sbt
+2
-2
src/main/scala/io/github/dreamylost/macros/MacroCommon.scala
src/main/scala/io/github/dreamylost/macros/MacroCommon.scala
+48
-0
src/main/scala/io/github/dreamylost/macros/applyMacro.scala
src/main/scala/io/github/dreamylost/macros/applyMacro.scala
+6
-5
src/main/scala/io/github/dreamylost/macros/builderMacro.scala
...main/scala/io/github/dreamylost/macros/builderMacro.scala
+6
-7
src/main/scala/io/github/dreamylost/macros/constructorMacro.scala
.../scala/io/github/dreamylost/macros/constructorMacro.scala
+30
-14
src/test/scala/io/github/dreamylost/ApplyTest.scala
src/test/scala/io/github/dreamylost/ApplyTest.scala
+5
-0
src/test/scala/io/github/dreamylost/BuilderTest.scala
src/test/scala/io/github/dreamylost/BuilderTest.scala
+11
-0
src/test/scala/io/github/dreamylost/ConstructorTest.scala
src/test/scala/io/github/dreamylost/ConstructorTest.scala
+19
-0
未找到文件。
.idea/artifacts/intellij_plugin.xml
已删除
100644 → 0
浏览文件 @
3575a35e
<component
name=
"ArtifactManager"
>
<artifact
name=
"intellij-plugin"
>
<output-path>
$PROJECT_DIR$/intellij-plugin/target/plugin/Scala-Macro-Tools
</output-path>
<root
id=
"root"
>
<element
id=
"directory"
name=
"lib"
>
<element
id=
"archive"
name=
"intellij-plugin.jar"
>
<element
id=
"module-output"
name=
"scala-macro-tools-intellij-plugin"
/>
</element>
</element>
</root>
</artifact>
</component>
\ No newline at end of file
README.md
浏览文件 @
d265f5bd
...
...
@@ -27,7 +27,6 @@
## 已知问题
-
不支持柯里化。
-
不支持泛型。
-
`@constructor`
与
`@toString`
同时使用,必须放最后。
-
IDEA对宏的支持不是很好,所以会出现标红,不过编译没问题,调用结果也符合预期。
...
...
@@ -90,6 +89,7 @@ Json.fromJson[Person](json)
-
说明
-
支持普通类和样例类。
-
仅支持对主构造函数使用。
-
如果该类没有伴生对象,将生成一个伴生对象来存储
`builder`
方法和类。
-
示例
...
...
@@ -215,12 +215,13 @@ println(B2(1, 2, None, None)) //0.1.0,不携带字段的默认值到apply参
## @constructor
`@constructor`
注解用于为普通类生成辅助构造函数。
`@constructor`
注解用于为普通类生成辅助构造函数。
仅当类有内部字段时可用。
-
说明
-
`verbose`
指定是否开启详细编译日志。可选,默认
`false`
。
-
`excludeFields`
指定是否需要排除不需要用于构造函数的
`var`
字段。可选,默认空(所有class内部的
`var`
字段都将作为构造函数的入参)。
-
仅支持在
`class`
上使用。
-
主构造函数存在柯里化时,内部字段被放置在柯里化的第一个括号块中。(生成的仍然是柯里化的辅助构造)
-
示例
...
...
README_EN.md
浏览文件 @
d265f5bd
...
...
@@ -92,6 +92,7 @@ The `@builder` used to generate builder pattern for Scala classes.
-
Note
-
Support
`case class`
/
`class`
.
-
Only support for
**primary constructor**
.
-
If there is no companion object, one will be generated to store the
`builder`
class and method.
-
Example
...
...
@@ -218,12 +219,13 @@ println(B2(1, 2))
## @constructor
The
`@constructor`
used to generate secondary constructor method for classes.
The
`@constructor`
used to generate secondary constructor method for classes
, only when it has inner fields
.
-
Note
-
`verbose`
Whether to enable detailed log.
-
`excludeFields`
Whether to exclude the specified
`var`
fields, default is
`Nil`
.
-
Only support
`class`
.
-
The inner fields are placed in the first bracket block if constructor is currying.
-
Example
...
...
build.sbt
浏览文件 @
d265f5bd
...
...
@@ -48,7 +48,7 @@ lazy val root = (project in file("."))
)
).
settings
(
Publishing
.
publishSettings
).
settings
(
paradise
())
lazy
val
`
examples213` = (project in file("examples2-13")).settings(scalaVersion := scala213)
lazy
val
`
examples2
-
13
` = (project in file("examples2-13")).settings(scalaVersion := scala213)
.settings(libraryDependencies ++= Seq(
"io.github.jxnu-liguobin" %% "scala-macro-tools" % (ThisBuild / version).value,
)).settings(
...
...
@@ -56,7 +56,7 @@ lazy val `examples213` = (project in file("examples2-13")).settings(scalaVersion
Compile / scalacOptions += "-Ymacro-annotations"
)
lazy val `examples212` = (project in file("examples2-12")).settings(scalaVersion := scala212)
lazy val `examples2
-
12
` = (project in file("examples2-12")).settings(scalaVersion := scala212)
.settings(libraryDependencies ++= Seq(
"io.github.jxnu-liguobin" %% "scala-macro-tools" % (ThisBuild / version).value,
)).settings(
...
...
src/main/scala/io/github/dreamylost/macros/MacroCommon.scala
浏览文件 @
d265f5bd
...
...
@@ -236,4 +236,52 @@ trait MacroCommon {
case
_
=>
false
})
}
/**
* We generate constructor with currying, and we have to deal with the first layer of currying alone.
*
* @param typeName
* @param fieldss
* @param isCase
* @return A constructor with currying, it not contains tpt, provide for calling method.
* @example [[new TestClass12(i)(j)(k)(t)]]
*/
def
getConstructorWithCurrying
(
c
:
whitebox.Context
)(
typeName
:
c.TypeName
,
fieldss
:
List
[
List
[
c.Tree
]],
isCase
:
Boolean
)
:
c.Tree
=
{
import
c.universe._
val
allFieldsTermName
=
fieldss
.
map
(
f
=>
f
.
map
(
ff
=>
fieldTermName
(
c
)(
ff
)))
// not currying
val
constructor
=
if
(
fieldss
.
isEmpty
||
fieldss
.
size
==
1
)
{
q
"${if (isCase) q"
$
{
typeName
.
toTermName
}(..
$
{
allFieldsTermName
.
flatten
})
" else q"
new
$typeName
(..
$
{
allFieldsTermName
.
flatten
})
"}"
}
else
{
// currying
val
first
=
allFieldsTermName
.
head
if
(
isCase
)
q
"${typeName.toTermName}(...$first)(...${allFieldsTermName.tail})"
else
q
"new $typeName(...$first)(...${allFieldsTermName.tail})"
}
c
.
info
(
c
.
enclosingPosition
,
s
"getConstructorWithCurrying constructor: $constructor, paramss: $fieldss"
,
force
=
true
)
constructor
}
/**
* We generate apply method with currying, and we have to deal with the first layer of currying alone.
*
* @param typeName
* @param fieldss
* @return A apply method with currying.
* @example [[def apply(int: Int)(j: Int)(k: Option[String])(t: Option[Long]): B3 = new B3(int)(j)(k)(t)]]
*/
def
getApplyMethodWithCurrying
(
c
:
whitebox.Context
)(
typeName
:
c.TypeName
,
fieldss
:
List
[
List
[
c.Tree
]])
:
c.Tree
=
{
import
c.universe._
val
allFieldsTermName
=
fieldss
.
map
(
f
=>
fieldAssignExpr
(
c
)(
f
))
// not currying
val
applyMethod
=
if
(
fieldss
.
isEmpty
||
fieldss
.
size
==
1
)
{
q
"def apply(..${allFieldsTermName.flatten}): $typeName = ${getConstructorWithCurrying(c)(typeName, fieldss, isCase = false)}"
}
else
{
// currying
val
first
=
allFieldsTermName
.
head
q
"def apply(..$first)(...${allFieldsTermName.tail}): $typeName = ${getConstructorWithCurrying(c)(typeName, fieldss, isCase = false)}"
}
c
.
info
(
c
.
enclosingPosition
,
s
"getApplyWithCurrying constructor: $applyMethod, paramss: $fieldss"
,
force
=
true
)
applyMethod
}
}
src/main/scala/io/github/dreamylost/macros/applyMacro.scala
浏览文件 @
d265f5bd
...
...
@@ -23,20 +23,21 @@ object applyMacro extends MacroCommon {
val
isCase
:
Boolean
=
isCaseClass
(
c
)(
annotateeClass
)
c
.
info
(
c
.
enclosingPosition
,
s
"impl argument: $args, isCase: $isCase"
,
force
=
args
.
_1
)
if
(
isCase
)
c
.
abort
(
c
.
enclosingPosition
,
s
"Annotation is only supported on 'case class'"
)
def
modifiedDeclaration
(
classDecl
:
ClassDef
,
compDeclOpt
:
Option
[
ModuleDef
]
=
None
)
:
Any
=
{
val
(
className
,
annotteeClassParams
)
=
classDecl
match
{
case
q
"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends ..$bases { ..$body }"
=>
c
.
info
(
c
.
enclosingPosition
,
s
"modifiedDeclaration className: $tpname, paramss: $paramss"
,
force
=
args
.
_1
)
(
tpname
,
paramss
)
(
tpname
,
paramss
.
asInstanceOf
[
List
[
List
[
Tree
]]]
)
case
_
=>
c
.
abort
(
c
.
enclosingPosition
,
s
"${ErrorMessage.ONLY_CLASS} classDef: $classDecl"
)
}
c
.
info
(
c
.
enclosingPosition
,
s
"modifiedDeclaration compDeclOpt: $compDeclOpt, annotteeClassParams: $annotteeClassParams"
,
force
=
args
.
_1
)
val
fieldNames
=
annotteeClassParams
.
asInstanceOf
[
List
[
List
[
Tree
]]].
flatten
.
map
(
f
=>
fieldTermName
(
c
)(
f
))
val
cName
=
className
match
{
val
tpName
=
className
match
{
case
t
:
TypeName
=>
t
}
val
a
nnotteeClassParamsOnlyAssignExpr
=
fieldAssignExpr
(
c
)(
annotteeClassParams
.
asInstanceOf
[
List
[
List
[
Tree
]]].
flatten
)
val
compDecl
=
modifiedCompanion
(
c
)(
compDeclOpt
,
q
"""def apply(..$annotteeClassParamsOnlyAssignExpr): $className = new $className(..$fieldNames)"""
,
c
Name
)
val
a
pply
=
getApplyMethodWithCurrying
(
c
)(
tpName
,
annotteeClassParams
)
val
compDecl
=
modifiedCompanion
(
c
)(
compDeclOpt
,
apply
,
tp
Name
)
c
.
Expr
(
q
"""
$classDecl
...
...
src/main/scala/io/github/dreamylost/macros/builderMacro.scala
浏览文件 @
d265f5bd
...
...
@@ -45,12 +45,11 @@ object builderMacro extends MacroCommon {
}
}
def
builderTemplate
(
typeName
:
TypeName
,
fields
:
List
[
Tree
],
isCase
:
Boolean
)
:
Tree
=
{
val
termName
=
typeName
.
toTermName
def
builderTemplate
(
typeName
:
TypeName
,
fields
s
:
List
[
List
[
Tree
]
],
isCase
:
Boolean
)
:
Tree
=
{
val
fields
=
fieldss
.
flatten
val
builderClassName
=
getBuilderClassName
(
typeName
)
val
builderFieldMethods
=
fields
.
map
(
f
=>
fieldSetMethod
(
typeName
,
f
))
val
builderFieldDefinitions
=
fields
.
map
(
f
=>
fieldDefinition
(
f
))
val
allFieldsTermName
=
fields
.
map
(
f
=>
fieldTermName
(
c
)(
f
))
q
"""
def builder(): $builderClassName = new $builderClassName()
...
...
@@ -60,27 +59,27 @@ object builderMacro extends MacroCommon {
..$builderFieldMethods
def build(): $typeName = ${
if (isCase) q"$termName(..$allFieldsTermName)" else q"new $typeName(..$allFieldsTermName)"
}
def build(): $typeName = ${
getConstructorWithCurrying(c)(typeName, fieldss, isCase)
}
}
"""
}
// Why use Any? The dependent type need aux-pattern in scala2. Now let's get around this.
def
modifiedDeclaration
(
classDecl
:
ClassDef
,
compDeclOpt
:
Option
[
ModuleDef
]
=
None
)
:
Any
=
{
val
(
className
,
fields
)
=
classDecl
match
{
val
(
className
,
fields
s
)
=
classDecl
match
{
// @see https://scala-lang.org/files/archive/spec/2.13/05-classes-and-objects.html
case
q
"$mods class $tpname[..$tparams](...$paramss) extends ..$bases { ..$body }"
=>
c
.
info
(
c
.
enclosingPosition
,
s
"modifiedDeclaration className: $tpname, paramss: $paramss"
,
force
=
true
)
(
tpname
,
paramss
)
case
_
=>
c
.
abort
(
c
.
enclosingPosition
,
s
"${ErrorMessage.ONLY_CLASS} classDef: $classDecl"
)
}
c
.
info
(
c
.
enclosingPosition
,
s
"modifiedDeclaration compDeclOpt: $compDeclOpt, fields
: $field
s"
,
force
=
true
)
c
.
info
(
c
.
enclosingPosition
,
s
"modifiedDeclaration compDeclOpt: $compDeclOpt, fields
s: $fields
s"
,
force
=
true
)
val
cName
=
className
match
{
case
t
:
TypeName
=>
t
}
val
isCase
=
isCaseClass
(
c
)(
classDecl
)
val
builder
=
builderTemplate
(
cName
,
fields
.
asInstanceOf
[
List
[
List
[
Tree
]]].
flatten
,
isCase
)
val
builder
=
builderTemplate
(
cName
,
fields
s
,
isCase
)
val
compDecl
=
modifiedCompanion
(
c
)(
compDeclOpt
,
builder
,
cName
)
c
.
info
(
c
.
enclosingPosition
,
s
"builderTree: $builder, compDecl: $compDecl"
,
force
=
true
)
// Return both the class and companion object declarations
...
...
src/main/scala/io/github/dreamylost/macros/constructorMacro.scala
浏览文件 @
d265f5bd
...
...
@@ -35,11 +35,8 @@ object constructorMacro extends MacroCommon {
case
_
=>
c
.
abort
(
c
.
enclosingPosition
,
s
"${ErrorMessage.ONLY_CLASS} classDef: $classDecl"
)
}
// Extract the field of the primary constructor.
val
annotteeClassParamsOnlyAssignExpr
=
fieldAssignExpr
(
c
)(
annotteeClassParams
.
asInstanceOf
[
List
[
List
[
Tree
]]].
flatten
)
// Extract the internal fields of members belonging to the class, but not in primary constructor.
val
annotteeC
lassFieldDefinitions
=
getClassMemberValDef
(
c
)(
annotteeClassDefinitions
)
val
c
lassFieldDefinitions
=
getClassMemberValDef
(
c
)(
annotteeClassDefinitions
)
val
excludeFields
=
args
.
_2
/**
...
...
@@ -55,13 +52,13 @@ object constructorMacro extends MacroCommon {
}
}
val
annotteeC
lassFieldDefinitionsOnlyAssignExpr
=
getClassMemberVarDefOnlyAssignExpr
()
val
c
lassFieldDefinitionsOnlyAssignExpr
=
getClassMemberVarDefOnlyAssignExpr
()
if
(
annotteeC
lassFieldDefinitionsOnlyAssignExpr
.
isEmpty
)
{
if
(
c
lassFieldDefinitionsOnlyAssignExpr
.
isEmpty
)
{
c
.
abort
(
c
.
enclosingPosition
,
s
"Annotation is only supported on class when the internal field (declare as 'var') is nonEmpty. classDef: $classDecl"
)
}
val
annotteeClassFieldNames
=
annotteeC
lassFieldDefinitions
.
filter
(
_
match
{
val
annotteeClassFieldNames
=
c
lassFieldDefinitions
.
filter
(
_
match
{
case
q
"$mods var $tname: $tpt = $expr"
if
!
excludeFields
.
contains
(
tname
.
asInstanceOf
[
TermName
].
decodedName
.
toString
)
=>
true
case
_
=>
false
}).
map
{
...
...
@@ -70,21 +67,40 @@ object constructorMacro extends MacroCommon {
c
.
info
(
c
.
enclosingPosition
,
s
"modifiedDeclaration compDeclOpt: $compDeclOpt, annotteeClassParams: $annotteeClassParams"
,
force
=
args
.
_1
)
// not suppport currying
val
ctorFieldNames
=
annotteeClassParams
.
asInstanceOf
[
List
[
List
[
Tree
]]].
flatten
.
map
(
f
=>
fieldTermName
(
c
)(
f
))
// Extract the field of the primary constructor.
val
ctorFieldNamess
=
annotteeClassParams
.
asInstanceOf
[
List
[
List
[
Tree
]]]
val
allFieldsTermName
=
ctorFieldNamess
.
map
(
f
=>
f
.
map
(
ff
=>
fieldTermName
(
c
)(
ff
)))
def
getConstructorTemplate
()
:
c.universe.Tree
=
{
/**
* We generate this method with currying, and we have to deal with the first layer of currying alone.
*/
def
getThisMethodWithCurrying
()
:
c.Tree
=
{
// not currying
// Extract the field of the primary constructor.
val
classParamsAssignExpr
=
fieldAssignExpr
(
c
)(
ctorFieldNamess
.
flatten
)
val
applyMethod
=
if
(
ctorFieldNamess
.
isEmpty
||
ctorFieldNamess
.
size
==
1
)
{
q
"""
def this(..${
annotteeClassParamsOnlyAssignExpr ++ annotteeClassFieldDefinitionsOnlyAssignExpr})
{
this(..$
ctorFieldNames
)
def this(..${
classParamsAssignExpr ++ classFieldDefinitionsOnlyAssignExpr}) =
{
this(..$
{allFieldsTermName.flatten}
)
..${annotteeClassFieldNames.map(f => q"this.$f = $f")}
}
"""
}
else
{
// NOTE: currying constructor overload must be placed in the first bracket block.
val
allClassParamsAssignExpr
=
ctorFieldNamess
.
map
(
cc
=>
fieldAssignExpr
(
c
)(
cc
))
q
"""
def this(..${allClassParamsAssignExpr.head ++ classFieldDefinitionsOnlyAssignExpr})(...${allClassParamsAssignExpr.tail}) = {
this(..${allFieldsTermName.head})(...${allFieldsTermName.tail})
..${annotteeClassFieldNames.map(f => q"this.$f = $f")}
}
"""
}
applyMethod
}
val
resTree
=
annotateeClass
match
{
case
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.:+(get
ConstructorTemplate
())} }"
q
"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..${stats.toList.:+(get
ThisMethodWithCurrying
())} }"
}
c
.
Expr
[
Any
](
treeResultWithCompanionObject
(
c
)(
resTree
,
annottees
:
_
*
))
}
...
...
src/test/scala/io/github/dreamylost/ApplyTest.scala
浏览文件 @
d265f5bd
...
...
@@ -37,4 +37,9 @@ class ApplyTest extends AnyFlatSpec with Matchers {
// FAILED, not support currying!!
"""@apply @toString class C(int: Int, val j: Int, var k: Option[String] = None, t: Option[Long] = Some(1L))(o: Int = 1)"""
shouldNot
compile
}
"apply2"
should
"ok with currying"
in
{
@apply
@toString
class
B3
(
int
:
Int
)(
val
j
:
Int
)(
var
k
:
Option
[
String
]
=
None
)(
t
:
Option
[
Long
]
=
Some
(
1L
))
}
}
src/test/scala/io/github/dreamylost/BuilderTest.scala
浏览文件 @
d265f5bd
...
...
@@ -75,4 +75,15 @@ class BuilderTest extends AnyFlatSpec with Matchers {
println
(
ret
)
assert
(
ret
.
toString
==
"TestClass1(i=1, j=0, x=x, o=Some())"
)
}
"builder8"
should
"ok on currying"
in
{
@builder
case
class
TestClass11
(
val
i
:
Int
=
0
)(
var
j
:
Int
)(
val
k
:
Int
)
(
val
t
:
Option
[
String
])
@builder
class
TestClass12
(
val
i
:
Int
=
0
)(
var
j
:
Int
)(
val
k
:
Int
)
(
val
t
:
Option
[
String
])
}
}
src/test/scala/io/github/dreamylost/ConstructorTest.scala
浏览文件 @
d265f5bd
...
...
@@ -121,5 +121,24 @@ class ConstructorTest extends AnyFlatSpec with Matchers {
println
(
A2
(
1
,
2
,
None
,
Some
(
12L
)))
println
(
A2
.
builder
().
int
(
1
).
j
(
2
).
build
())
println
(
new
A2
(
1
,
2
,
None
,
None
,
100
))
@toString
@constructor
class
TestClass12
(
val
i
:
Int
=
0
)(
var
j
:
Int
)(
val
k
:
Int
)
{
private
val
a
:
Int
=
1
var
b
:
Int
=
1
protected
var
c
:
Int
=
_
}
println
(
new
TestClass12
(
1
,
1
,
1
)(
2
)(
3
))
/**
* def <init>(i: Int, b: Int, c: Int)(j: Int)(k: Int) = {
* <init>(i)(j)(k);
* this.b = b;
* this.c = c
* }
*/
}
}
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录