8000 inference-driving macros · rssh/scala.github.com@0c70226 · GitHub
[go: up one dir, main page]

Skip to content

Commit 0c70226

Browse files
committed
inference-driving macros
1 parent 9b65fb8 commit 0c70226

File tree

7 files changed

+198
-6
lines changed

7 files changed

+198
-6
lines changed

overviews/macros/bundles.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ disqus: true
66

77
partof: macros
88
num: 6
9-
outof: 6
9+
outof: 7
1010
---
1111
<span class="label important" style="float: right;">MACRO PARADISE</span>
1212

overviews/macros/inference.md

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
---
2+
layout: overview-large
3+
title: Inference-Driving Macros
4+
5+
disqus: true
6+
7+
partof: macros
8+
num: 7
9+
outof: 7
10+
---
11+
<span class="label important" style="float: right;">MACRO PARADISE</span>
12+
13+
**Eugene Burmako**
14+
15+
Inference-driving macros are pre-release features included in so-called macro paradise, an experimental branch in the official Scala repository. Follow the instructions at the ["Macro Paradise"](/overviews/macros/paradise.html) page to download and use our nightly builds.
16+
17+
WARNING! This is the most experimental of all the experimental features in paradise. Not only the details of the API are unclear,
18+
but I'm not sure whether the current approach is the right one, unlike, for example, in type macros, where things look very solid.
19+
Your feedback is even more welcome than ever. Hint: if you scroll down, you can leave comments directly on this page.
20+
21+
## A motivating example
22+
23+
The use case, which gave birth to inference-driving macros, is provided by Miles Sabin and his [shapeless](https://github.com/milessabin/shapeless) library. Miles has defined the `Iso` trait, which represents isomorphisms between types.
24+
25+
trait Iso[T, U] {
26+
def to(t : T) : U
27+
def from(u : U) : T
28+
}
29+
30+
Currently instances of `Iso` are defined manually and then published as implicit values. Methods, which want to make use of
31+
defined isomorphisms, declare implicit parameters of type `Iso`, which then get filled in during implicit search.
32+
33+
def foo[C](c: C)(implicit iso: Iso[C, L]): L = iso.from(c)
34+
35+
case class Foo(i: Int, s: String, b: Boolean)
36+
implicit val fooIsoTuple = Iso.tuple(Foo.apply _, Foo.unapply _)
37+
38+
val tp = foo(Foo(23, "foo", true))
39+
tp : (Int, String, Boolean)
40+
tp == (23, "foo", true)
41+
42+
As we can see, the isomorphism between a case class and a tuple is trivial. The compiler already generates the necessary methods,
43+
and we just have to make use of them. Unfortunately in Scala 2.10.0 it's impossible to simplify this even further - for every case class
44+
you have manually define an implicit `Iso` instance.
45+
46+
Macros have proven to be quite the boilerplate scrapers, but unfortunately they can't help here in their 2.10.0 form. The problem
47+
is not just in [SI-5923](https://issues.scala-lang.org/browse/SI-5923), which has already been fixed in master for a few weeks
48+
(note to self: backport this fix to 2.10.1).
49+
50+
The real showstopper is the fact that when typechecking applications of methods like `foo`, scalac has to infer the type argument `L`,
51+
which it has no clue about (and that's no wonder, since this is domain-specific knowledge). As a result, even if you define an implicit
52+
macro, which synthesizes `Iso[C, L]`, scalac will helpfully infer `L` as `Nothing` before expanding the macro and then everything crumbles.
53+
54+
## Internals of type inference
55+
56+
From what I learned about this over a few days, type inference in Scala is performed by the following two methods
57+
in `scala/tools/nsc/typechecker/Infer.scala`: [`inferExprInstance`](https://github.com/scalamacros/kepler/blob/d7b59f452f5fa35df48a5e0385f579c98ebf3555/src/compiler/scala/tools/nsc/typechecker/Infer.scala#L1123) and
58+
[`inferMethodInstance`](https://github.com/scalamacros/kepler/blob/d7b59f452f5fa35df48a5e0385f579c98ebf3555/src/compiler/scala/tools/nsc/typechecker/Infer.scala#L1173).
59+
So far I have nothing to say here other than showing `-Yinfer-debug` logs of various code snippets, which involve type inference.
60+
61+
def foo[T1](x: T1) = ???
62+
foo(2)
63+
64+
[solve types] solving for T1 in ?T1
65+
[infer method] solving for T1 in (x: T1)Nothing based on (Int)Nothing (solved: T1=Int)
66+
67+
def bar[T2] = ???
68+
bar
69+
70+
[solve types] solving for T2 in ?T2
71+
inferExprInstance {
72+
tree C.this.bar[T2]
73+
tree.tpe Nothing
74+
tparams type T2
75+
pt ?
76+
targs Nothing
77+
tvars =?Nothing
78+
}
79+
80+
class Baz[T]
81+
implicit val ibaz = new Baz[Int]
82+
def baz[T3](implicit ibaz: Baz[T3]) = ???
83+
baz
84+
85+
[solve types] solving for T3 in ?T3
86+
inferExprInstance {
87+
tree C.this.baz[T3]
88+
tree.tpe (implicit ibaz: C.this.Baz[T3])Nothing
89+
tparams type T3
90+
pt ?
91+
targs Nothing
92+
tvars =?Nothing
93+
}
94+
inferExprInstance/AdjustedTypeArgs {
95+
okParams
96+
okArgs
97+
leftUndet type T3
98+
}
99+
[infer implicit] C.this.baz[T3] with pt=C.this.Baz[T3] in class C
100+
[search] C.this.baz[T3] with pt=C.this.Baz[T3] in class C, eligible:
101+
ibaz: => C.this.Baz[Int]
102+
[search] considering T3 (pt contains ?T3) trying C.this.Baz[Int] against pt=C.this.Baz[T3]
103+
[solve types] solving for T3 in ?T3
104+
[success] found SearchResult(C.this.ibaz, TreeTypeSubstituter(List(type T3),List(Int))) for pt C.this.Baz[=?Int]
105+
[infer implicit] inferred SearchResult(C.this.ibaz, TreeTypeSubstituter(List(type T3),List(Int)))
106+
107+
class Qwe[T]
108+
implicit def idef[T4] = new Qwe[T4]
109+
def qwe[T4](implicit xs: Qwe[T4]) = ???
110+
qwe
111+
112+
[solve types] solving for T4 in ?T4
113+
inferExprInstance {
114+
tree C.this.qwe[T4]
115+
tree.tpe (implicit xs: C.this.Qwe[T4])Nothing
116+
tparams type T4
117+
pt ?
118+
targs Nothing
119+
tvars =?Nothing
120+
}
121+
inferExprInstance/AdjustedTypeArgs {
122+
okParams
123+
okArgs
124+
leftUndet type T4
125+
}
126+
[infer implicit] C.this.qwe[T4] with pt=C.this.Qwe[T4] in class C
127+
[search] C.this.qwe[T4] with pt=C.this.Qwe[T4] in class C, eligible:
128+
idef: [T4]=> C.this.Qwe[T4]
129+
[solve types] solving for T4 in ?T4
130+
inferExprInstance {
131+
tree C.this.idef[T4]
132+
tree.tpe C.this.Qwe[T4]
133+
tparams type T4
134+
pt C.this.Qwe[?]
135+
targs Nothing
136+
tvars =?Nothing
137+
}
138+
[search] considering T4 (pt contains ?T4) trying C.this.Qwe[Nothing] against pt=C.this.Qwe[T4]
139+
[solve types] solving for T4 in ?T4
140+
[success] found SearchResult(C.this.idef[Nothing], ) for pt C.this.Qwe[=?Nothing]
141+
[infer implicit] inferred SearchResult(C.this.idef[Nothing], )
142+
[solve types] solving for T4 in ?T4
143+
[infer method] solving for T4 in (implicit xs: C.this.Qwe[T4])Nothing based on (C.this.Qwe[Nothing])Nothing (solved: T4=Nothing)
144+
145+
## Proposed solution
146+
147+
Using the infrastructure provided by [macro bundles](/overviews/macros/bundles.html) (in principle, we could achieve exactly the same
148+
thing using the traditional way of defining macro implementations, but that's not important here), we introduce the `onInfer` callback,
149+
which macros can define to be called by the compiler from `inferExprInstance` and `inferMethodInstance`. The callback takes a single
150+
parameter of type `c.TypeInferenceContext`, which encapsulates the arguments of `inferXXX` methods and provides methods to infer
151+
unknown type parameters.
152+
153+
trait Macro {
154+
val c: Context
155+
def onInfer(tc: c.TypeInferenceContext): Unit = tc.inferDefault()
156+
}
157+
158+
type TypeInferenceContext <: TypeInferenceContextApi
159+
trait TypeInferenceContextApi {
160+
def tree: Tree
161+
def unknowns: List[Symbol]
162+
def expectedType: Type
163+
def actualType: Type
164+
165+
// TODO: can we get rid of this couple?
166+
def keepNothings: Boolean
167+
def useWeaklyCompatible: Boolean
168+
169+
def infer(sym: Symbol, tpe: Type): Unit
170+
171+
// TODO: would be lovely to have a different signature here, namely:
172+
// def inferDefault(sym: Symbol): Type
173+
// so that the macro can partially rely on out-of-the-box inference
174+
// and infer the rest afterwards
175+
def inferDefault(): Unit
176+
}
177+
178+
With this infrastructure in place, we can write the `materializeIso` macro, which obviates the need for manual declaration of implicits.
179+
The full source code is available in [paradise/macros](https://github.com/scalamacros/kepler/blob/paradise/macros/test/files/run/macro-programmable-type-inference/Impls_Macros_1.scala), here's the relevant excerpt:
180+
181+
override def onInfer(tic: c.TypeInferenceContext): Unit = {
182+
val C = tic.unknowns(0)
183+
val L = tic.unknowns(1)
184+
import c.universe._
185+
import definitions._
186+
val TypeRef(_, _, caseClassTpe :: _ :: Nil) = tic.expectedType // Iso[Test.Foo,?]
187+
tic.infer(C, caseClassTpe)
188+
val fields = caseClassTpe.typeSymbol.typeSignature.declarations.toList.collect{ case x: TermSymbol if x.isVal && x.isCaseAccessor => x }
189+
val core = (TupleClass(fields.length) orElse UnitClass).asType.toType
190+
val tequiv = if (fields.length == 0) core else appliedType(core, fields map (_.typeSignature))
191+
tic.infer(L, tequiv)
192+
}

overviews/macros/overview.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ disqus: true
66

77
partof: macros
88
num: 1
9-
outof: 6
9+
outof: 7
1010
---
1111
<span class="label warning" style="float: right;">EXPERIMENTAL</span>
1212

overviews/macros/paradise.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ disqus: true
66

77
partof: macros
88
num: 2
9-
outof: 6
9+
outof: 7
1010
---
1111
<span class="label important" style="float: right;">MACRO PARADISE</span>
1212

overviews/macros/quasiquotes.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ disqus: true
66

77
partof: macros
88
num: 4
9-
outof: 6
9+
outof: 7
1010
---
1111
<a href="/overviews/macros/paradise.html"><span class="label important" style="float: right;">MACRO PARADISE</span></a>
1212

overviews/macros/typemacros.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ disqus: true
66

77
partof: macros
88
num: 3
9-
outof: 6
9+
outof: 7
1010
---
1111
<a href="/overviews/macros/paradise.html"><span class="label important" style="float: right;">MACRO PARADISE</span></a>
1212

overviews/macros/untypedmacros.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ disqus: true
66

77
partof: macros
88
num: 5
9-
outof: 6
9+
outof: 7
1010
---
1111
<a href="/overviews/macros/paradise.html"><span class="label important" style="float: right;">MACRO PARADISE</span></a>
1212

0 commit comments

Comments
 (0)
0