Skip to content

Commit d0fbcca

Browse files
committed
#2876 #2873 Better handling of async/await syntax
- `await` is now parsed as an unary named operator - `async`/`await` are now implicitly declared and auto-imported as subs from the package, making completion and quickdoc work as expected Fixes #2876 #2873 See also: #2878 #2877 #2875
1 parent 056a237 commit d0fbcca

File tree

16 files changed

+9725
-8852
lines changed

16 files changed

+9725
-8852
lines changed

plugin/core/grammar/Perl.flex

+1-1
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ TYPES_TINY = "ArrayRef"|"ConsumerOf"|"CycleTuple"|"Dict"|"Enum"|"HasMethods"|"Ha
134134
READONLY="Readonly"
135135

136136
// auto-generated by handlesubs.pl
137-
NAMED_UNARY_OPERATORS = "umask"|"srand"|"sleep"|"setservent"|"setprotoent"|"setnetent"|"sethostent"|"reset"|"readline"|"rand"|"prototype"|"localtime"|"gmtime"|"getsockname"|"getpwuid"|"getpwnam"|"getprotobyname"|"getpgrp"|"getpeername"|"getnetbyname"|"gethostbyname"|"getgrnam"|"getgrgid"|"exists"|"caller"
137+
NAMED_UNARY_OPERATORS = "umask"|"srand"|"sleep"|"setservent"|"setprotoent"|"setnetent"|"sethostent"|"reset"|"readline"|"rand"|"prototype"|"localtime"|"gmtime"|"getsockname"|"getpwuid"|"getpwnam"|"getprotobyname"|"getpgrp"|"getpeername"|"getnetbyname"|"gethostbyname"|"getgrnam"|"getgrgid"|"exists"|"caller"|"await"
138138
BARE_HANDLE_ACCEPTORS = "truncate"|"syswrite"|"sysseek"|"sysread"|"sysopen"|"stat"|"select"|"seekdir"|"seek"|"read"|"opendir"|"open"|"lstat"|"ioctl"|"flock"|"fcntl"|"binmode"
139139
NAMED_UNARY_BARE_HANDLE_ACCEPTORS = "write"|"telldir"|"tell"|"rewinddir"|"readdir"|"getc"|"fileno"|"eof"|"closedir"|"close"|"chdir"
140140
LIST_OPERATORS = "warn"|"waitpid"|"vec"|"utime"|"untie"|"tied"|"tie"|"system"|"syscall"|"symlink"|"substr"|"sprintf"|"socketpair"|"socket"|"shutdown"|"shmwrite"|"shmread"|"shmget"|"shmctl"|"setsockopt"|"setpriority"|"setpgrp"|"send"|"semop"|"semget"|"semctl"|"rindex"|"rename"|"recv"|"pipe"|"pack"|"msgsnd"|"msgrcv"|"msgget"|"msgctl"|"lock"|"listen"|"link"|"kill"|"join"|"index"|"getsockopt"|"getservbyport"|"getservbyname"|"getprotobynumber"|"getpriority"|"getnetbyaddr"|"gethostbyaddr"|"formline"|"exec"|"dump"|"die"|"dbmopen"|"dbmclose"|"crypt"|"connect"|"chown"|"chmod"|"bind"|"atan2"|"accept"

plugin/core/src/main/gen/com/perl5/lang/perl/lexer/PerlLexer.java

+8,863-8,843
Large diffs are not rendered by default.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*
2+
* Copyright 2015-2024 Alexandr Evstigneev
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.perl5.lang.perl.extensions.futureAsyncAwait;
18+
19+
import com.perl5.lang.perl.psi.references.PerlImplicitDeclarationsProvider;
20+
import org.jetbrains.annotations.NotNull;
21+
22+
public class FutureAsyncAwaitImplicitDeclarationsProvider extends PerlImplicitDeclarationsProvider {
23+
@Override
24+
public @NotNull String getDataFileName() {
25+
return "perlData/FutureAsyncAwait.xml";
26+
}
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*
2+
* Copyright 2015-2024 Alexandr Evstigneev
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.perl5.lang.perl.extensions.futureAsyncAwait;
18+
19+
import com.perl5.lang.perl.extensions.packageprocessor.PerlPackageOptionsProvider;
20+
import com.perl5.lang.perl.extensions.packageprocessor.PerlPackageProcessorBase;
21+
import com.perl5.lang.perl.psi.impl.PerlUseStatementElement;
22+
import org.jetbrains.annotations.NotNull;
23+
24+
import java.util.Collections;
25+
import java.util.List;
26+
import java.util.Map;
27+
import java.util.Set;
28+
29+
public class FutureAsyncAwaitPackageProcessor extends PerlPackageProcessorBase implements PerlPackageOptionsProvider {
30+
/**
31+
* These are actually keywords, but to avoid writing custom code for documentation/completion, we treat them here as exported methods
32+
* and lexer handles the rest.
33+
*/
34+
private static final List<String> METHODS = List.of("async", "await");
35+
36+
@Override
37+
public void addExports(@NotNull PerlUseStatementElement useStatement,
38+
@NotNull Set<? super String> export,
39+
@NotNull Set<? super String> exportOk) {
40+
export.addAll(METHODS);
41+
exportOk.addAll(METHODS);
42+
}
43+
44+
@Override
45+
public @NotNull Map<String, String> getOptions() {
46+
return Collections.emptyMap();
47+
}
48+
49+
@Override
50+
public @NotNull Map<String, String> getOptionsBundles() {
51+
return Collections.emptyMap();
52+
}
53+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<!--
2+
~ Copyright 2015-2020 Alexandr Evstigneev
3+
~
4+
~ Licensed under the Apache License, Version 2.0 (the "License");
5+
~ you may not use this file except in compliance with the License.
6+
~ You may obtain a copy of the License at
7+
~
8+
~ http://www.apache.org/licenses/LICENSE-2.0
9+
~
10+
~ Unless required by applicable law or agreed to in writing, software
11+
~ distributed under the License is distributed on an "AS IS" BASIS,
12+
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
~ See the License for the specific language governing permissions and
14+
~ limitations under the License.
15+
-->
16+
<xml>
17+
<package name="Future::AsyncAwait">
18+
<!-- This is actually a keyword used for async subs declarations -->
19+
<sub name="async"/>
20+
<!-- This is actually a keyword behaving like unary named operator -->
21+
<sub name="await">
22+
<arguments>
23+
<argument name="future" type="$"/>
24+
</arguments>
25+
</sub>
26+
</package>
27+
</xml>

plugin/src/main/resources/META-INF/plugin.xml

+3
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,8 @@
169169
implementationClass="com.perl5.lang.perl.extensions.packageprocessor.impl.FileSpecFunctionsProcessor"/>
170170
<packageProcessor key="Log::Log4perl"
171171
implementationClass="com.perl5.lang.perl.extensions.log4perl.Log4PerlPackageProcessor"/>
172+
<packageProcessor key="Future::AsyncAwait"
173+
implementationClass="com.perl5.lang.perl.extensions.futureAsyncAwait.FutureAsyncAwaitPackageProcessor"/>
172174

173175
<parserExtension implementation="com.perl5.lang.perl.parser.PerlSwitchParserExtensionImpl"/>
174176
<parserExtension implementation="com.perl5.lang.perl.parser.MooseParserExtension"/>
@@ -191,6 +193,7 @@
191193
<implicitSubsProvider implementation="com.perl5.lang.perl.extensions.readonly.ReadonlyImplicitDeclarationsProvider"/>
192194
<implicitSubsProvider implementation="com.perl5.lang.perl.extensions.log4perl.Log4PerlImplicitDeclarationsProvider"/>
193195
<implicitSubsProvider implementation="com.perl5.lang.perl.extensions.moo.MooImplicitSubsProvider"/>
196+
<implicitSubsProvider implementation="com.perl5.lang.perl.extensions.futureAsyncAwait.FutureAsyncAwaitImplicitDeclarationsProvider"/>
194197
</extensions>
195198

196199
<extensions defaultExtensionNs="com.intellij">

plugin/src/test/java/completion/PerlCompletionTest.java

+9-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2015-2023 Alexandr Evstigneev
2+
* Copyright 2015-2024 Alexandr Evstigneev
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -36,6 +36,14 @@ protected String getBaseDataPath() {
3636
return "completion/perl";
3737
}
3838

39+
@Test
40+
public void testAsyncAwait() { doTestFuture(); }
41+
42+
private void doTestFuture() {
43+
withFuture();
44+
doTestWithTypeText();
45+
}
46+
3947
@Test
4048
public void testUseCGISubs() { doTestCgi(); }
4149

plugin/src/test/java/documentation/PerlQuickDocTest.java

+13-6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2015-2023 Alexandr Evstigneev
2+
* Copyright 2015-2024 Alexandr Evstigneev
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -56,21 +56,28 @@ public void testIsaOperator() {
5656
}
5757

5858
@Test
59-
public void testAsyncSub() {
59+
public void testAwaitSub() {
60+
doTestFuture();
61+
}
62+
63+
private void doTestFuture() {
6064
withFuture();
6165
doTest528();
6266
}
6367

68+
@Test
69+
public void testAsyncSub() {
70+
doTestFuture();
71+
}
72+
6473
@Test
6574
public void testAsyncSubExpr() {
66-
withFuture();
67-
doTest528();
75+
doTestFuture();
6876
}
6977

7078
@Test
7179
public void testAsyncMethod() {
72-
withFuture();
73-
doTest528();
80+
doTestFuture();
7481
}
7582

7683
@Test

plugin/src/test/java/unit/perl/parser/PerlParserLikeTest.java

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2015-2023 Alexandr Evstigneev
2+
* Copyright 2015-2024 Alexandr Evstigneev
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -29,6 +29,9 @@ protected String getBaseDataPath() {
2929
@Test
3030
public void testSubSignatureDefault() { doTest(); }
3131

32+
@Test
33+
public void testAsyncAwait() { doTest(); }
34+
3235
@Test
3336
public void testUndefHash() { doTest(); }
3437

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
use Future::AsyncAwait;
2+
a<caret>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
Text: af; Tail: null; Type: after statement; Icon: /template.svg; Type Icon: null
2+
Lookups: af
3+
Live template: Perl5: Moose/af
4+
Text: ar; Tail: null; Type: around statement; Icon: /template.svg; Type Icon: null
5+
Lookups: ar
6+
Live template: Perl5: Moose/ar
7+
Text: as; Tail: null; Type: async sub definition; Icon: /template.svg; Type Icon: null
8+
Lookups: as
9+
Live template: Perl5/as
10+
Text: async; Tail: null; Type: Future::AsyncAwait; Icon: /subroutine_gutter_icon.png; Type Icon: null
11+
Lookups: async
12+
PsiElement: Implicit sub: async()
13+
Text: au; Tail: null; Type: augment statement; Icon: /template.svg; Type Icon: null
14+
Lookups: au
15+
Live template: Perl5: Moose/au
16+
Text: await; Tail: ($future); Type: Future::AsyncAwait; Icon: /subroutine_gutter_icon.png; Type Icon: null
17+
Lookups: await
18+
PsiElement: Implicit sub: await($future)($future)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
use Future::AsyncAwait;
2+
say aw<caret>ait something();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<p><a href="psi_element://Future%3A%3AAsyncAwait">Future::AsyncAwait</a>: <a href="psi_element://Future%3A%3AAsyncAwait%2FDESCRIPTION">DESCRIPTION</a></p><h2><code>await</code></h2><p style="padding-bottom: 10px;">The <code>await</code> keyword forms an expression which takes a <code>Future</code> instance as
2+
an operand and yields the eventual result of it. Superficially it can be
3+
thought of similar to invoking the <code>get</code> method on the future.</p>
4+
<div style="padding-bottom: 10px;"><pre><code> my $result = await $f;
5+
6+
my $result = $f-&gt;get;</code></pre></div>
7+
<p style="padding-bottom: 10px;">However, the key difference (and indeed the entire reason for being a new
8+
syntax keyword) is the behaviour when the future is still pending and is not
9+
yet complete. Whereas the simple <code>get</code> method would block until the future is
10+
complete, the <code>await</code> keyword causes its entire containing function to become
11+
suspended, making it return a new (pending) future instance. It waits in this
12+
state until the future it was waiting on completes, at which point it wakes up
13+
and resumes execution from the point of the <code>await</code> expression. When the
14+
now-resumed function eventually finishes (either by returning a value or
15+
throwing an exception), this value is set as the result of the future it had
16+
returned earlier.</p>
17+
<p style="padding-bottom: 10px;"><code>await</code> provides scalar context to its controlling expression.</p>
18+
<div style="padding-bottom: 10px;"><pre><code> async sub func {
19+
# this function is invoked in scalar context
20+
}
21+
22+
await func();</code></pre></div>
23+
<p style="padding-bottom: 10px;">Because the <code>await</code> keyword may cause its containing function to suspend
24+
early, returning a pending future instance, it is only allowed inside
25+
<code>async</code>-marked subs.</p>
26+
<p style="padding-bottom: 10px;">The converse is not true; just because a function is marked as <code>async</code> does
27+
not require it to make use of the <code>await</code> expression. It is still useful to
28+
turn the result of that function into a future, entirely without <code>await</code>ing
29+
on any itself.</p>
30+
<p style="padding-bottom: 10px;">Any function that doesn't actually await anything, and just returns immediate
31+
futures can be neatened by this module too.</p>
32+
<p style="padding-bottom: 10px;">Instead of writing</p>
33+
<div style="padding-bottom: 10px;"><pre><code> sub imm
34+
{
35+
...
36+
return Future-&gt;done( @result );
37+
}</code></pre></div>
38+
<p style="padding-bottom: 10px;">you can now simply write</p>
39+
<div style="padding-bottom: 10px;"><pre><code> async sub imm
40+
{
41+
...
42+
return @result;
43+
}</code></pre></div>
44+
<p style="padding-bottom: 10px;">with the added side-benefit that any exceptions thrown by the elided code will
45+
be turned into an immediate-failed <code>Future</code> rather than making the call
46+
itself propagate the exception, which is usually what you wanted when dealing
47+
with futures.</p>

0 commit comments

Comments
 (0)