Skip to content

Commit

Permalink
Implement instanceof patterns (Java 16).
Browse files Browse the repository at this point in the history
PiperOrigin-RevId: 698591831
  • Loading branch information
rluble authored and copybara-github committed Nov 21, 2024
1 parent cc545e2 commit b82bd44
Show file tree
Hide file tree
Showing 14 changed files with 324 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,23 +18,30 @@
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;

import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.google.j2cl.common.SourcePosition;
import com.google.j2cl.common.visitor.Processor;
import com.google.j2cl.common.visitor.Visitable;
import com.google.j2cl.transpiler.ast.Expression.Precedence;
import javax.annotation.Nullable;

/** Class for instanceof Expression. */
@Visitable
public class InstanceOfExpression extends Expression implements HasSourcePosition {
@Visitable Expression expression;
@Visitable TypeDescriptor testTypeDescriptor;
@Visitable @Nullable Variable patternVariable;
private final SourcePosition sourcePosition;

private InstanceOfExpression(
SourcePosition sourcePosition, Expression expression, TypeDescriptor testTypeDescriptor) {
SourcePosition sourcePosition,
Expression expression,
TypeDescriptor testTypeDescriptor,
Variable patternVariable) {
this.expression = checkNotNull(expression);
this.testTypeDescriptor = checkNotNull(testTypeDescriptor);
this.sourcePosition = sourcePosition;
this.patternVariable = patternVariable;
checkArgument(
testTypeDescriptor instanceof DeclaredTypeDescriptor
|| testTypeDescriptor instanceof ArrayTypeDescriptor);
Expand All @@ -53,6 +60,10 @@ public TypeDescriptor getTypeDescriptor() {
return PrimitiveTypes.BOOLEAN;
}

public Variable getPatternVariable() {
return patternVariable;
}

@Override
public boolean isIdempotent() {
return expression.isIdempotent();
Expand All @@ -66,7 +77,8 @@ public Precedence getPrecedence() {

@Override
public InstanceOfExpression clone() {
return new InstanceOfExpression(sourcePosition, expression.clone(), testTypeDescriptor);
return new InstanceOfExpression(
sourcePosition, expression.clone(), testTypeDescriptor, patternVariable);
}

@Override
Expand All @@ -87,31 +99,44 @@ public static Builder newBuilder() {
public static class Builder {
private Expression expression;
private TypeDescriptor testTypeDescriptor;
private Variable patternVariable;
private SourcePosition sourcePosition;

public static Builder from(InstanceOfExpression instanceOfExpression) {
return new Builder()
.setExpression(instanceOfExpression.getExpression())
.setTestTypeDescriptor(instanceOfExpression.getTestTypeDescriptor());
.setTestTypeDescriptor(instanceOfExpression.getTestTypeDescriptor())
.setPatternVariable(instanceOfExpression.getPatternVariable())
.setSourcePosition(instanceOfExpression.getSourcePosition());
}

@CanIgnoreReturnValue
public Builder setSourcePosition(SourcePosition sourcePosition) {
this.sourcePosition = sourcePosition;
return this;
}

@CanIgnoreReturnValue
public Builder setExpression(Expression expression) {
this.expression = expression;
return this;
}

public Builder setTestTypeDescriptor(TypeDescriptor castTypeDescriptor) {
this.testTypeDescriptor = castTypeDescriptor;
@CanIgnoreReturnValue
public Builder setTestTypeDescriptor(TypeDescriptor testTypeDescriptor) {
this.testTypeDescriptor = testTypeDescriptor;
return this;
}

@CanIgnoreReturnValue
public Builder setPatternVariable(Variable patternVariable) {
this.patternVariable = patternVariable;
return this;
}

public InstanceOfExpression build() {
return new InstanceOfExpression(sourcePosition, expression, testTypeDescriptor);
return new InstanceOfExpression(
sourcePosition, expression, testTypeDescriptor, patternVariable);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import com.google.j2cl.transpiler.passes.AddVisibilityMethodBridgesJ2kt;
import com.google.j2cl.transpiler.passes.ConvertMethodReferencesToLambdas;
import com.google.j2cl.transpiler.passes.CreateImplicitConstructors;
import com.google.j2cl.transpiler.passes.DesugarInstanceOfPatterns;
import com.google.j2cl.transpiler.passes.DevirtualizeBoxedTypesAndJsFunctionImplementations;
import com.google.j2cl.transpiler.passes.DevirtualizeMethodCalls;
import com.google.j2cl.transpiler.passes.ExpandCompoundAssignments;
Expand Down Expand Up @@ -212,6 +213,7 @@ public ImmutableList<Supplier<NormalizationPass>> getDesugaringPassFactories() {
// Early run of determining whether variables are effectively final so that passes that
// depend on Expression.isEffectivelyInvariant it can take advantage.
MakeVariablesFinal::new,
DesugarInstanceOfPatterns::new,
ConvertMethodReferencesToLambdas::new,
NormalizePackagedJsEnumVarargsLiterals::new,
ResolveImplicitInstanceQualifiers::new,
Expand Down Expand Up @@ -399,6 +401,7 @@ public ImmutableList<Supplier<NormalizationPass>> getDesugaringPassFactories() {
// TODO(b/277799806): Consider removing this pass if the immutable field optimization is
// removed.
MakeVariablesFinal::new,
DesugarInstanceOfPatterns::new,
ConvertMethodReferencesToLambdas::new,
NormalizePackagedJsEnumVarargsLiterals::new,
ResolveImplicitInstanceQualifiers::new,
Expand Down Expand Up @@ -548,6 +551,7 @@ public ImmutableList<Supplier<NormalizationPass>> getDesugaringPassFactories() {
// TODO(b/277799806): Consider removing this pass if the immutable field optimization is
// removed.
MakeVariablesFinal::new,
DesugarInstanceOfPatterns::new,
ConvertMethodReferencesToLambdas::new,
NormalizePackagedJsEnumVarargsLiterals::new,
ResolveImplicitInstanceQualifiers::new,
Expand Down Expand Up @@ -688,6 +692,7 @@ public void generateOutputs(BackendOptions options, Library library, Problems pr
public ImmutableList<Supplier<NormalizationPass>> getDesugaringPassFactories() {
return ImmutableList.of(
MakeVariablesFinal::new,
DesugarInstanceOfPatterns::new,
ConvertMethodReferencesToLambdas::new,
ResolveImplicitInstanceQualifiers::new);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -783,12 +783,17 @@ private IfStatement convert(org.eclipse.jdt.core.dom.IfStatement statement) {
.build();
}

private InstanceOfExpression convert(org.eclipse.jdt.core.dom.InstanceofExpression expression) {
private Expression convert(org.eclipse.jdt.core.dom.InstanceofExpression expression) {
Expression e = convert(expression.getLeftOperand());
TypeDescriptor typeDescriptor =
environment.createTypeDescriptor(expression.getRightOperand().resolveBinding());
Variable patternVariable =
expression.getPatternVariable() == null ? null : convert(expression.getPatternVariable());
return InstanceOfExpression.newBuilder()
.setSourcePosition(getSourcePosition(expression))
.setExpression(convert(expression.getLeftOperand()))
.setTestTypeDescriptor(
environment.createTypeDescriptor(expression.getRightOperand().resolveBinding()))
.setExpression(e)
.setTestTypeDescriptor(typeDescriptor)
.setPatternVariable(patternVariable)
.build();
}

Expand Down Expand Up @@ -1230,7 +1235,7 @@ private Expression convert(org.eclipse.jdt.core.dom.SimpleName expression) {
}

throw internalCompilerError(
"Unexpected binding class for SimpleName: %s", expression.getClass().getName());
"Unexpected binding class for SimpleName: %s", binding.getClass().getName());
}

private Variable convert(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ public JdtParser(Problems problems) {
compilerOptions.put(JavaCore.COMPILER_SOURCE, JAVA_VERSION);
compilerOptions.put(JavaCore.COMPILER_CODEGEN_TARGET_PLATFORM, JAVA_VERSION);
compilerOptions.put(JavaCore.COMPILER_COMPLIANCE, JAVA_VERSION);
compilerOptions.put(JavaCore.COMPILER_PB_ENABLE_PREVIEW_FEATURES, "enabled");

this.problems = problems;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/*
* Copyright 2024 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.j2cl.transpiler.passes;

import com.google.j2cl.transpiler.ast.AbstractRewriter;
import com.google.j2cl.transpiler.ast.CastExpression;
import com.google.j2cl.transpiler.ast.CompilationUnit;
import com.google.j2cl.transpiler.ast.ConditionalExpression;
import com.google.j2cl.transpiler.ast.Expression;
import com.google.j2cl.transpiler.ast.InstanceOfExpression;
import com.google.j2cl.transpiler.ast.MultiExpression;
import com.google.j2cl.transpiler.ast.Node;
import com.google.j2cl.transpiler.ast.TypeDescriptor;
import com.google.j2cl.transpiler.ast.Variable;
import com.google.j2cl.transpiler.ast.VariableDeclarationExpression;
import com.google.j2cl.transpiler.ast.VariableReference;

/** Normalizes instanceof patterns out. */
public class DesugarInstanceOfPatterns extends NormalizationPass {
@Override
public void applyTo(CompilationUnit compilationUnit) {
compilationUnit.accept(
new AbstractRewriter() {
@Override
public Node rewriteInstanceOfExpression(InstanceOfExpression instanceOfExpression) {
if (instanceOfExpression.getPatternVariable() == null) {
return instanceOfExpression;
}

Expression expression = instanceOfExpression.getExpression();
Variable patternVariable = instanceOfExpression.getPatternVariable();
TypeDescriptor testTypeDescriptor = instanceOfExpression.getTestTypeDescriptor();

// The instanceof has a pattern and will be represented as a multi expression as
// follows:
// ( ExpType exp = expression, // avoid double evaluation of expression
// PatternVarType v =
// exp instanceof T ? (T) exp : null, // Cast exp and assign to pattern variable
// v != null) // return the result of instanceof.

var variableDeclarationBuilder = VariableDeclarationExpression.newBuilder();

// Always use a variable for the expression that needs to be evaluated twice to
// prevent increasing code size and make the code more readable.
Variable expressionVariable;
if (expression instanceof VariableReference) {
// If it is already a variable just use it.
expressionVariable = ((VariableReference) expression).getTarget();
} else {
// Create a new variable to avoid evaluating expression twice.
expressionVariable =
Variable.newBuilder()
.setName("exp")
.setFinal(true)
.setTypeDescriptor(expression.getTypeDescriptor())
.build();
variableDeclarationBuilder.addVariableDeclaration(expressionVariable, expression);
}
// Declare and initialize the pattern variable.
//
// PatternVarType patternVariable =
// exp instanceof T ? (T) exp : null
variableDeclarationBuilder.addVariableDeclaration(
patternVariable,
ConditionalExpression.newBuilder()
.setTypeDescriptor(patternVariable.getTypeDescriptor())
.setConditionExpression(
InstanceOfExpression.Builder.from(instanceOfExpression)
.setExpression(expressionVariable.createReference())
.setPatternVariable(null)
.build())
.setTrueExpression(
CastExpression.newBuilder()
.setExpression(expressionVariable.createReference())
.setCastTypeDescriptor(testTypeDescriptor)
.build())
.setFalseExpression(testTypeDescriptor.getNullValue())
.build());

return MultiExpression.newBuilder()
.addExpressions(
variableDeclarationBuilder.build(),
// patternVariable != null is the actual value of this multiexpression.
patternVariable.createReference().infixNotEqualsNull())
.build();
}
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import com.google.j2cl.transpiler.ast.ForEachStatement;
import com.google.j2cl.transpiler.ast.FunctionExpression;
import com.google.j2cl.transpiler.ast.InitializerBlock;
import com.google.j2cl.transpiler.ast.InstanceOfExpression;
import com.google.j2cl.transpiler.ast.JavaScriptConstructorReference;
import com.google.j2cl.transpiler.ast.JsForInStatement;
import com.google.j2cl.transpiler.ast.LabeledStatement;
Expand Down Expand Up @@ -210,6 +211,11 @@ public void exitMultiExpression(MultiExpression multiExpression) {
checkState(multiExpression.getExpressions().size() > 1);
}

@Override
public void exitInstanceOfExpression(InstanceOfExpression instanceOfExpression) {
checkState(instanceOfExpression.getPatternVariable() == null);
}

@Override
public void exitNewArray(NewArray newArray) {
if (verifyForWasm) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,8 @@ integration_test(
name = "instanceofs",
srcs = glob(["*.java"]),
enable_kt = False,
javacopts = [
"-source 17",
"-target 17",
],
)
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ public static void main(String... args) {
testInstanceOf_string();
testInstanceOf_sideEffects();
testInstanceOf_markerInterfaces();
testInstanceOf_patternVariable();
}

private static void testInstanceOf_class() {
Expand Down Expand Up @@ -373,4 +374,30 @@ class B implements MarkerB {}
interface MarkerA {}

interface MarkerB {}

private static void testInstanceOf_patternVariable() {
String hello = "hello";
Object o = hello;
assertTrue(o instanceof String s && s.length() == hello.length());
assertEquals(hello.length(), o instanceof String s ? s.length() : 0);

String bye = "bye";
o = bye;
if (!(o instanceof String s)) {
throw new AssertionError();
}
// The variable s is in scope here.
assertEquals(bye.length(), s.length());

o = Integer.valueOf(1);
while (o instanceof Number n) {
assertEquals(1, n.intValue());
break;
}

do {
o = Integer.valueOf(2);
} while (!(o instanceof Number n));
assertEquals(2, n.intValue());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,8 @@ package(

readable_example(
srcs = glob(["*.java"]),
javacopts = [
"-source 17",
"-target 17",
],
)
Original file line number Diff line number Diff line change
Expand Up @@ -90,4 +90,26 @@ public void testInstanceOfArray() {
public void testPrecedence() {
boolean b = (false ? "foo" : "bar") instanceof String;
}

private void testPatternMatch() {
if (!(new Object() instanceof String s)
// the rhs of the short-circuit or only evaluates in the lhs is false, which makes
// instanceof true and hence the variable s has meaning there.
|| s.length() == 2) {
return;
}
if (new Object() instanceof Integer i
// since instanceof succeeded here we can immediately use the variable as the rhs of the
// and expression.
&& i.intValue() == 2) {
int l = i.intValue();
}
if (!(new Object() instanceof Number n)) {
// Since we only reach this point in the program with the instanceof operation from the
// first if statement here we can refer to that variable.
int j = s.length();
} else {
int k = n.byteValue();
}
}
}
Loading

0 comments on commit b82bd44

Please sign in to comment.