// Copyright (c) 2011, David J. Pearce (djp@ecs.vuw.ac.nz)
// All rights reserved.
//
// This software may be modified and distributed under the terms
// of the BSD license. See the LICENSE file for details.
package wyil.checks;
import java.util.*;
import static wyil.util.ErrorMessages.*;
import wybs.lang.Attribute;
import wybs.lang.Build;
import wybs.lang.SyntacticElement;
import wybs.lang.SyntaxError;
import wybs.util.ResolveError;
import wycc.util.Pair;
import wyil.lang.*;
import wyil.util.TypeSystem;
/**
* <p>
* The point of the coercion check is to check that all convert bytecodes make
* sense, and are not ambiguous. For example, consider the following code:
* </p>
*
* <pre>
* define Rec1 as { real x, int y }
* define Rec2 as { int x, real y }
* define uRec1Rec2 as Rec1 | Rec2
*
* int f(uRec1Rec2 r):
* if r is Rec1:
* return r.y
* else:
* return r.x
*
* int g():
* rec = { x: 1, y: 1}
* return f(rec)
* </pre>
*
* <p>
* An implicit coercion will be inserted just before the last statement in
* <code>g()</code>. This will be:
* </p>
*
* <pre>
* convert {int x,int y} => {real x,int y}|{int x,real y}
* </pre>
* <p>
* However, this conversion is <i>ambiguous</i> because we could convert the
* left-hand side to either of the two options in the right-hand side.
* </p>
*
* @author David J. Pearce
*/
public class CoercionCheck implements Build.Stage<WyilFile> {
private WyilFile file;
private final TypeSystem typeSystem;
public CoercionCheck(Build.Task builder) {
this.typeSystem = new TypeSystem(builder.project());
}
@Override
public void apply(WyilFile module) {
this.file = module;
for(WyilFile.Type type : module.types()) {
check(type.getTree());
}
for(WyilFile.FunctionOrMethod method : module.functionOrMethods()) {
check(method.getTree());
}
}
protected void check(SyntaxTree tree) {
// Examine all entries in this block looking for a conversion bytecode
List<SyntaxTree.Location<?>> expressions = tree.getLocations();
for (int i = 0; i != expressions.size(); ++i) {
SyntaxTree.Location<?> l = expressions.get(i);
if (l.getBytecode() instanceof Bytecode.Expr) {
Bytecode.Expr e = (Bytecode.Expr) l.getBytecode();
if (e instanceof Bytecode.Convert) {
Bytecode.Convert c = (Bytecode.Convert) e;
// FIXME: need to fix this :)
// check(conv.type(0), c.type(), new HashSet<Pair<Type,
// Type>>(), e.attribute(SourceLocation.class));
}
}
}
}
/**
* Recursively check that there is no ambiguity in coercing type from into
* type to. The visited set is necessary to ensure this process terminates
* in the presence of recursive types.
*
* @param from
* @param to
* @param visited
* - the set of pairs already checked.
* @param location
* - source location attribute (if applicable).
* @throws ResolveError
* If a named type within this condition cannot be resolved
* within the enclosing project.
*/
protected void check(Type from, Type to, HashSet<Pair<Type, Type>> visited, SyntacticElement element) throws ResolveError {
Pair<Type,Type> p = new Pair<Type,Type>(from,to);
if(visited.contains(p)) {
return; // already checked this pair
} else {
visited.add(p);
}
if(from == Type.T_VOID) {
// also no problem
} else if(from instanceof Type.Leaf && to instanceof Type.Leaf) {
// no problem
} else if(from instanceof Type.Reference && to instanceof Type.Reference) {
Type.Reference t1 = (Type.Reference) from;
Type.Reference t2 = (Type.Reference) to;
check(t1.element(),t2.element(),visited,element);
} else if(from instanceof Type.Array && to instanceof Type.Array) {
Type.Array t1 = (Type.Array) from;
Type.Array t2 = (Type.Array) to;
check(t1.element(),t2.element(),visited,element);
} else if(from instanceof Type.Record && to instanceof Type.Record) {
Type.Record t1 = (Type.Record) from;
Type.Record t2 = (Type.Record) to;
String[] fields = t1.getFieldNames();
for(String s : fields) {
Type e1 = t1.getField(s);
Type e2 = t2.getField(s);
check(e1,e2,visited,element);
}
} else if(from instanceof Type.Function && to instanceof Type.Function) {
Type.Function t1 = (Type.Function) from;
Type.Function t2 = (Type.Function) to;
check(t1.params(),t2.params(),visited,element);
check(t1.returns(),t2.returns(),visited,element);
} else if(from instanceof Type.Union) {
Type.Union t1 = (Type.Union) from;
for(Type b : t1.bounds()) {
check(b,to,visited,element);
}
} else if(to instanceof Type.Union) {
Type.Union t2 = (Type.Union) to;
// First, check for identical type (i.e. no coercion necessary)
for(Type b : t2.bounds()) {
if(from.equals(b)) {
// no problem
return;
}
}
// Second, check for single non-coercive match
Type match = null;
for(Type b : t2.bounds()) {
if(typeSystem.isSubtype(b,from)) {
if(match != null) {
// found ambiguity
throw new SyntaxError(errorMessage(AMBIGUOUS_COERCION,from,to), file.getEntry(), element);
} else {
check(from,b,visited,element);
match = b;
}
}
}
if(match != null) {
// ok, we have a hit on a non-coercive subtype.
return;
}
// Third, test for single coercive match
for(Type b : t2.bounds()) {
if(typeSystem.isExplicitCoerciveSubtype(b,from)) {
if(match != null) {
// found ambiguity
throw new SyntaxError("ambiguous coercion (" + from + " => " + to, file.getEntry(), element);
} else {
check(from,b,visited,element);
match = b;
}
}
}
}
}
private void check(Type[] params1, Type[] params2, HashSet<Pair<Type, Type>> visited,
SyntacticElement element) throws ResolveError {
for (int i = 0; i != params1.length; ++i) {
Type e1 = params1[i];
Type e2 = params2[i];
check(e1, e2, visited, element);
}
}
}