You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: _drafts/2025-09-14-a-time-for-reflection.md
+78-10Lines changed: 78 additions & 10 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -65,12 +65,7 @@ The forms listed above are the preferred expressions in user code. One should o
65
65
-`(. instance-expr (method-symbol args*))` or `(. instance-expr method-symbol args*)`
66
66
-`(. Classname-symbol (method-symbol args*))` or `(. Classname-symbol method-symbol args*)`
67
67
68
-
These forms are handled by the `HostExpr` parser. `HostExpr` is an abstract class; it will generate an instance of one its concrete subclasses. For ClojureCLR, these are:
69
-
70
-
<imgsrc="{{site.baseurl | prepend: site.url}}/assets/images/hostexpr-type-dependencies.png"alt="Graph of all types related to HostExpr" />
71
-
72
-
The JVM is simpler; it has fewer subclasses of `HostExpr`. The CLR complications arise from the need to handle properties, which do not exist on the JVM. Unfortunately, this copmplication means that the parsing logic differs enough that we need to treat them separately. We'll start with the JVM version.
73
-
68
+
These forms are handled by the `HostExpr` parser. `HostExpr` is an abstract class; it will generate an instance of one its concrete subclasses.
74
69
75
70
### HostExpr parsing on the JVM
76
71
@@ -92,15 +87,88 @@ Next we try to determine if we are looking at a field access or a method call.
Next, we see if there is a zero-arity member of the given name, either static or instance.
96
-
If we are in the static case, we have the type. If we in the instance case, it is necessary that the `instance` AST node have a known type. If we find a zero-arity method (), we set `maybeField` to false; otherwise it remains true. (I don't know enough about the JVM reflection APIs to know how looking for zero-arith methods picks up field accessors. Check out `Reflector.getMethods()` if you are curious.)
90
+
We then check if there is a zero-arity method of the given name -- unless we have an instance call and the name starts with`-`, in which case we are definitely dealing with a field access. We still might be a field if there no zero-arity methods.
What happens next determines if `maybeField` is true or not.
104
+
If `maybeField` is true, we _might_ be looking at a field access.
105
+
We create a `StaticFieldExpr` node if we are in the static case (`c` is non-null.)
106
+
We create an `InstanceFieldExpr` node if we are not in the static case.
107
+
We pass a flag indicating if the name started with a `-`, indicating that we are definitely dealing with a field access. Don't be fooled. An `InstanceFieldExpr` can generate reflection code that can access either a method or field at runtime, depending on what the runtime type of the `instance` expression turns out to be. See below.
108
+
109
+
If `maybeField` is false, we are looking at a method call. We create either a `StaticMethodExpr` or an `InstanceMethodExpr` node, depending on whether we are dealing with an instance or static member.
110
+
111
+
| Target is |`maybeField`| Node created |
112
+
|-----------|--------------|--------------|
113
+
| Type | true |`StaticFieldExpr`|
114
+
| Type | false |`StaticMethodExpr`|
115
+
| Not a type | true |`InstanceFieldExpr`|
116
+
| Not a type | false |`InstanceMethodExpr`|
117
+
118
+
The constructors for each of these node types does some additional analysis.
119
+
120
+
-`StaticMethodExpr`:
121
+
- We have a known static type, we have a name, we have an arity. If there are no methods of the given name and arity on the given type, we throw an error. (Method used to do method lookup: `Reflector.getMethods(...)`.)
122
+
- if there is more than one method of the given name and arity, we try to resolve to the best match based on the method argument types and the types of the provided arguments. (Method used to find best match: `Compiler.getMatchingParams(...)`.)
123
+
- if we cannot pick a best match, we will generate a reflection call during code-gen. (Maybe) print a warning.
124
+
- if we have a best match, we will be able to generate a direct call during code-gen.
125
+
- if we have a direct match and *unchecked-math* is :worn-on-boxed and the method is on the list of 'boxed match' methods, (maybe) print a warning.
126
+
127
+
-`InstanceMethodExpr`:
128
+
- if we do not know the type of our target, we will be generating reflection code during code-gen. (Maybe) print a warning.
129
+
- if we do know the type, the process is similar to `StaticMethodExpr` -- look for methods of the given name and arity, try to find a best match, etc. No boxing warnings, though; those methods are static only. There is an additional step to if we find a method but its declaring class is not public: we look up the class hierarchy to see if there is a public superclass that also declares the method. (`Reflector.getAsMethodOfPublicBase(...)` does this.)
97
130
98
-
If at this point we have `maybeField` true, we will create either an `InstanceFieldExpr` or a `StaticFieldExpr` node. The only wrinkle is if the field name starts with a `-`, in which case we strip off the `-`.
131
+
-`StaticFieldExpr`:
132
+
- this gets called only if there is not a zero-arity method of the given name on the given type.
133
+
- if there is no field of the given name on the given type, we throw an error.
134
+
- We will only generate code if there is a field of the given name on the given type, so no reflection will be needed.
99
135
100
-
If `maybeField` is false, we are looking at a method call -- maybe. We will create either an `InstanceMethodExpr` or a `StaticMethodExpr` node, depending on whether we are dealing with an instance or static member. Note that we might still be dealing with a property access in the case that we have an instance access and the type of the `instance` AST node is not known. It will be up to the code generation phase to generate reflection code in this case.
136
+
-`InstanceFieldExpr`:
137
+
- if we know the type of our target, we look for a field of the given name on that type.
138
+
- if the target type is unknown or we can't find a field on the target type, (maybe) print a warning; we will generate reflection code during code-gen.
139
+
140
+
141
+
### Code generation on the JVM
142
+
143
+
-`StaticFieldExpr` is the simplest.
144
+
- If we make it to code-gen, we know the type and the field. We can just emit the code to access the static field. This will never generate reflection code.
145
+
146
+
The other three node types have to decide if they can emit direct calls or if they need to emit reflection code.
147
+
148
+
-`StaticMethodExpr`
149
+
- direct: Emit the arguments, possibly with casts--that's done by `MethodExpr.emitTypedArgs(...)`. Call the method.
150
+
- reflection: set up a call to `Reflector.invokeStaticMethod(...)`, passing the class, method name, and arguments as an array.
151
+
-`InstanceMethodExpr`
152
+
- direct: Emit the target, emit the arguments, possibly with casts - also done by `MethodExpr.emitTypedArgs(...)`. Call the method.
153
+
- reflection: if we know the type (but we didn't have a good match on the method), set up a call to `Reflector.invokeInstanceMethodOfClass(...)`, passing the target, the target class, the method name, and arguments as an array. If we don't know the type, we call `Reflector.invokeInstanceMethod(...)`.
154
+
-`InstanceFieldExpr`
155
+
- direct: emit the target, get the field value.
156
+
- reflection: set up a call to `Reflector.invokeNoArgInstanceMember(...)`. As mentioned above, this one hides a trick. It can call a zero-arity method or access a field, depending on what is found.
157
+
158
+
There is some complexity buried in the various ancillary methods used to do method lookup, best-match selection, argument casting, and runtime dispatch. Let's dig in.
159
+
160
+
`Reflector.getMethods` - used both at compile-time and during runtime reflection.
161
+
`Compiler.getMatchingParams(...)`
101
162
102
163
### HostExpr parsing on the CLR
103
164
165
+
For ClojureCLR, the type hierarchy around host interop expressions looks like:
166
+
167
+
<imgsrc="{{site.baseurl | prepend: site.url}}/assets/images/hostexpr-type-dependencies.png"alt="Graph of all types related to HostExpr" />
168
+
169
+
The JVM is simpler; it has fewer subclasses of `HostExpr`. The CLR complications arise from the need to handle properties, which do not exist on the JVM. Unfortunately, this copmplication means that the parsing logic differs enough that we need to treat them separately. We'll start with the JVM version.
170
+
171
+
104
172
Reflection regarding type members -- methods vs fields vs properties -- differs non-trivially in CLR-land. ClojureCLR using the Dynamic Language Runtime (DLR) to handle reflection also has a bearing on how to handle ambiguity in the input. So I chose a somewhat different approach to parsing host expressions.
105
173
106
174
The first step in parsing is to regularize the syntactic variants into a common form, identifying
0 commit comments