Skip to content

Commit c3808d6

Browse files
committed
Minor tweaks
1 parent 5f8acbb commit c3808d6

10 files changed

Lines changed: 679 additions & 25 deletions

File tree

src/main/java/groovy/concurrent/AwaitResult.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,10 @@ public T getOrElse(Function<Throwable, ? extends T> fallback) {
100100
return success ? value : fallback.apply(error);
101101
}
102102

103+
/**
104+
* Returns a human-readable representation of this result:
105+
* {@code AwaitResult.Success[value]} or {@code AwaitResult.Failure[error]}.
106+
*/
103107
@Override
104108
public String toString() {
105109
return success

src/main/java/org/apache/groovy/parser/antlr4/AstBuilder.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1285,6 +1285,10 @@ public ClassNode visitClassDeclaration(final ClassDeclarationContext ctx) {
12851285
throw createParsingFailedException("only sealed type declarations should have `permits` clause", ctx);
12861286
}
12871287

1288+
if (modifierManager.containsAny(ASYNC)) {
1289+
throw createParsingFailedException("modifier `async` is not allowed for type declarations", modifierManager.get(ASYNC).get());
1290+
}
1291+
12881292
int modifiers = modifierManager.getClassModifiersOpValue();
12891293

12901294
boolean syntheticPublic = ((modifiers & Opcodes.ACC_SYNTHETIC) != 0);
@@ -2013,6 +2017,10 @@ public DeclarationListStatement visitVariableDeclaration(final VariableDeclarati
20132017
asBoolean(ctx.modifiers()) ? this.visitModifiers(ctx.modifiers()) : Collections.emptyList()
20142018
);
20152019

2020+
if (modifierManager.containsAny(ASYNC)) {
2021+
throw createParsingFailedException("modifier `async` is not allowed for variable declarations", modifierManager.get(ASYNC).get());
2022+
}
2023+
20162024
if (asBoolean(ctx.typeNamePairs())) { // e.g. def (int a, int b) = [1, 2]
20172025
return this.createMultiAssignmentDeclarationListStatement(ctx, modifierManager);
20182026
}

src/main/java/org/apache/groovy/parser/antlr4/ModifierManager.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
import java.util.stream.Collectors;
3838

3939
import static org.apache.groovy.parser.antlr4.GroovyLangParser.ABSTRACT;
40+
import static org.apache.groovy.parser.antlr4.GroovyLangParser.ASYNC;
4041
import static org.apache.groovy.parser.antlr4.GroovyLangParser.FINAL;
4142
import static org.apache.groovy.parser.antlr4.GroovyLangParser.NATIVE;
4243
import static org.apache.groovy.parser.antlr4.GroovyLangParser.STATIC;
@@ -47,7 +48,7 @@
4748
*/
4849
class ModifierManager {
4950
private static final Map<Class, List<Integer>> INVALID_MODIFIERS_MAP = Maps.of(
50-
ConstructorNode.class, Arrays.asList(STATIC, FINAL, ABSTRACT, NATIVE),
51+
ConstructorNode.class, Arrays.asList(STATIC, FINAL, ABSTRACT, NATIVE, ASYNC),
5152
MethodNode.class, Arrays.asList(VOLATILE/*, TRANSIENT*/) // Transient is left open for properties for legacy reasons but should be removed before ClassCompletionVerifier runs (CLASSGEN)
5253
);
5354
private AstBuilder astBuilder;

src/main/java/org/apache/groovy/runtime/async/AsyncStreamGenerator.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,21 @@ public void error(Throwable t) {
175175
}
176176
}
177177

178+
/**
179+
* {@inheritDoc}
180+
* <p>
181+
* Blocks the calling (consumer) thread on the {@link SynchronousQueue} until
182+
* the producer offers the next element, a completion sentinel, or an error.
183+
* If the stream has been {@linkplain #close() closed}, returns
184+
* {@code Awaitable.of(false)} immediately without blocking.
185+
* <p>
186+
* The consumer thread is registered via {@code consumerThread} during the
187+
* blocking call so that {@link #close()} can interrupt it if needed.
188+
*
189+
* @return an {@code Awaitable<Boolean>} that resolves to {@code true} if a
190+
* new element is available via {@link #getCurrent()}, or {@code false}
191+
* if the stream is exhausted or closed
192+
*/
178193
@Override
179194
@SuppressWarnings("unchecked")
180195
public Awaitable<Boolean> moveNext() {
@@ -208,6 +223,15 @@ public Awaitable<Boolean> moveNext() {
208223
}
209224
}
210225

226+
/**
227+
* {@inheritDoc}
228+
* <p>
229+
* Returns the most recently consumed element. The value is updated each time
230+
* {@link #moveNext()} returns {@code true}.
231+
*
232+
* @return the current element, or {@code null} before the first successful
233+
* {@code moveNext()} call
234+
*/
211235
@Override
212236
public T getCurrent() {
213237
return current;

src/main/java/org/apache/groovy/runtime/async/GroovyPromise.java

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,12 @@ public class GroovyPromise<T> implements Awaitable<T> {
4747

4848
private final CompletableFuture<T> future;
4949

50+
/**
51+
* Creates a new {@code GroovyPromise} wrapping the given {@link CompletableFuture}.
52+
*
53+
* @param future the backing future; must not be {@code null}
54+
* @throws NullPointerException if {@code future} is {@code null}
55+
*/
5056
public GroovyPromise(CompletableFuture<T> future) {
5157
this.future = Objects.requireNonNull(future, "future must not be null");
5258
}
@@ -58,6 +64,13 @@ public static <T> GroovyPromise<T> of(CompletableFuture<T> future) {
5864
return new GroovyPromise<>(future);
5965
}
6066

67+
/**
68+
* {@inheritDoc}
69+
* <p>
70+
* Waits if necessary for the computation to complete, then retrieves its result.
71+
* If the future was cancelled, the original {@link CancellationException} is
72+
* unwrapped from the JDK 23+ wrapper for cross-version consistency.
73+
*/
6174
@Override
6275
public T get() throws InterruptedException, ExecutionException {
6376
try {
@@ -67,6 +80,12 @@ public T get() throws InterruptedException, ExecutionException {
6780
}
6881
}
6982

83+
/**
84+
* {@inheritDoc}
85+
* <p>
86+
* Waits at most the given time for the computation to complete.
87+
* Unwraps JDK 23+ {@link CancellationException} wrappers for consistency.
88+
*/
7089
@Override
7190
public T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
7291
try {
@@ -76,6 +95,7 @@ public T get(long timeout, TimeUnit unit) throws InterruptedException, Execution
7695
}
7796
}
7897

98+
/** {@inheritDoc} */
7999
@Override
80100
public boolean isDone() {
81101
return future.isDone();
@@ -98,26 +118,47 @@ public boolean cancel() {
98118
return future.cancel(true);
99119
}
100120

121+
/** {@inheritDoc} */
101122
@Override
102123
public boolean isCancelled() {
103124
return future.isCancelled();
104125
}
105126

127+
/** {@inheritDoc} */
106128
@Override
107129
public boolean isCompletedExceptionally() {
108130
return future.isCompletedExceptionally();
109131
}
110132

133+
/**
134+
* {@inheritDoc}
135+
* <p>
136+
* Returns a new {@code GroovyPromise} whose result is obtained by applying
137+
* the given function to this promise's result.
138+
*/
111139
@Override
112140
public <U> Awaitable<U> then(Function<? super T, ? extends U> fn) {
113141
return new GroovyPromise<>(future.thenApply(fn));
114142
}
115143

144+
/**
145+
* {@inheritDoc}
146+
* <p>
147+
* Returns a new {@code GroovyPromise} that is the result of composing this
148+
* promise with the async function, enabling flat-mapping of awaitables.
149+
*/
116150
@Override
117151
public <U> Awaitable<U> thenCompose(Function<? super T, ? extends Awaitable<U>> fn) {
118152
return new GroovyPromise<>(future.thenCompose(t -> fn.apply(t).toCompletableFuture()));
119153
}
120154

155+
/**
156+
* {@inheritDoc}
157+
* <p>
158+
* Returns a new {@code GroovyPromise} that handles exceptions thrown by this promise.
159+
* The throwable passed to the handler is deeply unwrapped to strip JDK
160+
* wrapper layers ({@code CompletionException}, {@code ExecutionException}).
161+
*/
121162
@Override
122163
public Awaitable<T> exceptionally(Function<Throwable, ? extends T> fn) {
123164
return new GroovyPromise<>(future.exceptionally(t -> {
@@ -127,6 +168,11 @@ public Awaitable<T> exceptionally(Function<Throwable, ? extends T> fn) {
127168
}));
128169
}
129170

171+
/**
172+
* {@inheritDoc}
173+
* <p>
174+
* Returns the underlying {@link CompletableFuture} for interop with JDK APIs.
175+
*/
130176
@Override
131177
public CompletableFuture<T> toCompletableFuture() {
132178
return future;
@@ -143,6 +189,11 @@ private static CancellationException unwrapCancellation(CancellationException ex
143189
return cause instanceof CancellationException ce ? ce : exception;
144190
}
145191

192+
/**
193+
* Returns a human-readable representation showing the promise state:
194+
* {@code GroovyPromise{pending}}, {@code GroovyPromise{completed}}, or
195+
* {@code GroovyPromise{failed}}.
196+
*/
146197
@Override
147198
public String toString() {
148199
if (future.isDone()) {

src/spec/doc/core-async-await.adoc

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -382,6 +382,23 @@ each result is wrapped in an `AwaitResult` with `isSuccess()`, `isFailure()`, `v
382382
include::../test/AsyncAwaitSpecTest.groovy[tags=await_all_settled,indent=0]
383383
----
384384
385+
[[await-result]]
386+
=== `AwaitResult` — Outcome Wrapper
387+
388+
Each element returned by `allSettled` is an `AwaitResult<T>`. This immutable value type
389+
carries either a success value or a failure throwable, and provides safe accessors:
390+
391+
* `isSuccess()` / `isFailure()` — check outcome type
392+
* `value` — the result (throws `IllegalStateException` on failure)
393+
* `error` — the exception (throws `IllegalStateException` on success)
394+
* `getOrElse(Function)` — recover from failures with a fallback function
395+
* `toString()` — returns `AwaitResult.Success[value]` or `AwaitResult.Failure[error]`
396+
397+
[source,groovy]
398+
----
399+
include::../test/AsyncAwaitSpecTest.groovy[tags=await_result_api,indent=0]
400+
----
401+
385402
=== Continuation Helpers
386403
387404
Use `thenAccept`, `whenComplete`, and `handle` when you want continuation-style
@@ -415,6 +432,16 @@ provide the same capability on an existing awaitable.
415432
include::../test/AsyncAwaitSpecTest.groovy[tags=timeout_combinators,indent=0]
416433
----
417434
435+
==== Instance Forms: `orTimeout` and `completeOnTimeout`
436+
437+
The instance methods `orTimeout(millis)` and `completeOnTimeout(fallback, millis)` provide
438+
a fluent alternative to the static `Awaitable.timeout()` and `Awaitable.timeoutOr()` forms:
439+
440+
[source,groovy]
441+
----
442+
include::../test/AsyncAwaitSpecTest.groovy[tags=instance_timeout_methods,indent=0]
443+
----
444+
418445
[[flow-publisher]]
419446
== `Flow.Publisher` Integration
420447
@@ -638,6 +665,31 @@ lose exceptions silently:
638665
include::../test/AsyncAwaitSpecTest.groovy[tags=fire_and_forget,indent=0]
639666
----
640667
668+
[[inspection]]
669+
== Inspecting Awaitable State
670+
671+
An `Awaitable` provides non-blocking inspection methods for checking its state without
672+
blocking the calling thread:
673+
674+
* `isDone()` — `true` when the awaitable has completed (successfully, exceptionally, or via cancellation)
675+
* `isCancelled()` — `true` when the awaitable was cancelled via `cancel()`
676+
* `isCompletedExceptionally()` — `true` when the awaitable completed with an exception (including cancellation)
677+
678+
[source,groovy]
679+
----
680+
include::../test/AsyncAwaitSpecTest.groovy[tags=inspection_methods,indent=0]
681+
----
682+
683+
[[async-modifier-restrictions]]
684+
== `async` Modifier Restrictions
685+
686+
The `async` keyword is valid only on **method declarations** (including script-level methods)
687+
and **closure/lambda expressions**. Applying `async` to other declarations is a compile-time error:
688+
689+
* **Class/interface declarations**: `async class Foo {}` → parser error
690+
* **Field/variable declarations**: `async def x = 1` → compilation error ("async not allowed for variable declarations")
691+
* **Constructor declarations**: `async Foo() {}` → compilation error ("async not allowed for constructors")
692+
641693
[[implementation-details]]
642694
== Implementation Notes
643695
@@ -899,4 +951,13 @@ JavaScript, C#, Kotlin, and Swift, for developers familiar with those languages.
899951
900952
| Annotation form
901953
| `@Async def methodName() { ... }`
954+
955+
| Inspection
956+
| `awaitable.isDone()` / `isCancelled()` / `isCompletedExceptionally()`
957+
958+
| Timeout (instance)
959+
| `awaitable.orTimeout(ms)` / `awaitable.completeOnTimeout(fallback, ms)`
960+
961+
| AwaitResult
962+
| `result.isSuccess()` / `result.isFailure()` / `result.getOrElse { fallback }`
902963
|===

0 commit comments

Comments
 (0)