/*
* 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.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import polyglot.ast.Block;
import polyglot.ast.CodeBlock;
import polyglot.ast.Expr_c;
import polyglot.ast.Formal;
import polyglot.ast.Node;
import polyglot.ast.NodeFactory;
import polyglot.ast.Precedence;
import polyglot.ast.Term;
import polyglot.ast.TypeNode;
import polyglot.main.Reporter;
import polyglot.types.ClassDef;
import polyglot.types.ClassType;
import polyglot.types.CodeDef;
import polyglot.types.Context;
import polyglot.types.Def;
import polyglot.types.FieldDef;
import polyglot.types.Flags;
import polyglot.types.LazyRef;
import polyglot.types.LocalDef;
import polyglot.types.Ref;
import polyglot.types.SemanticException;
import polyglot.types.Type;
import polyglot.types.TypeSystem;
import polyglot.types.Types;
import polyglot.types.VarDef;
import polyglot.types.VarInstance;
import polyglot.util.CodeWriter;
import polyglot.util.CollectionUtil;
import polyglot.util.Position;
import polyglot.util.SubtypeSet;
import polyglot.util.TypedList;
import polyglot.visit.CFGBuilder;
import polyglot.visit.ContextVisitor;
import polyglot.visit.NodeVisitor;
import polyglot.visit.PrettyPrinter;
import polyglot.visit.TypeBuilder;
import polyglot.visit.TypeCheckPreparer;
import x10.errors.Errors;
import x10.extension.X10Del;
import x10.types.ClosureDef;
import x10.types.EnvironmentCapture;
import x10.types.ThisDef;
import x10.types.X10ClassDef;
import x10.types.MethodInstance;
import x10.types.X10MemberDef;
import polyglot.types.LazyRef_c;
import x10.types.checker.PlaceChecker;
import x10.types.checker.VarChecker;
import x10.types.constants.ConstantValue;
import x10.types.constraints.CConstraint;
import x10.types.constraints.TypeConstraint;
import x10.types.constraints.XConstrainedTerm;
import x10.util.AnnotationUtils;
import x10.util.ClosureSynthesizer;
/**
* An implementation of a closure literal in the source text.
*
* It has associated with it a ClosurDef.
*
* Its type is a ClosureType.
* @author vj
*
*/
public class Closure_c extends Expr_c implements Closure {
// List<TypeParamNode> typeParameters;
protected List<Formal> formals;
protected TypeNode returnType;
protected Block body;
protected MethodInstance container;
protected ClosureDef closureDef;
protected ClassType typeContainer;
protected DepParameterExpr guard;
private static final Collection<String> TOPICS =
CollectionUtil.list(Reporter.types, Reporter.context);
public Closure_c(Position pos) {
super(pos);
}
protected TypeNode hasType;
protected TypeNode offerType;
public Closure_c(NodeFactory nf, Position pos, List<Formal> formals,
TypeNode returnType, DepParameterExpr guard, TypeNode offerType, Block body) {
super(pos);
// this.typeParameters = TypedList.copyAndCheck(typeParams, TypeParamNode.class, true);
this.formals = TypedList.copyAndCheck(formals, Formal.class, true);
this.returnType = returnType instanceof HasTypeNode_c ? nf.UnknownTypeNode(returnType.position()) : returnType;
this.guard = guard;
//this.throwTypes = TypedList.copyAndCheck(throwTypes, TypeNode.class, true);
this.body = body;
if (returnType instanceof HasTypeNode_c)
hasType = ((HasTypeNode_c) returnType).typeNode();
this.offerType = offerType;
}
/* public List<TypeParamNode> typeParameters() {
return typeParameters;
}
*/
public Closure position(Position pos) {
Closure_c n = (Closure_c) copy();
n.position=pos;
return n;
}
/* public Closure typeParameters(List<TypeParamNode> typeParams) {
Closure_c n = (Closure_c) copy();
n.typeParameters=TypedList.copyAndCheck(typeParams, TypeParamNode.class, true);
return n;
}
*/
public List<Formal> formals() {
return formals;
}
public Closure formals(List<Formal> formals) {
Closure_c c= (Closure_c) copy();
c.formals= formals;
return c;
}
public Closure_c hasType(TypeNode htn) {
if (htn != hasType) {
Closure_c c= (Closure_c) copy();
c.hasType = htn;
return c;
}
return this;
}
public TypeNode returnType() {
return returnType;
}
public Closure returnType(TypeNode returnType) {
Closure_c c = (Closure_c) copy();
c.returnType = returnType;
return c;
}
public DepParameterExpr guard() {
return guard;
}
public Closure guard(DepParameterExpr guard) {
Closure_c n = (Closure_c) copy();
n.guard = guard;
return n;
}
public Block body() {
return body;
}
public CodeBlock body(Block body) {
Closure_c c= (Closure_c) copy();
c.body= body;
return c;
}
public Term codeBody() {
return body;
}
public MethodInstance methodContainer() {
return container;
}
public Closure methodContainer(MethodInstance methodInstance) {
Closure_c c= (Closure_c) copy();
c.container = methodInstance;
return c;
}
public CodeDef codeDef() {
return closureDef;
}
public ClosureDef closureDef() {
return this.closureDef;
}
public Closure closureDef(ClosureDef ci) {
//System.out.println("Closure_c.closureDef called with " + ci);
if (ci == this.closureDef) return this;
Closure_c n = (Closure_c) copy();
n.closureDef = ci;
return n;
}
/** Reconstruct the closure. */
protected Closure_c reconstruct(/*List<TypeParamNode> typeParams,*/ List<Formal> formals, DepParameterExpr guard, TypeNode returnType, Block body) {
if (/*! CollectionUtil.allEqual(typeParams, this.typeParameters) ||*/
!CollectionUtil.allEqual(formals, this.formals)
|| returnType != this.returnType
|| guard != this.guard
|| body != this.body) {
Closure_c n = (Closure_c) copy();
// n.typeParameters = TypedList.copyAndCheck(typeParams, TypeParamNode.class, true);
n.formals = TypedList.copyAndCheck(formals, Formal.class, true);
n.guard = guard;
n.returnType = returnType;
n.body = body;
return n;
}
return this;
}
/** Visit the children of the expression. */
public Node visitChildren(NodeVisitor v) {
//List<TypeParamNode> typeParams = visitList(this.typeParameters, v);
List<Formal> formals = visitList(this.formals, v);
DepParameterExpr guard = (DepParameterExpr) visitChild(this.guard, v);
TypeNode returnType = (TypeNode) visitChild(this.returnType, v);
Block body = (Block) visitChild(this.body, v);
TypeNode htn = (TypeNode) visitChild(this.hasType, v);
return reconstruct(/*typeParams,*/ formals, guard, returnType, body).hasType(htn);
}
public Node buildTypesOverride(TypeBuilder tb) {
TypeSystem ts = (TypeSystem) tb.typeSystem();
X10ClassDef ct = (X10ClassDef) tb.currentClass();
assert ct != null;
Def def = tb.def();
if (def instanceof FieldDef) {
FieldDef fd = (FieldDef) def;
def = fd.initializer();
}
if (! (def instanceof CodeDef)) {
Errors.issue(tb.job(),
new Errors.CannotOccurOutsideCodeBody(Errors.CannotOccurOutsideCodeBody.Element.Closure, position()));
// Fake it
def = ts.initializerDef(position(), Types.ref(ct.asType()), Flags.STATIC);
}
CodeDef code = (CodeDef) def;
// Get the enclosing this variable.
ThisDef thisDef;
if (code instanceof X10MemberDef) {
thisDef = ((X10MemberDef) code).thisDef();
}
else {
thisDef = ct.thisDef();
}
ClosureDef mi = ts.closureDef(position(),
Types.ref(ct.asType()),
Types.ref(code.asInstance()),
returnType.typeRef(),
Collections.<Ref<? extends Type>>emptyList(),
thisDef,
Collections.<LocalDef>emptyList(),
null,
//null,
offerType == null ? null : offerType.typeRef());
mi.setStaticContext(code.staticContext());
mi.setPlaceTerm(PlaceChecker.closurePlaceTerm(mi));
if (returnType() instanceof UnknownTypeNode) {
mi.inferReturnType(true);
}
// Unlike methods and constructors, do not create new goals for resolving the signature and body separately;
// since closures don't have names, we'll never have to resolve the signature. Just push the code context.
TypeBuilder tb2 = tb.pushCode(mi);
Closure_c n = (Closure_c) this.del().visitChildren(tb2);
if (n.guard() != null) {
mi.setGuard(n.guard().valueConstraint());
//mi.setTypeGuard(n.guard().typeConstraint());
}
/* List<Ref<? extends Type>> typeParameters = new ArrayList<Ref<? extends Type>>(n.typeParameters().size());
for (TypeParamNode tpn : n.typeParameters()) {
typeParameters.add(Types.ref(tpn.type()));
}
*/
List<Ref<? extends Type>> formalTypes = new ArrayList<Ref<? extends Type>>(n.formals().size());
for (Formal f : n.formals()) {
formalTypes.add(f.type().typeRef());
}
List<LocalDef> formalNames = new ArrayList<LocalDef>(n.formals().size());
for (Formal f : n.formals()) {
formalNames.add(f.localDef());
}
mi.setFormalNames(formalNames);
mi.setReturnType(n.returnType().typeRef());
// mi.setTypeParameters(Collections.EMPTY_LIST);
mi.setFormalTypes(formalTypes);
if (code instanceof X10MemberDef) {
assert mi.thisDef() == ((X10MemberDef) code).thisDef();
}
if (returnType instanceof UnknownTypeNode && body == null) {
Errors.issue(tb.job(),
new SemanticException("Cannot infer return type; closure has no body.", position()));
}
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());
}
mi.setDefAnnotations(ats);
}
return n.closureDef(mi);
}
public Context enterScope(Context c) {
Reporter reporter = c.typeSystem().extensionInfo().getOptions().reporter;
if (reporter.should_report(TOPICS, 5))
reporter.report(5, "enter scope of closure at " + position());
while (c.inDepType()) { // could happen if the closure appears in a constraint -- error already reported
c = c.pop();
}
// TODO maybe we want a new type of "code context thingy" that is not a type system object, but can live on the Context stack.
c = c.pushCode(closureDef);
return c;
}
@Override
public Context enterChildScope(Node child, Context c) {
// We should have entered the method scope already.
Context oldC=c;
if (c.currentCode() != this.closureDef())
assert c.currentCode() == this.closureDef();
if (child != body()) {
// Push formals so they're in scope in the types of the other formals.
c = c.pushBlock();
for (int i=0; i < formals.size(); i++) {
Formal f = formals.get(i);
f.addDecls(c);
if (f == child)
break; // do not add downstream formals
}
}
// Ensure that the place constraint is set appropriately when
// entering the appropriate children
if (child == body || child == returnType || child == hasType || child == offerType || child == guard
|| (formals != null && formals.contains(child))) {
ClosureDef cd = closureDef();
XConstrainedTerm placeTerm = cd == null ? null : cd.placeTerm();
if (placeTerm == null) {
placeTerm = PlaceChecker.closurePlaceTerm(cd);
}
if (c == oldC)
c = c.pushBlock();
c.setPlace(placeTerm);
}
if (child == guard) {
TypeSystem ts = c.typeSystem();
c = c.pushDepType(Types.<Type>ref(ts.unknownType(this.position)));
}
// Add the closure guard into the environment.
if (guard != null) {
if (child == body || child == returnType || child == hasType) {
Ref<CConstraint> vc = guard.valueConstraint();
Ref<TypeConstraint> tc = guard.typeConstraint();
if (oldC==c && (vc != null || tc != null)) {
c = c.pushBlock();
}
if (vc != null)
c.addConstraint(vc);
if (tc != null)
c.setTypeConstraintWithContextTerms(tc);
}
}
if (child == body && offerType != null && offerType.typeRef().known()) {
if (oldC == c)
c = c.pushBlock();
c.setCollectingFinishScope(offerType.type());
}
return super.enterChildScope(child, c);
}
@Override
public Node setResolverOverride(Node parent, TypeCheckPreparer v) {
if (returnType() instanceof UnknownTypeNode && body() != null) {
UnknownTypeNode tn = (UnknownTypeNode) returnType();
tn.setResolver(this, v);
NodeVisitor childv = v.enter(parent, this);
childv = childv.enter(this, tn);
if (childv instanceof TypeCheckPreparer) {
final LazyRef<Type> r = (LazyRef<Type>) tn.typeRef();
// THROW_RESOLVER also tells UnknownTypeNode_c not to try and infer its type (because we visit the returnType first, then the closure)
r.setResolver(LazyRef_c.THROW_RESOLVER); // this resolver should never be called (we update the return type when inferring the closure body).
}
}
return super.setResolverOverride(parent, v);
}
@Override
public Node typeCheck(ContextVisitor tc) {
TypeSystem xts = tc.typeSystem();
Context c = tc.context();
Closure_c n = this;
if (guard != null) {
VarChecker ac = new VarChecker(tc.job());
ac = (VarChecker) ac.context(tc.context());
guard.visit(ac);
if (ac.error != null) {
Errors.issue(tc.job(), ac.error, this);
}
if (guard.typeConstraint() != null && !Types.get(guard.typeConstraint()).terms().isEmpty()) {
Errors.issue(tc.job(),
new SemanticException("Type constraints not permitted in closure guards.",
position()));
}
}
if (n.returnType() instanceof UnknownTypeNode) {
NodeFactory nf = tc.nodeFactory();
TypeSystem ts = (TypeSystem) tc.typeSystem();
Ref<Type> tr = ((Ref<Type>) n.returnType().typeRef());
Type t = tr.getCached();
if (!tr.known() && ts.isUnknown(t)) {
// Body had no return statement. Set to void.
t = ts.Void();
}
t = Types.removeLocals( c, t);
tr.update(t);
n = (Closure_c) n.returnType(nf.CanonicalTypeNode(n.returnType().position(), t));
}
try {
X10MethodDecl_c.dupFormalCheck(Collections.<TypeParamNode>emptyList(), formals);
} catch (SemanticException e) {
Errors.issue(tc.job(), e, n);
}
try {
Types.checkMissingParameters(n.returnType());
} catch (SemanticException e) {
Errors.issue(tc.job(), e, n.returnType());
}
// Create an anonymous subclass of the closure type.
ClosureDef def = n.closureDef;
//if (!def.capturedEnvironment().isEmpty()) {
// System.out.println(this.position() + ": " + this + " captures "+def.capturedEnvironment());
//}
propagateCapturedEnvironment(c, def);
n = (Closure_c) n.type(def.asType());
if (hasType != null) {
final TypeNode h = (TypeNode) n.visitChild(n.hasType, tc);
Type hasType = PlaceChecker.ReplaceHereByPlaceTerm(h.type(), tc.context());
n = n.hasType(h);
if (!xts.isSubtype(n.returnType().type(), hasType, tc.context())) {
Errors.issue(tc.job(), new Errors.TypeIsNotASubtypeOfTypeBound(type, hasType, position()));
}
}
return n;
}
// Propagate the captured variables to the parent closure (if any)
public static void propagateCapturedEnvironment(Context c, EnvironmentCapture def) {
Context o = c;
while (o != null && o.currentCode() == def)
o = o.pop().popToCode();
if (o == null)
return;
for (VarInstance<? extends VarDef> vi : def.capturedEnvironment()) {
o.recordCapturedVariable(vi);
}
}
@Override
public Node conformanceCheck(ContextVisitor tc) {
return this;
}
public Term firstChild() {
return //listChild(/*typeParameters(),
listChild(formals(), returnType);
}
/**
* Visit this term in evaluation order.
* [IP] Treat this as a conditional to make sure the following
* statements are always reachable.
* FIXME: We should really build our own CFG, push a new context,
* and disallow uses of "continue", "break", etc. in closures.
*/
@Override
public <S> List<S> acceptCFG(CFGBuilder v, List<S> succs) {
/* if (formals().isEmpty()) {
v.visitCFGList(typeParameters(), returnType, ENTRY);
}
else {
v.visitCFGList(typeParameters(), formals.get(0), ENTRY);
}*/
v.visitCFGList(formals(), returnType, ENTRY);
// If building the CFG for the enclosing code, don't thread
// in the closure body. Otherwise, we're building the CFG
// for the closure itself.
if (! succs.isEmpty()) {
v.visitCFG(returnType, this, EXIT);
}
else {
v.visitCFG(returnType, body, ENTRY);
v.visitCFG(body, this, EXIT);
}
/*
v.visitCFG(returnType, FlowGraph.EDGE_KEY_TRUE, body, ENTRY,
FlowGraph.EDGE_KEY_FALSE, this, EXIT);
*/
return succs;
}
@Override
public SubtypeSet exceptions() {
// The closure itself doesn't throw any exceptions, but a call to it might.
// return new SubtypeSet(Globals.TS());
return null;
}
public Precedence precedence() {
return Precedence.LITERAL;
}
public boolean isConstant() {
// TODO: Dave G. Hack around replication of closures by constant propagation.
if (AnnotationUtils.hasAnnotation(this.body, type.typeSystem().RemoteInvocation())) return false;
return true;
}
public ConstantValue constantValue() {
return ConstantValue.makeClosure(this);
}
public String toString() {
StringBuilder sb= new StringBuilder();
/*if (! typeParameters.isEmpty()) {
sb.append("[");
for(Iterator iter= typeParameters.iterator(); iter.hasNext(); ) {
TypeParamNode tpn= (TypeParamNode) iter.next();
sb.append(tpn.toString());
if (iter.hasNext()) sb.append(", ");
}
sb.append("]");
}*/
sb.append(" (");
for(Iterator<Formal> iter = formals.iterator(); iter.hasNext(); ) {
Formal formal = iter.next();
sb.append(formal.toString());
if (iter.hasNext()) sb.append(", ");
}
sb.append(")");
sb.append(guard==null?"{}":guard);
sb.append(": ");
sb.append(returnType.toString());
sb.append(" => ");
sb.append(body);
return sb.toString();
}
/** Write the statement to an output file. */
public void prettyPrint(CodeWriter w, PrettyPrinter tr) {
w.begin(0);
w.write("(");
w.allowBreak(2, 2, "", 0);
w.begin(0);
for (Iterator<Formal> i = formals.iterator(); i.hasNext(); ) {
Formal f = i.next();
print(f, w, tr);
if (i.hasNext()) {
w.write(",");
w.allowBreak(0, " ");
}
}
w.end();
w.write(") ");
w.end();
printSubStmt(body, w, tr);
}
}