add an option to propagate async/await contructs automatically

This commit is contained in:
Renaud Pawlak 2020-07-27 17:44:41 +02:00
parent bbb1bd1ddf
commit 80721ed55d
8 changed files with 227 additions and 6 deletions

View File

@ -624,6 +624,14 @@ public class JSweetCommandLineLauncher {
"PrinterAdapter#getClassMemberComparator(), to be overloaded by the user to "+
"implement the desired order.");
jsap.registerParameter(switchArg);
// Auto propagate async methods and await invocations
switchArg = new Switch(JSweetOptions.autoPropagateAsyncAwaits);
switchArg.setLongFlag(JSweetOptions.autoPropagateAsyncAwaits);
switchArg.setHelp(
"Propagate automatically the async modifier when a method invokes an async method "+
"and automatically adds await keywords when invoking async methods.");
jsap.registerParameter(switchArg);
return jsap;
}
@ -860,6 +868,9 @@ public class JSweetCommandLineLauncher {
if (jsapArgs.userSpecified(JSweetOptions.sortClassMembers)) {
transpiler.setSortClassMembers(jsapArgs.getBoolean(JSweetOptions.sortClassMembers));
}
if (jsapArgs.userSpecified(JSweetOptions.autoPropagateAsyncAwaits)) {
transpiler.setAutoPropagateAsyncAwaits(jsapArgs.getBoolean(JSweetOptions.autoPropagateAsyncAwaits));
}
if (tsOutputDir != null) {
transpiler.setTsOutputDir(tsOutputDir);

View File

@ -0,0 +1,91 @@
/*
* JSweet transpiler - http://www.jsweet.org
* Copyright (C) 2015 CINCHEO SAS <renaud.pawlak@cincheo.fr>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package org.jsweet.transpiler;
import java.util.List;
import org.jsweet.JSweetConfig;
import org.jsweet.transpiler.util.AbstractTreeScanner;
import com.sun.tools.javac.code.Symbol.MethodSymbol;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.JCTree.JCCompilationUnit;
import com.sun.tools.javac.tree.JCTree.JCMethodDecl;
import com.sun.tools.javac.tree.JCTree.JCMethodInvocation;
/**
* This AST scanner performs propagates async methods automatically.
*
* @author Renaud Pawlak
*/
public class AsyncAwaitPropagationScanner extends AbstractTreeScanner {
boolean stillWorking = false;
/**
* Creates a new global scanner.
*/
public AsyncAwaitPropagationScanner(JSweetContext context) {
super(null, context, null);
}
@Override
public void visitTopLevel(JCCompilationUnit topLevel) {
if (topLevel.packge.getQualifiedName().toString().startsWith(JSweetConfig.LIBS_PACKAGE + ".")) {
return;
}
this.compilationUnit = topLevel;
super.visitTopLevel(topLevel);
}
@Override
public void visitApply(JCMethodInvocation invocation) {
try {
MethodSymbol method = (MethodSymbol) invocation.meth.getClass().getField("sym").get(invocation.meth);
if (context.hasAnnotationType(method, JSweetConfig.ANNOTATION_ASYNC)
&& !"void".equals(method.getReturnType().toString())) {
JCMethodDecl parent = getParent(JCMethodDecl.class);
if (!context.hasAnnotationType(parent.sym, JSweetConfig.ANNOTATION_ASYNC)) {
context.addExtraAnnotationType(parent.sym, JSweetConfig.ANNOTATION_ASYNC);
stillWorking = true;
}
JCTree directParent = getParent();
if (!(context.isAwaitInvocation(invocation) || (directParent instanceof JCMethodInvocation
&& (((JCMethodInvocation) directParent).meth.toString().equals("await")
|| ((JCMethodInvocation) directParent).meth.toString().endsWith(".await"))))) {
context.addAwaitInvocation(invocation);
stillWorking = true;
}
}
} catch (Exception e) {
e.printStackTrace();
}
super.visitApply(invocation);
}
public void process(List<JCCompilationUnit> compilationUnits) {
do {
stillWorking = false;
for (JCCompilationUnit compilationUnit : compilationUnits) {
scan(compilationUnit);
}
} while (stillWorking);
}
}

View File

@ -1316,9 +1316,40 @@ public class JSweetContext extends Context {
}
}
}
if (extraAnnotations != null) {
for (String annotationType : annotationTypes) {
Set<Symbol> symbols = extraAnnotations.get(annotationType);
if (symbols != null && symbols.contains(symbol)) {
return true;
}
}
}
return hasActualAnnotationType(symbol, annotationTypes);
}
private Map<String, Set<Symbol>> extraAnnotations;
/**
* Adds an extra annotation type to a symbol (no args).
* See {@link #hasAnnotationType(Symbol, String...)}.
*
* @param symbol the symbol to add the annotation to
* @param annotationTypeName the annotation type name
*/
public void addExtraAnnotationType(Symbol symbol, String annotationTypeName) {
if (extraAnnotations == null) {
extraAnnotations = new HashMap<String, Set<Symbol>>();
}
Set<Symbol> symbols = extraAnnotations.get(annotationTypeName);
if (symbols == null) {
symbols = new HashSet<>();
extraAnnotations.put(annotationTypeName, symbols);
}
symbols.add(symbol);
}
/**
* Gets the actual name of a symbol from a JSweet convention, so including
* potential <code>jsweet.lang.Name</code> annotation.
@ -1961,4 +1992,21 @@ public class JSweetContext extends Context {
}
}
private Set<JCMethodInvocation> awaitInvocations;
public void addAwaitInvocation(JCMethodInvocation invocation) {
if (this.awaitInvocations == null) {
this.awaitInvocations = new HashSet<>();
}
this.awaitInvocations.add(invocation);
}
public boolean isAwaitInvocation(JCMethodInvocation invocation) {
if (this.awaitInvocations != null) {
return this.awaitInvocations.contains(invocation);
} else {
return false;
}
}
}

View File

@ -143,6 +143,11 @@ public interface JSweetOptions {
* Constant string for the 'sortClassMembers' option.
*/
String sortClassMembers = "sortClassMembers";
/**
* Constant string for the 'autoPropagateAsyncs' option.
*/
String autoPropagateAsyncAwaits = "autoPropagateAsyncAwaits";
/**
* All the supported options (used to report non-blocking errors when options do not exist).
@ -150,7 +155,8 @@ public interface JSweetOptions {
String[] options = { bundle, noRootDirectories, sourceMap, module, encoding, outEncoding, enableAssertions,
declaration, tsOnly, ignoreDefinitions, ignoreJavaErrors, header, disableSinglePrecisionFloats,
disableStaticsLazyInitialization, targetVersion, tsout, dtsout, jsout, candiesJsOut, moduleResolution,
extraSystemPath, useSingleQuotesForStringLiterals, nonEnumerableTransients, classpath, sortClassMembers };
extraSystemPath, useSingleQuotesForStringLiterals, nonEnumerableTransients, classpath, sortClassMembers,
autoPropagateAsyncAwaits };
/**
* Returns the configuration from the configuration file.
@ -375,4 +381,10 @@ public interface JSweetOptions {
* {@link PrinterAdapter#getClassMemberComparator()}.
*/
boolean isSortClassMembers();
/**
* If true, auto propagates async methods and await invocations.
*/
boolean isAutoPropagateAsyncAwaits();
}

View File

@ -243,7 +243,8 @@ public class JSweetTranspiler implements JSweetOptions {
private boolean useSingleQuotesForStringLiterals = false;
private boolean nonEnumerableTransients = false;
private boolean sortClassMembers = false;
private boolean autoPropagateAsyncAwaits = false;
private ArrayList<String> adapters = new ArrayList<>();
private File configurationFile;
@ -998,6 +999,10 @@ public class JSweetTranspiler implements JSweetOptions {
context.dumpOverloads(System.out);
}
if (isAutoPropagateAsyncAwaits()) {
new AsyncAwaitPropagationScanner(context).process(compilationUnits);
}
adapter.onTranspilationStarted();
String[] headerLines = getHeaderLines();
@ -1125,9 +1130,14 @@ public class JSweetTranspiler implements JSweetOptions {
}
new OverloadScanner(transpilationHandler, context).process(orderedCompilationUnits);
context.constAnalyzer = new ConstAnalyzer();
context.constAnalyzer.process(orderedCompilationUnits);
if (isAutoPropagateAsyncAwaits()) {
new AsyncAwaitPropagationScanner(context).process(compilationUnits);
}
adapter.onTranspilationStarted();
logger.debug("ordered compilation units: " + orderedCompilationUnits.stream().map(cu -> {
@ -1959,4 +1969,13 @@ public class JSweetTranspiler implements JSweetOptions {
this.sortClassMembers = sortClassMembers;
}
@Override
public boolean isAutoPropagateAsyncAwaits() {
return this.autoPropagateAsyncAwaits;
}
public void setAutoPropagateAsyncAwaits(boolean autoPropagateAsyncAwaits) {
this.autoPropagateAsyncAwaits = autoPropagateAsyncAwaits;
}
}

View File

@ -924,7 +924,7 @@ public class Java2TypeScriptTranslator extends AbstractTreePrinter {
@Override
public void visitApply(JCMethodInvocation invocation) {
// TODO: same for static variables
// TODO: same for static variables
if (invocation.meth instanceof JCIdent
&& JSweetConfig.TS_STRICT_MODE_KEYWORDS.contains(invocation.meth.toString().toLowerCase())) {
PackageSymbol invocationPackage = (PackageSymbol) ((JCIdent) invocation.meth).sym
@ -2683,13 +2683,13 @@ public class Java2TypeScriptTranslator extends AbstractTreePrinter {
boolean promisify = isAsyncMethod(methodDecl)
&& !methodDecl.restype.type.tsym.getQualifiedName().toString().endsWith(".Promise");
if (promisify) {
print(" Promise< ");
print("Promise<");
}
substituteAndPrintType(methodDecl.restype);
if (promisify) {
print(" > ");
print(">");
}
}
}
@ -3841,7 +3841,6 @@ public class Java2TypeScriptTranslator extends AbstractTreePrinter {
*/
@Override
public void visitApply(JCMethodInvocation inv) {
boolean debugMode = false;
if (context.options.isDebugMode()) {
if (Util.getAccessedSymbol(inv.meth) instanceof MethodSymbol) {
@ -3877,6 +3876,10 @@ public class Java2TypeScriptTranslator extends AbstractTreePrinter {
applyVarargs = false;
}
if (context.isAwaitInvocation(inv)) {
print("await ");
}
boolean anonymous = isAnonymousMethod(methName);
boolean targetIsThisOrStaticImported = /*
* !"super".equals(methName) &&

View File

@ -39,6 +39,7 @@ import org.junit.Test;
import source.extension.ToBeSorted;
import source.syntax.AnnotationQualifiedNames;
import source.syntax.AsyncAwaitPropagation;
import source.syntax.Casts;
import source.syntax.DocComments;
import source.syntax.DynamicInvoke;
@ -312,4 +313,13 @@ public class SyntaxTests extends AbstractTest {
transpile(TestTranspilationHandler::assertNoProblems, getSourceFile(MemberReferences.class));
}
@Test
public void testAsyncAwaitPropagation() {
transpilerTest().getTranspiler().setAutoPropagateAsyncAwaits(true);
transpile(ModuleKind.none, logHandler -> {
logHandler.assertNoProblems();
}, getSourceFile(AsyncAwaitPropagation.class));
transpilerTest().getTranspiler().setAutoPropagateAsyncAwaits(false);
}
}

View File

@ -0,0 +1,27 @@
package source.syntax;
import jsweet.lang.Async;
public class AsyncAwaitPropagation {
@Async int m1() {
return 0;
}
int m2() {
return 1 + m1();
}
void m3() {
if (m1() == 1) {
// do something
} else {
// do something else
}
}
int m4() {
return m2();
}
}