Skip to content

Commit de75713

Browse files
authored
Merge pull request #9201 from dotty-staging/structural-new-2
2 parents 3570a42 + 12c7583 commit de75713

File tree

4 files changed

+96
-31
lines changed

4 files changed

+96
-31
lines changed

compiler/src/dotty/tools/dotc/core/TypeOps.scala

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -343,17 +343,20 @@ object TypeOps:
343343
).suchThat(decl.matches(_))
344344
val inheritedInfo = inherited.info
345345
val isPolyFunctionApply = decl.name == nme.apply && (parent <:< defn.PolyFunctionType)
346-
if isPolyFunctionApply
347-
|| inheritedInfo.exists
348-
&& !decl.isClass
349-
&& decl.info.widenExpr <:< inheritedInfo.widenExpr
350-
&& !(inheritedInfo.widenExpr <:< decl.info.widenExpr)
351-
then
352-
val r = RefinedType(parent, decl.name, decl.info)
353-
typr.println(i"add ref $parent $decl --> " + r)
354-
r
355-
else
356-
parent
346+
val needsRefinement =
347+
isPolyFunctionApply
348+
|| !decl.isClass
349+
&& {
350+
if inheritedInfo.exists then
351+
decl.info.widenExpr <:< inheritedInfo.widenExpr
352+
&& !(inheritedInfo.widenExpr <:< decl.info.widenExpr)
353+
else
354+
parent.derivesFrom(defn.SelectableClass)
355+
}
356+
if needsRefinement then
357+
RefinedType(parent, decl.name, decl.info)
358+
.reporting(i"add ref $parent $decl --> " + result, typr)
359+
else parent
357360
}
358361

359362
def close(tp: Type) = RecType.closeOver { rt =>

docs/docs/reference/changed-features/structural-types.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,43 @@ New instances of `Selectable` can be defined to support means of
123123
access other than Java reflection, which would enable usages such as
124124
the database access example given at the beginning of this document.
125125

126+
## Local Selectable Instances
127+
128+
Local and anonymous classes that extend `Selectable` get more refined types
129+
than other classes. Here is an example:
130+
```scala
131+
class Vehicle extends reflect.Selectable {
132+
val wheels: Int
133+
}
134+
val i3 = new Vehicle { // i3: Vehicle { val range: Int }
135+
val wheels = 4
136+
val range = 240
137+
}
138+
i3.range
139+
```
140+
The type of `i3` in this example is `Vehicle { val range: Int }`. Hence,
141+
`i3.range` is well-formed. Since the base class `Vehicle` does not define a `range` field or method, we need structural dispatch to access the `range` field of the anonymous class that initializes `id3`. Structural dispatch
142+
is implemented by the base trait `reflect.Selectable` of `Vehicle`, which
143+
defines the necessary `selectDynamic` member.
144+
145+
`Vehicle` could also extend some other subclass of `scala.Selectable` that implements `selectDynamic` and `applyDynamic` differently. But if it does not extend a `Selectable` at all, the code would no longer typecheck:
146+
```scala
147+
class Vehicle {
148+
val wheels: Int
149+
}
150+
val i3 = new Vehicle { // i3: Vehicle
151+
val wheels = 4
152+
val range = 240
153+
}
154+
i3.range: // error: range is not a member of `Vehicle`
155+
```
156+
The difference is that the type of an anonymous class that does not extend `Selectable` is just formed from the parent type(s) of the class, without
157+
adding any refinements. Hence, `i3` now has just type `Vehicle` and the selection `i3.range` gives a "member not found" error.
158+
159+
Note that in Scala 2 all local and anonymous classes could produce values with refined types. But
160+
members defined by such refinements could be selected only with the language import
161+
`reflectiveCalls`.
162+
126163
## Relation with `scala.Dynamic`
127164

128165
There are clearly some connections with `scala.Dynamic` here, since
Lines changed: 34 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,43 @@
11
package scala.reflect
22

3-
class Selectable(val receiver: Any) extends AnyVal with scala.Selectable {
4-
def selectDynamic(name: String): Any = {
5-
val rcls = receiver.getClass
6-
try {
3+
/** A class that implements structural selections using Java reflection.
4+
* It can be used as a supertrait of a class or be made available
5+
* as an implicit conversion via `reflectiveSelectable`.
6+
*/
7+
trait Selectable extends scala.Selectable:
8+
9+
/** The value from which structural members are selected.
10+
* By default this is the Selectable instance itself, but it can
11+
* be overridden.
12+
*/
13+
protected def selectedValue: Any = this
14+
15+
/** Select member with given name */
16+
def selectDynamic(name: String): Any =
17+
val rcls = selectedValue.getClass
18+
try
719
val fld = rcls.getField(name)
820
ensureAccessible(fld)
9-
fld.get(receiver)
10-
}
11-
catch {
12-
case ex: NoSuchFieldException =>
13-
applyDynamic(name)()
14-
}
15-
}
21+
fld.get(selectedValue)
22+
catch case ex: NoSuchFieldException =>
23+
applyDynamic(name)()
1624

17-
def applyDynamic(name: String, paramTypes: ClassTag[_]*)(args: Any*): Any = {
18-
val rcls = receiver.getClass
25+
/** Select method and apply to arguments.
26+
* @param name The name of the selected method
27+
* @param paramTypes The class tags of the selected method's formal parameter types
28+
* @param args The arguments to pass to the selected method
29+
*/
30+
def applyDynamic(name: String, paramTypes: ClassTag[_]*)(args: Any*): Any =
31+
val rcls = selectedValue.getClass
1932
val paramClasses = paramTypes.map(_.runtimeClass)
2033
val mth = rcls.getMethod(name, paramClasses: _*)
2134
ensureAccessible(mth)
22-
mth.invoke(receiver, args.asInstanceOf[Seq[AnyRef]]: _*)
23-
}
24-
}
35+
mth.invoke(selectedValue, args.asInstanceOf[Seq[AnyRef]]: _*)
36+
37+
object Selectable:
2538

26-
object Selectable {
27-
implicit def reflectiveSelectable(receiver: Any): Selectable =
28-
new Selectable(receiver)
29-
}
39+
/** An implicit conversion that turns a value into a Selectable
40+
* such that structural selections are performed on that value.
41+
*/
42+
implicit def reflectiveSelectable(x: Any): Selectable =
43+
new Selectable { override val selectedValue = x }

tests/run/selectable-new.scala

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
@main def Test =
2+
val x =
3+
class C extends reflect.Selectable:
4+
def name: String = "hello"
5+
new C
6+
7+
val y = new reflect.Selectable:
8+
def name: String = "hello"
9+
10+
assert(x.name == "hello")
11+
assert(y.name == "hello")

0 commit comments

Comments
 (0)