[
https://issues.apache.org/jira/browse/GROOVY-12724?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18142634#comment-18142634
]
ASF GitHub Bot commented on GROOVY-12724:
-----------------------------------------
daniellansun commented on PR #3259:
URL: https://github.com/apache/groovy/pull/3259#issuecomment-6781911494
(3) The example I answered last time really was two locals, and
`VariableScopeVisitor` rejects that with or without the bridge. What you meant
is a field or a property and a local of the same name. That does compile, and
the bridge does not treat them as one name.
The lowered query is spliced into the method, `VariableScopeVisitor` links
each reference to the declaration in scope, and the original call is put back.
A `FieldNode` or a `PropertyNode` is not copied. It is read when the closure
runs. A local or a closure parameter declared inside the query is a different
declaration, and that is the one the references in that scope use. A
`DynamicVariable` is not copied either. At conversion that is what an import
alias still is, including a lower-case alias such as `import
java.util.concurrent.TimeUnit as threshold`, and the alias then resolves to the
class.
I ran that shape under `@CompileStatic`. A field `threshold` of 10 is set to
4 after the `GQ` expression and before `toList()`. The `where` clause sees 4. A
closure parameter `threshold` of 5 in the same query's `select` stays 5. A
getter/setter property behaves the same way: the getter runs at the terminal,
not when the expression is built. `@TypeChecked` does this too. `GQL` still
materializes at the expression, so a later write of the field is not observed.
A closure parameter of the same name as the field wins over the field. Two
locals of the same name are still rejected. `STRATEGY.md` now says this
explicitly. There is still no name walk in the bridge.
(5) You are right that I answered a different question. The problem is not
that the GINQ operators are dynamic. `DYNAMIC_RESOLUTION = Boolean.TRUE` is set
on the lowered query closure, and `StaticTypesClosureWriter` compiles that
whole closure with the dynamic writer. Every expression in it is dynamic,
including a call written in `where` or `select`. A target another transform
already chose for that call is not what is emitted. The call is dispatched at
runtime.
A concrete case: an extension's `onMethodSelection` forces
`choose(CharSequence)` when the argument is a `String`. In the enclosing
`@CompileStatic` method that is the method that runs. The same call inside
`select` hits `choose(String)`, because the runtime type wins. The same is true
of any other call in the clause whose static target differs from the runtime
dispatch.
I tried not to mark the closure. Clause parameters stayed dynamic, so a map
row is not inferred as a bean. A call the checker could resolve kept its
target, and only an operator whose target was a stand-in became a dynamic call.
The read-only snapshot was passed into the result lambda as a parameter,
because a static lambda still cannot capture a local of an enclosing closure:
the bytecode loads the outer class and casts it to `GroovyObject`. `orderby n
in desc` and the join tests in the static suite still passed, and the
`choose(CharSequence)` target was honoured inside the query.
The rest of the dynamic query did not. On the groovy-ginq suite, 71 tests
failed. `where note(n)`, `note` being a method of the enclosing class, was
emitted as `invokevirtual` of `Object.note()`, which does not exist. A join
alias was resolved against `QueryableCollection`. A parallel join that returns
rows dynamically returned an empty list. Group, window, aggregate, and
hash-join scripts failed in the same way. Parallel `submit` also cannot take
the snapshot as an extra parameter, because that lambda is a `Supplier`.
So the closure stays dynamic. The enclosing method does not. I have written
that limitation into `STRATEGY.md` and the CompileStatic section of the user
guide, including the `choose(CharSequence)` example. I would rather the clause
bodies stayed on the static writer. I do not have a way to do that which still
runs the queries that pass today.
> GINQ: support static compilation and a fluent query API
> -------------------------------------------------------
>
> Key: GROOVY-12724
> URL: https://issues.apache.org/jira/browse/GROOVY-12724
> Project: Groovy
> Issue Type: Improvement
> Components: GINQ
> Reporter: Daniel Sun
> Assignee: Daniel Sun
> Priority: Major
>
> Under @CompileStatic / @TypeChecked, GQ, GQL, and @GQ currently rely on
> dynamic lowering. This improvement makes those entry points usable from a
> statically checked scope while keeping the SQL-like syntax lowered to the
> in-memory collection runtime.
> h3. Goals
> * Allow GQ / GQL / @GQ from @CompileStatic and @TypeChecked without forcing
> the lowered tree through static checking at the expression site.
> * Defer pipeline construction with LazyQueryable until a terminal read
> (toList, count, etc.), copying locals at the expression site (same binding as
> dynamic GQ).
> * Add Ginq.from(...) as a deferred, generic fluent chain on the same runtime,
> with @ClosureParams so where/select/join/aggregate closures can infer element
> types where inference is sound.
> * Preserve default lazy/deferred evaluation and dynamic GQ behavior for map
> rows and other intentionally dynamic cases.
> h3. Non-goals / known limits
> * Stored untyped Closure variables still need a SAM target type for parameter
> inference (same as Java/C#).
> * Raw Query parameters/fields cannot recover a safe element type.
> * Names bound by from inside GQ / GQL / @GQ stay dynamic by design; callers
> that need checked element types use Ginq.from.
> h3. Related work
> GitHub PR: https://github.com/apache/groovy/pull/3259
> Stacked on GEP-26 SQL window work (PR #3167 / gep26-sql-window).
--
This message was sent by Atlassian Jira
(v10.3.26#10030026)