Light Mode

Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit ec35b7d

Browse files
committed
GROOVY-9381: Support async/await like ES7
1 parent 7d8349c commit ec35b7d

File tree

30 files changed

+18665
-2
lines changed
  • build.gradle
  • gradle
    • verification-metadata.xml
  • src
    • antlr
      • GroovyLexer.g4
      • GroovyParser.g4
    • main/java
      • groovy
        • concurrent
          • AsyncStream.java
          • AwaitResult.java
          • Awaitable.java
          • AwaitableAdapter.java
          • AwaitableAdapterRegistry.java
        • transform
          • Async.java
      • org
        • apache/groovy
          • parser/antlr4
            • AstBuilder.java
          • runtime/async
            • AsyncStreamGenerator.java
            • AsyncSupport.java
            • GroovyPromise.java
        • codehaus/groovy
          • ast
            • ModifierNode.java
          • transform
            • AsyncASTTransformation.java
            • AsyncTransformHelper.java
    • spec
      • doc
        • core-async-await.adoc
      • test
        • AsyncAwaitSpecTest.groovy
    • test/groovy/org/codehaus/groovy/transform
      • AsyncAwaitSyntaxTest.groovy
      • AsyncBestPracticesTest.groovy
      • AsyncClosureLambdaTest.groovy
      • AsyncCoverageTest.groovy
      • AsyncDeferFlowTest.groovy
      • AsyncExceptionHandlingTest.groovy
      • AsyncFrameworkIntegrationTest.groovy
      • AsyncPatternsTest.groovy
      • AsyncTransformTest.groovy
      • AsyncVirtualThreadTest.groovy
  • versions.properties

30 files changed

+18665
-2
lines changed

build.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,8 @@ dependencies {
122122
testImplementation "com.thoughtworks.qdox:qdox:${versions.qdox}"
123123
testImplementation "com.fasterxml.jackson.core:jackson-databind:${versions.jackson}"
124124
testImplementation "com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:${versions.jackson}"
125+
testImplementation "io.reactivex.rxjava3:rxjava:${versions.rxjava3}"
126+
testImplementation "io.projectreactor:reactor-core:${versions.reactor}"
125127

126128
testFixturesImplementation projects.groovyXml
127129
testFixturesImplementation projects.groovyTest

gradle/verification-metadata.xml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@
114114
<ignored-key id="C71FB765CD9DE313" reason="Key couldn't be downloaded from any key server"/>
115115
<ignored-key id="C7CA19B7B620D787" reason="Key couldn't be downloaded from any key server"/>
116116
<ignored-key id="CA80D1F0EB6CA4BA" reason="Key couldn't be downloaded from any key server"/>
117+
<ignored-key id="D1031D14464180E0" reason="Key couldn't be downloaded from any key server"/>
117118
<ignored-key id="D2151178A123C97F" reason="Key couldn't be downloaded from any key server"/>
118119
<ignored-key id="D364ABAA39A47320" reason="Key couldn't be downloaded from any key server"/>
119120
<ignored-key id="D7742D58455ECC7C" reason="Key couldn't be downloaded from any key server"/>
@@ -861,6 +862,16 @@
861862
<sha512 value="f220e44fe6b61f8dbb61226f832dfb16a09584384540fd48a4dff5c4de9fee060623f85cbead720dfe776aa25105949e70758a9bb1d9db43f63068d8d22164c9" origin="Generated by Gradle"/>
862863
artifact>
863864
component>
865+
<component group="io.projectreactor" name="reactor-core" version="3.7.3">
866+
<artifact name="reactor-core-3.7.3.jar">
867+
<pgp value="48B086A7D843CFA258E83286928FBF39003C0425"/>
868+
artifact>
869+
component>
870+
<component group="io.reactivex.rxjava3" name="rxjava" version="3.1.10">
871+
<artifact name="rxjava-3.1.10.jar">
872+
<pgp value="E9CC3CD1AE59E851E4DB3FA350FFD7487D34B5B9"/>
873+
artifact>
874+
component>
864875
<component group="jakarta.activation" name="jakarta.activation-api" version="1.2.1">
865876
<artifact name="jakarta.activation-api-1.2.1.jar">
866877
<pgp value="6DD3B8C64EF75253BEB2C53AD908A43FB7EC07AC"/>
@@ -2216,6 +2227,11 @@
22162227
<sha512 value="adcc480f68828ffd68d03846be852988b595c2e1bb69224d273578dd6c2ad2773edfe96625a7c00bc40ae0f2d1cac8412eaa54b88cc8e681b0b4c0ee3b082333" origin="Generated by Gradle"/>
22172228
artifact>
22182229
component>
2230+
<component group="org.reactivestreams" name="reactive-streams" version="1.0.4">
2231+
<artifact name="reactive-streams-1.0.4.jar">
2232+
<sha512 value="cdab6bd156f39106cd6bbfd47df1f4b0a89dc4aa28c68c31ef12a463193c688897e415f01b8d7f0d487b0e6b5bd2f19044bf8605704b024f26d6aa1f4f9a2471" origin="Generated by Gradle" reason="A key couldn't be downloaded"/>
2233+
artifact>
2234+
component>
22192235
<component group="org.reflections" name="reflections" version="0.10.2">
22202236
<artifact name="reflections-0.10.2.jar">
22212237
<pgp value="3F2A008A91D11A7FAC4A0786F13D3E721D56BD54"/>

src/antlr/GroovyLexer.g4

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,9 @@ DEF : 'def';
392392
IN : 'in';
393393
TRAIT : 'trait';
394394
THREADSAFE : 'threadsafe'; // reserved keyword
395+
ASYNC : 'async';
396+
AWAIT : 'await';
397+
DEFER : 'defer';
395398

396399
// SS3.9 Keywords
397400
BuiltInPrimitiveType

src/antlr/GroovyParser.g4

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ modifier
134134
| VOLATILE
135135
| DEF
136136
| VAR
137+
| ASYNC
137138
)
138139
;
139140

@@ -600,7 +601,7 @@ switchStatement
600601
;
601602

602603
loopStatement
603-
: FOR LPAREN forControl RPAREN nls statement #forStmtAlt
604+
: FOR AWAIT? LPAREN forControl RPAREN nls statement #forStmtAlt
604605
| WHILE expressionInPar nls statement #whileStmtAlt
605606
| DO nls statement nls WHILE expressionInPar #doWhileStmtAlt
606607
;
@@ -642,6 +643,8 @@ statement
642643
| continueStatement #continueStmtAlt
643644
| { inSwitchExpressionLevel > 0 }?
644645
yieldStatement #yieldStmtAlt
646+
| YIELD RETURN nls expression #yieldReturnStmtAlt
647+
| DEFER nls statementExpression #deferStmtAlt
645648
| identifier COLON nls statement #labeledStmtAlt
646649
| assertStatement #assertStmtAlt
647650
| localVariableDeclaration #localVariableDeclarationStmtAlt
@@ -778,6 +781,10 @@ expression
778781
// must come before postfixExpression to resolve the ambiguities between casting and call on parentheses expression, e.g. (int)(1 / 2)
779782
: castParExpression castOperandExpression #castExprAlt
780783

784+
// async closure/lambda must come before postfixExpression to resolve the ambiguities between async and method call, e.g. async { ... }
785+
| ASYNC nls closureOrLambdaExpression #asyncClosureExprAlt
786+
| AWAIT nls (LPAREN expression RPAREN | expression) #awaitExprAlt
787+
781788
// qualified names, array expressions, method invocation, post inc/dec
782789
| postfixExpression #postfixExprAlt
783790

@@ -1228,6 +1235,9 @@ identifier
12281235
: Identifier
12291236
| CapitalizedIdentifier
12301237
| AS
1238+
| ASYNC
1239+
| AWAIT
1240+
| DEFER
12311241
| IN
12321242
| PERMITS
12331243
| RECORD
@@ -1246,12 +1256,15 @@ keywords
12461256
: ABSTRACT
12471257
| AS
12481258
| ASSERT
1259+
| ASYNC
1260+
| AWAIT
12491261
| BREAK
12501262
| CASE
12511263
| CATCH
12521264
| CLASS
12531265
| CONST
12541266
| CONTINUE
1267+
| DEFER
12551268
| DEF
12561269
| DEFAULT
12571270
| DO

src/main/java/groovy/concurrent/AsyncStream.java

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package groovy.concurrent;
20+
21+
/**
22+
* Asynchronous iteration abstraction, analogous to C#'s
23+
* {@code IAsyncEnumerable} or JavaScript's async iterables.
24+
*

25+
* Used with the {@code for await} syntax:
26+
*
27+
* for await (item in asyncStream) {
28+
* process(item)
29+
* }
30+
*
31+
*

32+
* An {@code AsyncStream} can be produced in several ways:
33+
*
    34+
    *
  • Using {@code yield return} inside an {@code async} method or closure
  • 35+
    * to create a generator-style stream
    36+
    *
  • Adapting JDK {@link java.util.concurrent.Flow.Publisher} instances
  • 37+
    * (supported out of the box by the built-in adapter)
    38+
    *
  • Adapting third-party reactive types (Reactor {@code Flux}, RxJava
  • 39+
    * {@code Observable}) via {@link AwaitableAdapter}
    40+
    *
    41+
    *
    42+
    * @param the element type
    43+
    * @see AwaitableAdapter
    44+
    * @see AwaitableAdapterRegistry
    45+
    * @since 6.0.0
    46+
    */
    47+
    public interface AsyncStream<T> extends AutoCloseable {
    48+
    49+
    /**
    50+
    * Asynchronously advances to the next element. Returns an {@link Awaitable}
    51+
    * that completes with {@code true} if an element is available, or
    52+
    * {@code false} if the stream is exhausted.
    53+
    */
    54+
    Awaitable<Boolean> moveNext();
    55+
    56+
    /**
    57+
    * Returns the current element. Must only be called after {@link #moveNext()}
    58+
    * has completed with {@code true}.
    59+
    */
    60+
    T getCurrent();
    61+
    62+
    /**
    63+
    * Closes the stream and releases any associated resources.
    64+
    *

    65+
    * The default implementation is a no-op. Implementations that bridge to
    66+
    * generators, publishers, or other resource-owning sources may override
    67+
    * this to propagate cancellation upstream. Compiler-generated
    68+
    * {@code for await} loops invoke {@code close()} automatically from a
    69+
    * {@code finally} block, including on early {@code break}, {@code return},
    70+
    * and exceptional exit.
    71+
    *
    72+
    * @since 6.0.0
    73+
    */
    74+
    @Override
    75+
    default void close() {
    76+
    }
    77+
    78+
    /**
    79+
    * Returns an empty {@code AsyncStream} that completes immediately.
    80+
    */
    81+
    @SuppressWarnings("unchecked")
    82+
    static <T> AsyncStream<T> empty() {
    83+
    return (AsyncStream<T>) EMPTY;
    84+
    }
    85+
    86+
    /** Singleton empty stream instance. */
    87+
    AsyncStream<Object> EMPTY = new AsyncStream<>() {
    88+
    @Override public Awaitable<Boolean> moveNext() { return Awaitable.of(false); }
    89+
    @Override public Object getCurrent() { return null; }
    90+
    };
    91+
    }

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

    Lines changed: 109 additions & 0 deletions
    Original file line numberDiff line numberDiff line change
    @@ -0,0 +1,109 @@
    1+
    /*
    2+
    * Licensed to the Apache Software Foundation (ASF) under one
    3+
    * or more contributor license agreements. See the NOTICE file
    4+
    * distributed with this work for additional information
    5+
    * regarding copyright ownership. The ASF licenses this file
    6+
    * to you under the Apache License, Version 2.0 (the
    7+
    * "License"); you may not use this file except in compliance
    8+
    * with the License. You may obtain a copy of the License at
    9+
    *
    10+
    * http://www.apache.org/licenses/LICENSE-2.0
    11+
    *
    12+
    * Unless required by applicable law or agreed to in writing,
    13+
    * software distributed under the License is distributed on an
    14+
    * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    15+
    * KIND, either express or implied. See the License for the
    16+
    * specific language governing permissions and limitations
    17+
    * under the License.
    18+
    */
    19+
    package groovy.concurrent;
    20+
    21+
    import java.util.Objects;
    22+
    import java.util.function.Function;
    23+
    24+
    /**
    25+
    * Represents the outcome of an asynchronous computation that may have
    26+
    * succeeded or failed. This is used by {@code awaitAllSettled()} --
    27+
    * the Groovy equivalent of JavaScript's {@code Promise.allSettled()}.
    28+
    *

    29+
    * An {@code AwaitResult} is either a {@linkplain #isSuccess() success}
    30+
    * carrying a value, or a {@linkplain #isFailure() failure} carrying a
    31+
    * {@link Throwable}.
    32+
    *
    33+
    * @param the value type
    34+
    * @since 6.0.0
    35+
    */
    36+
    public final class AwaitResult<T> {
    37+
    38+
    private final T value;
    39+
    private final Throwable error;
    40+
    private final boolean success;
    41+
    42+
    private AwaitResult(T value, Throwable error, boolean success) {
    43+
    this.value = value;
    44+
    this.error = error;
    45+
    this.success = success;
    46+
    }
    47+
    48+
    /**
    49+
    * Creates a successful result with the given value.
    50+
    */
    51+
    @SuppressWarnings("unchecked")
    52+
    public static <T> AwaitResult<T> success(Object value) {
    53+
    return new AwaitResult<>((T) value, null, true);
    54+
    }
    55+
    56+
    /**
    57+
    * Creates a failure result with the given exception.
    58+
    */
    59+
    public static <T> AwaitResult<T> failure(Throwable error) {
    60+
    return new AwaitResult<>(null, Objects.requireNonNull(error), false);
    61+
    }
    62+
    63+
    /** Returns {@code true} if this result represents a successful completion. */
    64+
    public boolean isSuccess() {
    65+
    return success;
    66+
    }
    67+
    68+
    /** Returns {@code true} if this result represents a failed completion. */
    69+
    public boolean isFailure() {
    70+
    return !success;
    71+
    }
    72+
    73+
    /**
    74+
    * Returns the value if successful.
    75+
    *
    76+
    * @return the computation result
    77+
    * @throws IllegalStateException if this result represents a failure
    78+
    */
    79+
    public T getValue() {
    80+
    if (!success) throw new IllegalStateException("Cannot get value from a failed result");
    81+
    return value;
    82+
    }
    83+
    84+
    /**
    85+
    * Returns the exception if failed.
    86+
    *
    87+
    * @return the exception that caused the failure
    88+
    * @throws IllegalStateException if this result represents a success
    89+
    */
    90+
    public Throwable getError() {
    91+
    if (success) throw new IllegalStateException("Cannot get error from a successful result");
    92+
    return error;
    93+
    }
    94+
    95+
    /**
    96+
    * Returns the value if successful, or applies the given function to
    97+
    * the error to produce a fallback value.
    98+
    */
    99+
    public T getOrElse(Function<Throwable, ? extends T> fallback) {
    100+
    return success ? value : fallback.apply(error);
    101+
    }
    102+
    103+
    @Override
    104+
    public String toString() {
    105+
    return success
    106+
    ? "AwaitResult.Success[" + value + "]"
    107+
    : "AwaitResult.Failure[" + error + "]";
    108+
    }
    109+
    }

    0 commit comments

    Comments
    (0)