/*
* 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.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.HashSet;
import java.util.Set;
import polyglot.ast.Expr;
import polyglot.ast.FieldDecl_c;
import polyglot.ast.FlagsNode;
import polyglot.ast.FloatLit;
import polyglot.ast.Id;
import polyglot.ast.IntLit;
import polyglot.ast.Node;
import polyglot.ast.NodeFactory;
import polyglot.ast.StringLit;
import polyglot.ast.TypeNode;
import polyglot.ast.CanonicalTypeNode;
import polyglot.frontend.AbstractGoal_c;
import polyglot.frontend.Globals;
import polyglot.frontend.Goal;
import polyglot.types.ClassDef;
import polyglot.types.Context;
import polyglot.types.FieldDef;
import polyglot.types.Flags;
import polyglot.types.InitializerDef;
import polyglot.types.LazyRef;
import polyglot.types.Name;
import polyglot.types.QName;
import polyglot.types.Ref;
import polyglot.types.SemanticException;
import polyglot.types.ContainerType;
import polyglot.types.Type;
import polyglot.types.TypeSystem;
import polyglot.types.Types;
import polyglot.types.VarDef_c.ConstantValue;
import polyglot.util.CodeWriter;
import polyglot.util.CollectionUtil; import x10.util.CollectionFactory;
import polyglot.util.Position;
import polyglot.visit.ContextVisitor;
import polyglot.visit.NodeVisitor;
import polyglot.visit.PrettyPrinter;
import polyglot.visit.TypeBuilder;
import polyglot.visit.TypeCheckPreparer;
import polyglot.visit.TypeChecker;
import x10.constraint.XTerm;
import x10.errors.Errors;
import x10.extension.X10Del;
import x10.extension.X10Del_c;
import x10.extension.X10Ext;
import x10.types.ParameterType;
import x10.types.X10ClassDef;
import x10.types.X10ClassType;
import polyglot.types.Context;
import x10.types.X10Def;
import x10.types.X10FieldDef;
import x10.types.X10InitializerDef;
import x10.types.X10FieldDef_c;
import x10.types.X10ParsedClassType;
import x10.types.X10ParsedClassType_c;
import x10.types.X10ClassDef_c;
import polyglot.types.TypeSystem;
import polyglot.types.FieldInstance;
import x10.types.checker.Checker;
import x10.types.checker.Converter;
import x10.types.checker.PlaceChecker;
import x10.types.constraints.CConstraint;
import x10.types.constraints.TypeConstraint;
import x10.types.constraints.XConstrainedTerm;
import x10.visit.X10TypeChecker;
public class X10FieldDecl_c extends FieldDecl_c implements X10FieldDecl {
TypeNode hasType;
public Type hasType() {
return hasType==null ? null : hasType.type();
}
public X10FieldDecl_c(NodeFactory nf, Position pos, FlagsNode flags, TypeNode type,
Id name, Expr init)
{
super(pos, flags,
type instanceof HasTypeNode_c
? nf.UnknownTypeNode(type.position())
: type, name, init);
if (type instanceof HasTypeNode_c)
hasType = ((HasTypeNode_c) type).typeNode();
}
@Override
public Context enterScope(Context c) {
c = super.enterScope(c);
if (!c.inStaticContext() && fieldDef().thisDef() != null)
c.addVariable(fieldDef().thisDef().asInstance());
return c;
}
@Override
public X10FieldDecl_c flags(FlagsNode flags) {
return (X10FieldDecl_c) super.flags(flags);
}
@Override
public X10FieldDecl_c type(TypeNode type) {
return (X10FieldDecl_c) super.type(type);
}
@Override
public X10FieldDecl_c name(Id name) {
return (X10FieldDecl_c) super.name(name);
}
@Override
public X10FieldDecl_c init(Expr init) {
return (X10FieldDecl_c) super.init(init);
}
@Override
public X10FieldDecl_c fieldDef(FieldDef mi) {
return (X10FieldDecl_c) super.fieldDef(mi);
}
@Override
public X10FieldDef fieldDef() {
return (X10FieldDef) super.fieldDef();
}
protected X10FieldDecl_c reconstruct(TypeNode hasType) {
if (this.hasType != hasType) {
X10FieldDecl_c n = (X10FieldDecl_c) copy();
n.hasType = hasType;
return n;
}
return this;
}
public Context enterChildScope(Node child, Context c) {
Context oldC=c;
if (child == this.type || child==this.hasType) {
c = c.pushBlock();
FieldDef fi = fieldDef();
c.addVariable(fi.asInstance());
c.setVarWhoseTypeIsBeingElaborated(fi);
addInClassInvariantIfNeeded(c);
//PlaceChecker.setHereTerm(fieldDef(), c);
}
if (child == this.init) {
c = c.pushBlock();
addInClassInvariantIfNeeded(c);
PlaceChecker.setHereTerm(fieldDef(), c);
}
c = super.enterChildScope(child, c);
return c;
}
public void addInClassInvariantIfNeeded(Context c) {
if (!fieldDef().flags().isStatic()) {
// this call occurs in the body of an instance method for T.
// Pick up the real clause for T -- that information is known
// statically about "this"
Ref<? extends ContainerType> container = fieldDef().container();
if (container.known()) {
X10ClassType type = (X10ClassType) Types.get(container);
Ref<CConstraint> rc = type.x10Def().realClause();
c.addConstraint(rc);
Ref<TypeConstraint> tc = type.x10Def().typeBounds();
if (tc != null) {
c.setTypeConstraintWithContextTerms(tc);
}
}
}
}
@Override
public void setResolver(final Node parent, TypeCheckPreparer v) {
final FieldDef def = fieldDef();
Ref<ConstantValue> rx = def.constantValueRef();
if (rx instanceof LazyRef<?>) {
LazyRef<ConstantValue> r = (LazyRef<ConstantValue>) rx;
TypeChecker tc0 = new X10TypeChecker(v.job(), v.typeSystem(), v.nodeFactory(), v.getMemo());
final TypeChecker tc = (TypeChecker) tc0.context(v.context().freeze());
final Node n = this;
r.setResolver(new AbstractGoal_c("ConstantValue") {
private static final long serialVersionUID = -4839673421806815982L;
{ this.scheduler = tc.job().extensionInfo().scheduler(); }
public boolean runTask() {
if (state() == Goal.Status.RUNNING_RECURSIVE || state() == Goal.Status.RUNNING_WILL_FAIL) {
// The field is not constant if the initializer is recursive.
//
// But, we could be checking if the field is constant for another
// reference in the same file:
//
// m() { use x; }
// final int x = 1;
//
// So this is incorrect. The goal below needs to be refined to only visit the initializer.
def.setNotConstant();
}
else {
Node m = parent.visitChild(n, tc);
tc.job().nodeMemo().put(n, m);
tc.job().nodeMemo().put(m, m);
}
return true;
}
});
}
}
public Node conformanceCheck(ContextVisitor tc) {
Node result = super.conformanceCheck(tc);
// Any occurrence of a non-final static field in X10
// should be reported as an error.
if (flags().flags().isStatic() && (!flags().flags().isFinal())) {
Errors.issue(tc.job(),
new Errors.CannotDeclareStaticNonFinalField(position()));
}
FieldDef fi = fieldDef();
ContainerType ref = fi.container().get();
TypeSystem xts = (TypeSystem) ref.typeSystem();
Context context = (Context) tc.context();
if (Types.isX10Struct(ref) && !isMutable(xts, ref)) {
Flags x10flags = fi.flags();
if (! x10flags.isFinal())
Errors.issue(tc.job(),
new Errors.IllegalFieldDefinition(fi, position()));
}
checkVariance(tc);
X10MethodDecl_c.checkVisibility(tc, this);
return result;
}
protected boolean isMutable(TypeSystem xts, Type t) {
if (!(t instanceof X10ClassType)) return false;
X10ClassType ct = (X10ClassType) t;
try {
Type m = xts.systemResolver().findOne(QName.make("x10.compiler.Mutable"));
return ct.annotations().contains(m);
} catch (SemanticException e) {
return false;
}
}
protected void checkVariance(ContextVisitor tc) {
Context c = (Context) tc.context();
X10ClassDef cd;
if (c.inSuperTypeDeclaration())
cd = c.supertypeDeclarationType();
else
cd = (X10ClassDef) c.currentClassDef();
final Map<Name,ParameterType.Variance> vars = CollectionFactory.newHashMap();
for (int i = 0; i < cd.typeParameters().size(); i++) {
ParameterType pt = cd.typeParameters().get(i);
ParameterType.Variance v = cd.variances().get(i);
vars.put(pt.name(), v);
}
/*try {
if (flags().flags().isFinal()) {
Checker.checkVariancesOfType(type.position(), type.type(), ParameterType.Variance.COVARIANT, "as the type of a final field", vars, tc);
}
else {
Checker.checkVariancesOfType(type.position(), type.type(), ParameterType.Variance.INVARIANT, "as the type of a non-final field", vars, tc);
}
} catch (SemanticException e) {
Errors.issue(tc.job(), e, this);
}*/
}
protected FieldDef createFieldDef(TypeSystem ts, ClassDef ct, Flags xFlags) {
X10FieldDef fi = (X10FieldDef) ts.fieldDef(position(), Types.ref(ct.asType()), xFlags, type.typeRef(), name.id());
fi.setThisDef(((X10ClassDef) ct).thisDef());
return fi;
}
protected InitializerDef createInitializerDef(TypeSystem ts, ClassDef ct, Flags iflags) {
X10InitializerDef ii;
ii = (X10InitializerDef) super.createInitializerDef(ts, ct , iflags);
ii.setThisDef(((X10ClassDef) ct).thisDef());
return ii;
}
@Override
public Node buildTypesOverride(TypeBuilder tb) {
TypeSystem ts = (TypeSystem) tb.typeSystem();
X10FieldDecl_c n = (X10FieldDecl_c) super.buildTypesOverride(tb);
X10FieldDef fi = (X10FieldDef) n.fieldDef();
final ClassDef container = tb.currentClass();
n = (X10FieldDecl_c) X10Del_c.visitAnnotations(n, tb);
List<AnnotationNode> as = ((X10Del) n.del()).annotations();
if (as != null) {
List<Ref<? extends Type>> ats = new ArrayList<Ref<? extends Type>>(as.size());
for (AnnotationNode an : as) {
ats.add(an.annotationType().typeRef());
}
fi.setDefAnnotations(ats);
}
// Clear the static bit on properties
if (this instanceof PropertyDecl) {
Flags flags = flags().flags().clearStatic();
n = (X10FieldDecl_c) n.flags(n.flags.flags(flags));
fi.setFlags(flags);
}
// vj - shortcut and initialize the field instance if the decl has an initializer
// This is the hack to permit reading the list of properties from the StringLit initializer
// of a field, without waiting for a ConstantsChecked pass to run.
if (init instanceof StringLit) {
String val = ((StringLit) init).value();
fi.setConstantValue(x10.types.constants.ConstantValue.makeString(val));
}
// TODO: Could infer type of final fields as LCA of types assigned in the constructor.
if (type instanceof UnknownTypeNode && init == null)
Errors.issue(tb.job(), new Errors.CannotInferFieldType(position()));
// Do not infer types of mutable fields, since there could be more than one assignment and the compiler might not see them all.
if (type instanceof UnknownTypeNode && ! flags.flags().isFinal())
Errors.issue(tb.job(), new Errors.CannotInferNonFinalFieldType(position()));
return n;
}
public static boolean shouldInferType(Node n, TypeSystem ts) {
try {
Type at = ts.systemResolver().findOne(QName.make("x10.compiler.NoInferType"));
boolean res = ((X10Ext)n.ext()).annotationMatching(at).isEmpty();
if (res == true) return true;
return res;
} catch (SemanticException e) {
return false;
}
}
@Override
public Node setResolverOverride(Node parent, TypeCheckPreparer v) {
if (type() instanceof UnknownTypeNode && init != null) {
UnknownTypeNode tn = (UnknownTypeNode) type();
NodeVisitor childv = v.enter(parent, this);
childv = childv.enter(this, init);
if (childv instanceof TypeCheckPreparer) {
TypeCheckPreparer tcp = (TypeCheckPreparer) childv;
final LazyRef<Type> r = (LazyRef<Type>) tn.typeRef();
TypeChecker tc = new X10TypeChecker(v.job(), v.typeSystem(), v.nodeFactory(), v.getMemo());
tc = (TypeChecker) tc.context(tcp.context().freeze());
r.setResolver(new TypeCheckExprGoal(this, init, tc, r));
}
}
return super.setResolverOverride(parent, v);
}
@Override
public Node typeCheckOverride(Node parent, ContextVisitor tc) {
X10FieldDecl_c nn = this;
X10FieldDecl old = nn;
NodeVisitor childtc = tc.enter(parent, nn);
nn = (X10FieldDecl_c) X10Del_c.visitAnnotations(nn, childtc);
// Do not infer types of native fields
if (nn.type() instanceof UnknownTypeNode && ! shouldInferType(nn, tc.typeSystem()))
Errors.issue(tc.job(), new Errors.CannotInferNativeFieldType(nn.position()));
if (nn.hasType != null && ! nn.flags().flags().isFinal()) {
Errors.issue(tc.job(), new Errors.OnlyValMayHaveHasType(nn));
}
if (nn.type() instanceof UnknownTypeNode && shouldInferType(nn, tc.typeSystem())) {
Expr init = (Expr) nn.visitChild(nn.init(), childtc);
if (init != null) {
Type t = init.type();
if (t instanceof X10ClassType) {
X10ClassType ct = (X10ClassType) t;
if (ct.isAnonymous()) {
if (ct.interfaces().size() > 0)
t = ct.interfaces().get(0);
else
t = ct.superClass();
}
}
Context xc = nn.enterChildScope(nn.type(), tc.context());
t = PlaceChecker.ReplaceHereByPlaceTerm(t, xc);
LazyRef<Type> r = (LazyRef<Type>) nn.type().typeRef();
r.update(t);
TypeNode htn = null;
{
TypeNode tn = (TypeNode) nn.visitChild(nn.type(), childtc);
if (nn.hasType != null) {
htn = (TypeNode) nn.visitChild(nn.hasType, childtc);
boolean checkSubType = true;
try {
Types.checkMissingParameters(htn);
} catch (SemanticException e) {
Errors.issue(tc.job(), e, htn);
checkSubType = false;
}
if (checkSubType && ! htn.type().typeSystem().isSubtype(nn.type().type(), htn.type(),tc.context())) {
xc = (Context) nn.enterChildScope(init, tc.context());
Expr newInit = Converter.attemptCoercion(tc.context(xc), init, htn.type());
if (newInit == null) {
Errors.issue(tc.job(),
new Errors.TypeIsNotASubtypeOfTypeBound(nn.type().type(),
htn.type(),
nn.position()));
} else {
init = newInit;
r.update(newInit.type());
}
}
}
}
FlagsNode flags = (FlagsNode) nn.visitChild(nn.flags(), childtc);
Id name = (Id) nn.visitChild(nn.name(), childtc);
TypeNode tn = (TypeNode) nn.visitChild(nn.type(), childtc);
nn = nn.reconstruct(flags, tn, name, init, htn);
return tc.leave(parent, old, nn, childtc);
}
}
return null;
}
/** Reconstruct the declaration. */
protected X10FieldDecl_c reconstruct(FlagsNode flags, TypeNode type, Id name, Expr init, TypeNode hasType) {
X10FieldDecl_c n = (X10FieldDecl_c) super.reconstruct(flags, type, name, init);
if (n.hasType != hasType) {
n.hasType = hasType;
}
return n;
}
@Override
public Node typeCheck(ContextVisitor tc) {
final TypeNode typeNode = this.type();
Type type = typeNode.type();
Type oldType = (Type)type.copy();
Context xc = (Context) enterChildScope(type(), tc.context());
Flags f = flags.flags();
try {
Types.checkMissingParameters(typeNode);
} catch (SemanticException e) {
Errors.issue(tc.job(), e, this);
}
// Need to replace here by current placeTerm in type,
// since the field of this type can be referenced across
// a place shift. So here must be bound to the current placeTerm.
type = PlaceChecker.ReplaceHereByPlaceTerm(type, xc);
TypeSystem ts = (TypeSystem) tc.typeSystem();
if (type.isVoid()) {
Errors.issue(tc.job(), new Errors.FieldCannotHaveType(typeNode.type(), position()));
type = ts.unknownType(position());
}
if (Types.isX10Struct(fieldDef().container().get()) &&
!isMutable(ts, fieldDef().container().get()) &&
! f.isFinal())
{
Errors.issue(tc.job(), new Errors.StructMayNotHaveVarFields(position()));
}
final boolean noInit = init() == null;
if (f.isStatic() && noInit) {
Errors.issue(tc.job(), new Errors.StaticFieldMustHaveInitializer(name, position()));
}
NodeFactory nf = (NodeFactory) tc.nodeFactory();
X10FieldDecl_c n = (X10FieldDecl_c) this.type((CanonicalTypeNode) nf.CanonicalTypeNode(type().position(), type).ext(type().ext().copy()));
// Add an initializer to uninitialized var field unless field is annotated @Uninitialized.
final X10FieldDef fieldDef = (X10FieldDef) n.fieldDef();
final boolean needsInit = !f.isFinal() && noInit && !Types.isUninitializedField(fieldDef, ts);
final boolean isTransient = f.isTransient() && !Types.isSuppressTransientErrorField(fieldDef,ts);
if (needsInit || isTransient) {
final boolean hasZero = Types.isHaszero(type, xc);
// creating an init.
ContextVisitor tcWithNewContext = tc.context(xc);
Expr e = Types.getZeroVal(typeNode,position().markCompilerGenerated(),tcWithNewContext);
if (needsInit) {
if (e != null) {
n = (X10FieldDecl_c) n.init(e);
}
}
if (isTransient) {
// transient fields (not annotated with @SuppressTransientError) must have a default value
if (!hasZero)
Errors.issue(tc.job(), new Errors.TransientFieldMustHaveTypeWithDefaultValue(n.name(), position()));
}
}
if (n.init != null) {
xc = (Context) n.enterChildScope(n.init, tc.context());
ContextVisitor childtc = tc.context(xc);
Expr newInit = Converter.attemptCoercion(childtc, n.init, oldType); // use the oldType. The type of n.init may have "here".
if (newInit == null)
Errors.issue(tc.job(),
new Errors.FieldInitTypeWrong(n.init, type, n.init.position()),
this);
else
n = n.init(newInit);
}
// Types.checkVariance(n.type(), f.isFinal() ? ParameterType.Variance.COVARIANT : ParameterType.Variance.INVARIANT,tc.job());
// check cycles in struct declaration that will cause a field of infinite size, e.g.,
// struct Z(@ERR u:Z) {}
// struct Box[T](t:T) { }
// struct InfiniteSize(@ERR x:Box[Box[InfiniteSize]]) {}
final ContainerType containerType = fieldDef.container().get();
X10ClassDef_c goalDef = Types.getDef(containerType);
if (ts.isStruct(containerType)) {
Set<X10ClassDef_c> otherStructsUsed = CollectionFactory.newHashSet();
ArrayList<X10ParsedClassType> toExamine = new ArrayList<X10ParsedClassType>();
final X10ParsedClassType_c goal = Types.myBaseType(type);
if (goal!=null) {
toExamine.add(goal);
boolean isFirstTime = true;
while (toExamine.size()>0) {
final X10ParsedClassType curr = toExamine.remove(toExamine.size() - 1);
if (!isFirstTime && Types.getDef(curr)==goalDef) {
Errors.issue(tc.job(),new Errors.StructsCircularity(position),this);
break;
}
isFirstTime = false;
if (!ts.isStruct(curr)) continue;
X10ClassDef_c def = Types.getDef(curr);
if (otherStructsUsed.contains(def)) {
continue;
}
otherStructsUsed.add(def);
toExamine.addAll(getAllTypeArgs(curr));
for (FieldDef fi : def.fields()) {
if (fi.flags().isStatic()) continue;
X10ParsedClassType fiType = Types.myBaseType(fi.type().get());
if (fiType!=null) {
toExamine.add(fiType);
toExamine.addAll(getAllTypeArgs(fiType));
}
}
}
}
}
if (f.isProperty()) {
// you cannot write: class A[T](b:T) {...}
// i.e., property base type must be a class
Type t = Types.baseType(n.type().type());
if (!(t instanceof X10ParsedClassType))
Errors.issue(tc.job(),new SemanticException("A property type cannot be a type parameter.",position),this);
}
return n;
}
public ArrayList<X10ParsedClassType> getAllTypeArgs(X10ParsedClassType curr) {
final List<Type> typeArgs = curr.typeArguments();
ArrayList<X10ParsedClassType> res = new ArrayList<X10ParsedClassType>();
if (typeArgs!=null) {
// consider: struct InfiniteSize(x:Box[Box[InfiniteSize]]) {}
// if I just add Box[InfiniteSize] to toExamine, then when I pop it and check "if (otherStructsUsed.contains(def))" then I'll ignore it.
// therefore I must also add InfiniteSize (i.e., all the type args found recursively in the type.)
ArrayList<Type> toExamineArgs = new ArrayList<Type>(typeArgs);
while (toExamineArgs.size()>0) {
Type ta = toExamineArgs.remove(toExamineArgs.size()-1);
final X10ParsedClassType_c baseTa = Types.myBaseType(ta);
if (baseTa!=null) {
res.add(baseTa);
List<Type> typeArgs2 = baseTa.typeArguments();
if (typeArgs2!=null) toExamineArgs.addAll(typeArgs2);
}
}
}
return res;
}
/** Visit the children of the declaration. */
public Node visitChildren(NodeVisitor v) {
X10FieldDecl_c n = (X10FieldDecl_c) super.visitChildren(v);
TypeNode hasType = (TypeNode) visitChild(n.hasType, v);
return n.reconstruct(hasType);
}
public Node visitSignature(NodeVisitor v) {
X10FieldDecl_c n = (X10FieldDecl_c) super.visitSignature(v);
TypeNode hasType = (TypeNode) visitChild(n.hasType, v);
return n.reconstruct(hasType);
}
public void prettyPrint(CodeWriter w, PrettyPrinter tr) {
boolean isInterface = fi != null && fi.container() != null &&
fi.container().get().toClass().flags().isInterface();
Flags fs = flags.flags();
Boolean f = fs.isFinal();
if (isInterface) {
fs = fs.clearPublic();
fs = fs.clearStatic();
}
fs = fs.clearFinal();
w.write(fs.translate());
for (Iterator<AnnotationNode> i = (((X10Ext) this.ext()).annotations()).iterator(); i.hasNext(); ) {
AnnotationNode an = i.next();
an.prettyPrint(w, tr);
w.allowBreak(0, " ");
}
if (f)
w.write("val ");
else
w.write("var ");
tr.print(this, name, w);
w.allowBreak(2, 2, ":", 1);
print(type, w, tr);
if (init != null) {
w.write(" =");
w.allowBreak(2, " ");
print(init, w, tr);
}
w.write(";");
}
public Node checkConstants(ContextVisitor tc) {
Type native_annotation_type = tc.typeSystem().NativeType();
if (!((X10Ext)ext).annotationMatching(native_annotation_type).isEmpty()) {
fi.setNotConstant();
return this;
} else {
return super.checkConstants(tc);
}
}
}