/*
* This file is part of the X10 project (http://x10-lang.org).
*
* This file is licensed to You under the Eclipse Public License (EPL);
* You may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.opensource.org/licenses/eclipse-1.0.php
*
* (C) Copyright IBM Corporation 2006-2010.
*/
package x10.ast;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import polyglot.ast.Cast;
import polyglot.ast.Cast_c;
import polyglot.ast.Expr;
import polyglot.ast.Node;
import polyglot.ast.Precedence;
import polyglot.ast.Term;
import polyglot.ast.TypeNode;
import polyglot.main.Options;
import polyglot.types.ClassDef;
import polyglot.types.ConstructorDef;
import polyglot.types.ConstructorInstance;
import polyglot.types.Context;
import polyglot.types.ErrorRef_c;
import polyglot.types.ObjectType;
import polyglot.types.SemanticException;
import polyglot.types.Type;
import polyglot.types.TypeSystem;
import polyglot.types.Types;
import polyglot.util.CodeWriter;
import polyglot.util.InternalCompilerError;
import polyglot.util.Position;
import polyglot.visit.CFGBuilder;
import polyglot.visit.ContextVisitor;
import polyglot.visit.NodeVisitor;
import polyglot.visit.PrettyPrinter;
import x10.ExtensionInfo;
import x10.X10CompilerOptions;
import x10.errors.Errors;
import x10.errors.Warnings;
import x10.types.ParameterType;
import x10.types.X10ClassType;
import x10.types.X10ParsedClassType;
import polyglot.types.TypeSystem;
import x10.types.checker.Converter;
import x10.types.checker.Converter.ConversionType;
import x10.types.constants.BooleanValue;
import x10.types.constants.CharValue;
import x10.types.constants.ClosureValue;
import x10.types.constants.ConstantValue;
import x10.types.constants.DoubleValue;
import x10.types.constants.FloatValue;
import x10.types.constants.IntegralValue;
import x10.types.constants.NullValue;
import x10.types.constants.StringValue;
/**
* Represent java cast operation.
* (CastType) expression
* This class is compliant with dependent type constraint.
* If a dynamic cast is needed, then some code is generated to check
* instance's value, field, etc... are valid against declared type constraint.
*
* @author vcave
*
*/
public class X10Cast_c extends Cast_c implements X10Cast, X10CastInfo {
protected Converter.ConversionType convert;
protected TypeNode castType;
protected Expr expr;
public X10Cast_c(Position pos, TypeNode castType, Expr expr, Converter.ConversionType convert) {
super(pos, castType, expr);
assert(castType != null && expr != null);
this.castType = castType;
this.expr = expr;
this.convert = convert;
}
public Converter.ConversionType conversionType() {
return convert;
}
public X10Cast conversionType(ConversionType convert) {
X10Cast_c n = (X10Cast_c) copy();
n.convert = convert;
return n;
}
public X10Cast exprAndConversionType(Expr expr, ConversionType convert) {
X10Cast_c n = (X10Cast_c) copy();
n.convert = convert;
n.expr = expr;
return n;
}
/** Get the cast type of the expression. */
public TypeNode castType() {
return this.castType;
}
/** Set the cast type of the expression. */
public X10Cast castType(TypeNode castType) {
X10Cast_c n = (X10Cast_c) copy();
n.castType = castType;
return n;
}
/** Get the expression being cast. */
public Expr expr() {
return this.expr;
}
/** Set the expression being cast. */
public X10Cast expr(Expr expr) {
X10Cast_c n = (X10Cast_c) copy();
n.expr = expr;
return n;
}
/** Reconstruct the expression. */
protected X10Cast_c reconstruct(TypeNode castType, Expr expr) {
if (castType != this.castType || expr != this.expr) {
X10Cast_c n = (X10Cast_c) copy();
n.castType = castType;
n.expr = expr;
return n;
}
return this;
}
@Override
public Precedence precedence() {
switch (convert) {
case PRIMITIVE:
case SUBTYPE:
return Precedence.CAST;
default:
return Precedence.UNKNOWN;
}
}
public Node typeCheck(ContextVisitor tc) {
if (castType() != null) {
try {
Types.checkMissingParameters(castType());
} catch (SemanticException e) {
Errors.issue(tc.job(), e, this);
}
}
try {
Expr e = Converter.converterChain(this, tc);
final Type type = e.type();
assert type != null;
assert ! (e instanceof X10Cast_c) || ((X10Cast_c) e).conversionType() != Converter.ConversionType.UNKNOWN_CONVERSION;
assert ! (e instanceof X10Cast_c) || ((X10Cast_c) e).conversionType() != Converter.ConversionType.UNKNOWN_IMPLICIT_CONVERSION;
// todo hack: after constraints will be kept at runtime, and we will do constraint solving at runtime, then all casts will be sound!
// X10 currently doesn't do constraint solving at runtime (and constraints are erased at runtime!),
// so given o:Any, a cast:
// o as Array[Int]
// is unsound if "o" had constraints (e.g., Array[Int{self!=0}])
// obviously, o as Array[Int{self!=0}] is always unsound.
// Note that any generic struct will this warning due to the auto-generated equals method:
//struct A[T] {
// public def equals(o:Any) {
// if (o instanceof A[T]) {
// val x = o as A[T]; // Warning: unsound cast!
// ...
// Therefore we do not produce warnings in compiler-generated code (too confusing for the programmer).
// In addition, I also don't report the 3 warnings we have in XRX (or else every client of HashMap will have a warning)
if (!position.isCompilerGenerated() &&
!position.file().contains("Accumulator.x10")&&
!position.file().contains("Array.x10")&&
!position.file().contains("Box.x10")&&
!position.file().contains("HashMap.x10")&&
!position.file().contains("FinishState.x10")&&
!position.file().contains("Runtime.x10")&&
!position.file().contains("HashSet.x10")) {
Type base = Types.baseType(type);
boolean isClassType = base instanceof X10ParsedClassType;
boolean isParamType = base instanceof ParameterType;
if (isClassType || isParamType) {
List<Type> args = null;
if (isClassType) {
X10ParsedClassType classType = (X10ParsedClassType) base;
args = classType.typeArguments();
}
if (isParamType || (args!=null && args.size()>0)) {
boolean isOk = false;
if (e instanceof X10Cast) {
// ok, e.g., x:Array[Int], x as Array[Int](3)
final X10Cast cast = (X10Cast) e;
if (cast.conversionType()== ConversionType.SUBTYPE)
isOk = true;
else if (tc.typeSystem().isSubtype(Types.baseType(cast.expr().type()),base, tc.context()))
isOk = true;
}
if (!isOk) {
final ExtensionInfo extensionInfo = (ExtensionInfo) tc.job().extensionInfo();
X10CompilerOptions opts = extensionInfo.getOptions();
if (opts.x10_config.VERBOSE) { // it used to be VERBOSE_CHECKS, but then how do I get this error if I want to run with STATIC_CHECKS???
Warnings.issue(tc.job(), "This is an unsound cast because X10 currently does not perform constraint solving at runtime for generic parameters.", position);
}
}
}
}
}
return e;
} catch (SemanticException e) {
Errors.issue(tc.job(), e, this);
}
return this;
}
public TypeNode getTypeNode() {
return (TypeNode) this.castType().copy();
}
public String toString() {
return expr.toString() + " as " + castType.toString();
}
@Override
public List<Type> throwTypes(TypeSystem ts) {
// 'e as T' and 'e to T' can throw ClassCastException
if (expr.type().isReference()) {
return Collections.<Type>singletonList(ts.ClassCastException());
}
return Collections.<Type>emptyList();
}
/** Write the expression to an output file. */
public void prettyPrint(CodeWriter w, PrettyPrinter tr)
{
if (convert == ConversionType.UNBOXING) {
w.begin(0);
print(castType, w, tr);
w.write(".$unbox(");
printSubExpr(expr, w, tr);
w.write(")");
w.end();
} else if (convert == ConversionType.BOXING) {
w.begin(0);
print(castType, w, tr);
w.write(".$box(");
printSubExpr(expr, w, tr);
w.write(")");
w.end();
} else {
w.begin(0);
w.write("(");
print(castType, w, tr);
w.write(")");
w.allowBreak(2, " ");
printSubExpr(expr, w, tr);
w.end();
}
}
public Term firstChild() {
return expr;
}
public <S> List<S> acceptCFG(CFGBuilder v, List<S> succs) {
v.visitCFG(expr, castType, ENTRY);
v.visitCFG(castType, this, EXIT);
return succs;
}
/** Visit the children of the expression. */
public Node visitChildren(NodeVisitor v) {
TypeNode castType = (TypeNode) visitChild(this.castType, v);
Expr expr = (Expr) visitChild(this.expr, v);
return reconstruct(castType, expr);
}
public boolean isConstant() {
if (!expr.isConstant()) return false;
boolean ctIntrinsic = castType.type().isNumeric() || castType.type().isUnsignedNumeric() || castType.type().isChar();
boolean etIntrinsic = expr.type().isNumeric() || expr.type().isUnsignedNumeric() || expr.type().isChar();
if (ctIntrinsic && etIntrinsic) return true;
TypeSystem ts = expr.type().typeSystem();
ConstantValue exprCV = expr.constantValue();
if (exprCV instanceof NullValue && ts.isObjectOrInterfaceType(castType.type(), ts.emptyContext())) return true;
if (exprCV instanceof StringValue && ts.String().isSubtype(castType.type(), ts.emptyContext())) return true;
if (exprCV instanceof BooleanValue && ts.Boolean().isSubtype(castType.type(), ts.emptyContext())) return true;
if (exprCV instanceof ClosureValue) return true;
// NOTE: The cast might still be statically removable, but that is different than having a constant value.
return false;
}
public ConstantValue constantValue() {
ConstantValue v = expr.constantValue();
if (v == null) {
return null;
}
Type cType = castType.type();
if (v instanceof IntegralValue) {
return ConstantValue.make(cType, ((IntegralValue) v).longValue());
}
if (v instanceof DoubleValue) {
return ConstantValue.make(cType, ((DoubleValue) v).value());
}
if (v instanceof FloatValue) {
return ConstantValue.make(cType, ((FloatValue) v).value());
}
if (v instanceof CharValue) {
return ConstantValue.make(cType, (long)((CharValue)v).value());
}
TypeSystem ts = cType.typeSystem();
Context emptyContext = ts.emptyContext();
if (v instanceof StringValue) {
if (ts.String().isSubtype(cType, emptyContext)) return v;
}
if (v instanceof BooleanValue) {
if (ts.Boolean().isSubtype(cType, emptyContext)) return v;
}
if (v instanceof NullValue) {
if (ts.isObjectOrInterfaceType(cType, emptyContext)) return v;
}
if (v instanceof ClosureValue) {
return v;
}
return null;
}
}