On Oct 30, 2021, at 3:22 PM, John Rose <john.r.r...@oracle.com<mailto:john.r.r...@oracle.com>> wrote:
restrict the operand x (the receiver LHS of the S.T.) to be a statically constant expression. If we do that, then we can hand three things to a bootstrap method that can do static validation: 1. the (constant) receiver 2. the string body (with holes marked) 3. maybe the static types of the arguments (this is very natural for indy) Completing the design is pretty straightforward, but I might as well write out more of my work. Here’s *one possible* design in which the terminal “apply” operation is performed under the name “MethodHandle.invokeExact”. X."y…\{z…}" translates to an invokedynamic instruction The static arguments to the indy instruction are X (formed as a CONSTANT_Dynamic constant as necessary) and the string body containing y with hole indicators. Thus, the indy BSM gets the following: 1. a Lookup 2. a name (ignored) 3. a method-type (composed of the static types of z, returning R the expression type) 4. X (via condy) 5. "y…" where the holes are appropriately marked It returns a CallSite, which is then used for all evaluations of the expression. Applications will use a ConstantCallSite. That is the mechanism. It does not say what is the logic of the BSM or the type R. That is where the language rules come in. The type of X must contain, directly or not, two or three methods, validate, apply, asMethodHandle. The methods are declared as abstracts using one or two API types. (Logically, they could also be left “hanging” outside of any interface as the magic methods Brian detests.) I will show one-interface and two-interface potential designs. interface ST_A<R,E> { // 1 type with 3 methods ST12 validate(Lookup, String, MethodType); <R> apply(E…); MethodHandle asMethodHandle(); } interface ST_B<R,E> { // 2 types with 1 or 2 methods Applier<R,E> validate(Lookup, String, MethodType); interface Applier<R,E> { <R> apply(Object… E); MethodHandle asMethodHandle(); } //default R validateAndApply(Lookup, String, MethodType) { … } } interface ST_C<R> { // 1 type with 2 methods, plus MH MethodHandle validate(Lookup, String, MethodType); R validateAndInvoke(Lookup, String, MethodType, Object...); } // “apply” here is MethodHandle::invokeExact; asMethodHandle is a nop The language infers R as usual, as if the call were going through apply (A), validate then apply (B) or validateAndInvoke (C). But the BSM uses drives the same API points to obtain the needed MethodHandle, which is then installed in a CCS. Further variations: Have a static hook to produce, not a CCS but a general CS such as a MCS. Drop the Lookup argument from the APIs, because who wants that? You can add it later. The oddity here, as in existing prototypes, is that there are “two sets of books”, one for indy with its static bootstrap logic that produces a method handle, and one “for the police” which shows how all the types (including R) fit together. All of the above APIs allow implementations (subtypes) of the interface to supply different calling sequences for the eventual apply (or invoke). This is important, because a logger-oriented Applier wants to accept delayed evaluation lambdas if possible, while other simpler uses of the S.T. mechanism will be happy to get along with just the Object arguments of apply(Object…). One of the fine points of this design is whether and how to statically type the *hole arguments* and whether the static type of the receiver (x in x."…") can affect the subsequent static typing of the hole arguments. With a separate Applier type, the degrees of freedom in hole type checking are, maybe, a little easier to manage, but all of the API types above are malleable to some degree. Ultimately, I think we will be pushed to allow some amount of overloading on the “apply” method, if use cases demand static checking of argument lists. I’ve put in the “E” parameter above as a stop gap to allow (at least) the necessary distinction between Object and Supplier<Object> for distinct use cases. If we ever do “Varargs 2.0” (better varargs, with richer argument type patterns encoded into the VA receiver), that will naturally add value to the above APIs, if they can be retrofitted or replaced with VA2.0 APIs situated on apply. That last one (ST_C) is nice and simple. Maybe that’s a good one to start with, maybe sans Lookup. The others can be layered on later on. A final word: If you said “that’s a curried function” to yourself at some point reading the above, you are not wrong.