blob: f75f95cbc20faa99226545702d9372081637eb66 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2022 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.jdt.internal.compiler.ast;
import org.eclipse.jdt.internal.compiler.ASTVisitor;
import org.eclipse.jdt.internal.compiler.codegen.BranchLabel;
import org.eclipse.jdt.internal.compiler.codegen.CodeStream;
import org.eclipse.jdt.internal.compiler.codegen.Opcodes;
import org.eclipse.jdt.internal.compiler.flow.FlowContext;
import org.eclipse.jdt.internal.compiler.flow.FlowInfo;
import org.eclipse.jdt.internal.compiler.lookup.BlockScope;
import org.eclipse.jdt.internal.compiler.lookup.LocalVariableBinding;
import org.eclipse.jdt.internal.compiler.lookup.MethodBinding;
import org.eclipse.jdt.internal.compiler.lookup.RecordComponentBinding;
import org.eclipse.jdt.internal.compiler.lookup.Scope;
import org.eclipse.jdt.internal.compiler.lookup.TypeBinding;
public class RecordPattern extends TypePattern {
public Pattern[] patterns;
public TypeReference type;
int thenInitStateIndex1 = -1;
int thenInitStateIndex2 = -1;
public RecordPattern(LocalDeclaration local) {
super(local);
this.type = local.type;
this.sourceStart = local.sourceStart;
this.sourceEnd = local.sourceEnd;
}
public RecordPattern(TypeReference type, int sourceStart, int sourceEnd) {
super();
this.type = type;
this.sourceStart = sourceStart;
this.sourceEnd = sourceEnd;
}
@Override
public TypeReference getType() {
return this.type;
}
@Override
public void collectPatternVariablesToScope(LocalVariableBinding[] variables, BlockScope scope) {
if (this.resolvedType == null) {
this.resolveType(scope);
}
this.addPatternVariablesWhenTrue(variables);
super.collectPatternVariablesToScope(variables, scope);
for (Pattern p : this.patterns) {
p.collectPatternVariablesToScope(variables, scope);
this.addPatternVariablesWhenTrue(p.patternVarsWhenTrue);
}
}
@Override
public boolean checkUnsafeCast(Scope scope, TypeBinding castType, TypeBinding expressionType, TypeBinding match, boolean isNarrowing) {
if (!castType.isReifiable())
return CastExpression.checkUnsafeCast(this, scope, castType, expressionType, match, isNarrowing);
else
return super.checkUnsafeCast(scope, castType, expressionType, match, isNarrowing);
}
@Override
public LocalDeclaration getPatternVariable() {
return super.getPatternVariable();
}
@Override
public FlowInfo analyseCode(BlockScope currentScope, FlowContext flowContext, FlowInfo flowInfo) {
this.thenInitStateIndex1 = currentScope.methodScope().recordInitializationStates(flowInfo);
flowInfo = super.analyseCode(currentScope, flowContext, flowInfo);
for (Pattern p : this.patterns) {
p.analyseCode(currentScope, flowContext, flowInfo);
}
flowInfo = flowInfo.safeInitsWhenTrue();
this.thenInitStateIndex2 = currentScope.methodScope().recordInitializationStates(flowInfo);
return flowInfo;
}
@Override
public boolean isTotalForType(TypeBinding t) {
return false;
}
@Override
public void resolveWithExpression(BlockScope scope, Expression exp) {
this.expression = exp;
}
@Override
public TypeBinding resolveAtType(BlockScope scope, TypeBinding u) {
for (Pattern p : this.patterns) {
p.resolveAtType(scope, u);
}
if (this.local != null) {
this.resolvedType = super.resolveAtType(scope, u);
}
return this.resolvedType;
}
@Override
public TypeBinding resolveType(BlockScope scope, boolean isPatternVariable) {
if (this.resolvedType != null)
return this.resolvedType;
super.resolveType(scope, isPatternVariable);
if (this.local != null) {
this.resolvedType = super.resolveType(scope);
} else {
this.resolvedType = this.type.resolveType(scope);
}
if (!this.resolvedType.isValidBinding())
return this.resolvedType;
initSecretPatternVariable(scope);
// check whether the give type reference is a record
// check whether a raw type is being used in pattern types
// check whether the pattern signature matches that of the record declaration
if (!this.resolvedType.isRecord()) {
scope.problemReporter().unexpectedTypeinRecordPattern(this.resolvedType, this.type);
return this.resolvedType;
} else if (this.resolvedType.isRawType()) {
scope.problemReporter().rawTypeInRecordPattern(this.resolvedType, this.type);
}
this.isTotalTypeNode = isTotalForType(this.resolvedType);
RecordComponentBinding[] components = this.resolvedType.components();
if (components.length != this.patterns.length) {
scope.problemReporter().recordPatternSignatureMismatch(this.resolvedType, this);
} else {
for (int i = 0; i < components.length; i++) {
Pattern p = this.patterns[i];
if (!(p instanceof TypePattern))
continue;
TypePattern tp = (TypePattern) p;
RecordComponentBinding componentBinding = components[i];
if (p.getType().isTypeNameVar(scope)) {
infuseInferredType(tp, componentBinding);
}
p.resolveType(scope, true);
TypeBinding expressionType = componentBinding.type;
if (p.isPatternTypeCompatible(expressionType, scope)) {
p.isTotalTypeNode = p.isTotalForType(componentBinding.type);
MethodBinding[] methods = this.resolvedType.getMethods(componentBinding.name);
if (methods != null && methods.length > 0) {
p.accessorMethod = methods[0];
}
}
}
}
return this.resolvedType;
}
private void infuseInferredType(TypePattern tp, RecordComponentBinding componentBinding) {
SingleTypeReference ref = new SingleTypeReference(tp.local.type.getTypeName()[0],
tp.local.type.sourceStart,
tp.local.type.sourceEnd) {
@Override
public TypeBinding resolveType(BlockScope scope, boolean checkBounds) {
return componentBinding.type;
}
};
tp.local.type = ref;
}
@Override
public boolean isAlwaysTrue() {
return false;
}
@Override
public boolean dominates(Pattern p) {
if (!this.resolvedType.isValidBinding())
return false;
if (!super.isTotalForType(p.resolvedType)) {
return false;
}
if (p instanceof RecordPattern) {
RecordPattern rp = (RecordPattern) p;
if (this.patterns.length != rp.patterns.length)
return false;
for(int i = 0; i < this.patterns.length; i++) {
if (!this.patterns[i].dominates(rp.patterns[i])) {
return false;
}
}
}
return true;
}
@Override
public void generateOptimizedBoolean(BlockScope currentScope, CodeStream codeStream, BranchLabel trueLabel, BranchLabel falseLabel) {
codeStream.checkcast(this.resolvedType);
initializePatternVariables(currentScope, codeStream);
generatePatternVariable(currentScope, codeStream, trueLabel, falseLabel);
wrapupGeneration(codeStream);
if (this.thenInitStateIndex2 != -1) {
codeStream.removeNotDefinitelyAssignedVariables(currentScope, this.thenInitStateIndex2);
codeStream.addDefinitelyAssignedVariables(currentScope, this.thenInitStateIndex2);
}
}
@Override
protected void generatePatternVariable(BlockScope currentScope, CodeStream codeStream, BranchLabel trueLabel, BranchLabel falseLabel) {
if (!this.isTotalTypeNode) {
codeStream.load(this.secretPatternVariable);
codeStream.instance_of(this.resolvedType);
BranchLabel target = falseLabel != null ? falseLabel : new BranchLabel(codeStream);
codeStream.ifeq(target);
}
for (Pattern p : this.patterns) {
if (p.accessorMethod != null) {
codeStream.load(this.secretPatternVariable);
if (!this.isTotalTypeNode)
codeStream.checkcast(this.resolvedType);
generateArguments(p.accessorMethod, null, currentScope, codeStream);
codeStream.invoke(Opcodes.OPC_invokevirtual, p.accessorMethod, this.resolvedType, null);
if (!p.isTotalTypeNode) {
if (p instanceof TypePattern) {
((TypePattern)p).initializePatternVariables(currentScope, codeStream);
codeStream.load(p.secretPatternVariable);
codeStream.instance_of(p.resolvedType);
BranchLabel target = falseLabel != null ? falseLabel : new BranchLabel(codeStream);
codeStream.ifeq(target);
codeStream.load(p.secretPatternVariable);
}
}
p.generateOptimizedBoolean(currentScope, codeStream, trueLabel, falseLabel);
}
}
super.generatePatternVariable(currentScope, codeStream, trueLabel, falseLabel);
}
@Override
public void wrapupGeneration(CodeStream codeStream) {
for (Pattern p : this.patterns) {
p.wrapupGeneration(codeStream);
}
super.wrapupGeneration(codeStream);
}
@Override
public void suspendVariables(CodeStream codeStream, BlockScope scope) {
codeStream.removeNotDefinitelyAssignedVariables(scope, this.thenInitStateIndex1);
}
@Override
public void resumeVariables(CodeStream codeStream, BlockScope scope) {
codeStream.addDefinitelyAssignedVariables(scope, this.thenInitStateIndex2);
}
@Override
public void traverse(ASTVisitor visitor, BlockScope scope) {
for (Pattern p : this.patterns) {
visitor.visit(p, scope);
}
if (visitor.visit(this, scope)) {
if (this.local != null)
this.local.traverse(visitor, scope);
else if (this.type != null) {
this.type.traverse(visitor, scope);
}
for (Pattern p : this.patterns) {
p.traverse(visitor, scope);
}
}
visitor.endVisit(this, scope);
}
@Override
public StringBuffer printExpression(int indent, StringBuffer output) {
output.append(this.type).append('(');
if (this.patterns != null) {
for (int i = 0; i < this.patterns.length; i++) {
if (i > 0) output.append(", "); //$NON-NLS-1$
this.patterns[i].print(0, output);
}
}
output.append(')');
if (this.local != null)
output.append(' ').append(this.local.name);
return output;
}
}