package com.redhat.ceylon.compiler.typechecker.analyzer;
import static com.redhat.ceylon.compiler.typechecker.analyzer.Util.getParameterTypeErrorNode;
import static com.redhat.ceylon.compiler.typechecker.analyzer.Util.getTypeErrorNode;
import static com.redhat.ceylon.compiler.typechecker.model.Util.isCompletelyVisible;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import com.redhat.ceylon.compiler.typechecker.model.Class;
import com.redhat.ceylon.compiler.typechecker.model.Declaration;
import com.redhat.ceylon.compiler.typechecker.model.Element;
import com.redhat.ceylon.compiler.typechecker.model.IntersectionType;
import com.redhat.ceylon.compiler.typechecker.model.Method;
import com.redhat.ceylon.compiler.typechecker.model.Module;
import com.redhat.ceylon.compiler.typechecker.model.ModuleImport;
import com.redhat.ceylon.compiler.typechecker.model.Package;
import com.redhat.ceylon.compiler.typechecker.model.Parameter;
import com.redhat.ceylon.compiler.typechecker.model.ProducedType;
import com.redhat.ceylon.compiler.typechecker.model.TypeAlias;
import com.redhat.ceylon.compiler.typechecker.model.TypeDeclaration;
import com.redhat.ceylon.compiler.typechecker.model.TypeParameter;
import com.redhat.ceylon.compiler.typechecker.model.TypedDeclaration;
import com.redhat.ceylon.compiler.typechecker.model.UnionType;
import com.redhat.ceylon.compiler.typechecker.tree.Node;
import com.redhat.ceylon.compiler.typechecker.tree.Tree;
import com.redhat.ceylon.compiler.typechecker.tree.Tree.ParameterList;
import com.redhat.ceylon.compiler.typechecker.tree.Visitor;
public class VisibilityVisitor extends Visitor {
@Override public void visit(Tree.TypedDeclaration that) {
checkVisibility(that, that.getDeclarationModel());
super.visit(that);
}
@Override public void visit(Tree.TypedArgument that) {
checkVisibility(that, that.getDeclarationModel());
super.visit(that);
}
@Override
public void visit(Tree.AnyClass that) {
super.visit(that);
Class c = that.getDeclarationModel();
if (that.getParameterList()!=null) {
checkParameterVisibility(c, that.getParameterList());
}
}
@Override
public void visit(Tree.AnyMethod that) {
super.visit(that);
checkParameterVisibility(that.getDeclarationModel(),
that.getParameterLists());
}
@Override
public void visit(Tree.MethodArgument that) {
super.visit(that);
checkParameterVisibility(that.getDeclarationModel(),
that.getParameterLists());
}
@Override
public void visit(Tree.FunctionArgument that) {
super.visit(that);
Method m = that.getDeclarationModel();
checkVisibility(that, m);
checkParameterVisibility(m, that.getParameterLists());
}
private void checkParameterVisibility(Method m,
List<ParameterList> parameterLists) {
for (Tree.ParameterList list: parameterLists) {
checkParameterVisibility(m, list);
}
}
private void checkParameterVisibility(Declaration m,
Tree.ParameterList list) {
for (Tree.Parameter tp: list.getParameters()) {
if (tp!=null) {
Parameter p = tp.getParameterModel();
if (p.getModel()!=null) {
checkParameterVisibility(tp, m, p);
}
}
}
}
@Override public void visit(Tree.TypeDeclaration that) {
validateSupertypes(that, that.getDeclarationModel());
super.visit(that);
}
@Override public void visit(Tree.ObjectDefinition that) {
validateSupertypes(that,
that.getDeclarationModel().getType().getDeclaration());
super.visit(that);
}
@Override public void visit(Tree.ObjectArgument that) {
validateSupertypes(that,
that.getDeclarationModel().getType().getDeclaration());
super.visit(that);
}
private void validateSupertypes(Tree.StatementOrArgument that,
TypeDeclaration td) {
if (td instanceof TypeAlias) {
ProducedType at = td.getExtendedType();
if (at!=null) {
if (!isCompletelyVisible(td, at)) {
that.addError("aliased type is not visible everywhere type alias '" +
td.getName() + "' is visible: '" +
at.getProducedTypeName(that.getUnit()) +
"' involves an unshared type declaration",
713);
}
if (!checkModuleVisibility(td, at)) {
that.addError("aliased type of type alias '" + td.getName() +
"' that is visible outside this module comes from an imported module that is not re-exported: '" +
at.getProducedTypeName(that.getUnit()) +
"' involves an unexported type declaration",
714);
}
}
}
else {
List<ProducedType> supertypes = td.getType().getSupertypes();
if (!td.isInconsistentType()) {
for (ProducedType st: supertypes) {
// don't do this check for ObjectArguments
if (that instanceof Tree.Declaration) {
if (!isCompletelyVisible(td, st)) {
that.addError("supertype is not visible everywhere type '" +
td.getName() + "' is visible: '" +
st.getProducedTypeName(that.getUnit()) +
"' involves an unshared type declaration",
713);
}
if (!checkModuleVisibility(td, st)) {
that.addError("supertype of type '" + td.getName() +
"' that is visible outside this module comes from an imported module that is not re-exported: '" +
st.getProducedTypeName(that.getUnit()) +
"' involves an unexported type declaration",
714);
}
}
}
}
}
// validateMemberRefinement(td, that, unit);
}
private static boolean checkModuleVisibility(Declaration member, ProducedType pt) {
if (inExportedScope(member)) {
Module declarationModule = getModule(member);
if (declarationModule!=null) {
return isCompletelyVisibleFromOtherModules(member,pt,declarationModule);
}
}
return true;
}
private static boolean inExportedScope(Declaration decl) {
// if it has a visible scope it's not exported outside the module
if(decl.getVisibleScope() != null)
return false;
// now perhaps its package is not shared
Package p = decl.getUnit().getPackage();
return p != null && p.isShared();
}
static boolean isCompletelyVisibleFromOtherModules(Declaration member,
ProducedType pt, Module thisModule) {
if (pt.getDeclaration() instanceof UnionType) {
for (ProducedType ct: pt.getDeclaration().getCaseTypes()) {
if (!isCompletelyVisibleFromOtherModules(member,
ct.substitute(pt.getTypeArguments()), thisModule)) {
return false;
}
}
return true;
}
else if (pt.getDeclaration() instanceof IntersectionType) {
for (ProducedType ct: pt.getDeclaration().getSatisfiedTypes()) {
if (!isCompletelyVisibleFromOtherModules(member,
ct.substitute(pt.getTypeArguments()), thisModule)) {
return false;
}
}
return true;
}
else {
if (!isVisibleFromOtherModules(member, thisModule,
pt.getDeclaration())) {
return false;
}
for (ProducedType at: pt.getTypeArgumentList()) {
if ( at!=null &&
!isCompletelyVisibleFromOtherModules(member,at,thisModule) ) {
return false;
}
}
return true;
}
}
private static String getName(Declaration td) {
if (td.isAnonymous()) {
return "anonymous function";
}
else {
return "'" + td.getName() + "'";
}
}
private static void checkVisibility(Node that,
TypedDeclaration td) {
ProducedType type = td.getType();
if (type!=null) {
Node typeNode = getTypeErrorNode(that);
if (!isCompletelyVisible(td, type)) {
typeNode.addError("type of declaration " + getName(td) +
" is not visible everywhere declaration is visible: '" +
type.getProducedTypeName(that.getUnit()) +
"' involves an unshared type declaration", 711);
}
if (!checkModuleVisibility(td, type)) {
typeNode.addError("type of declaration " + getName(td) +
" that is visible outside this module comes from an imported module that is not re-exported: '" +
type.getProducedTypeName(that.getUnit()) +
"' involves an unexported type declaration", 712);
}
}
}
private static void checkParameterVisibility(Tree.Parameter tp,
Declaration td, Parameter p) {
ProducedType pt = p.getType();
if (pt!=null) {
if (!isCompletelyVisible(td, pt)) {
getParameterTypeErrorNode(tp)
.addError("type of parameter '" + p.getName() + "' of " + getName(td) +
" is not visible everywhere declaration is visible: '" +
pt.getProducedTypeName(tp.getUnit()) +
"' involves an unshared type declaration", 710);
}
if (!checkModuleVisibility(td, pt)) {
getParameterTypeErrorNode(tp)
.addError("type of parameter '" + p.getName() + "' of " + getName(td) +
" that is visible outside this module comes from an imported module that is not re-exported: '" +
pt.getProducedTypeName(tp.getUnit()) +
"' involves an unexported type declaration", 714);
}
}
}
private static boolean isVisibleFromOtherModules(Declaration member,
Module thisModule, TypeDeclaration type) {
// type parameters are OK
if (type instanceof TypeParameter) {
return true;
}
Module typeModule = getModule(type);
if (typeModule!=null && thisModule!=null &&
thisModule!=typeModule) {
// find the module import, but only in exported imports, otherwise it's an error anyways
// language module stuff is automagically exported
if (typeModule == thisModule.getLanguageModule()) {
return true;
}
// try to find a direct import first
for (ModuleImport imp: thisModule.getImports()) {
if (imp.isExport() &&
imp.getModule() == typeModule) {
// found it
return true;
}
}
// then try the more expensive implicit imports
Set<Module> visited = new HashSet<Module>();
visited.add(thisModule);
for (ModuleImport imp : thisModule.getImports()) {
// now try implicit dependencies
if (imp.isExport() &&
includedImplicitly(imp.getModule(),
typeModule, visited)) {
// found it
return true;
}
}
// couldn't find it
return false;
}
// no module or it does not belong to a module? more likely an error was already reported
return true;
}
private static boolean includedImplicitly(Module importedModule,
Module targetModule, Set<Module> visited) {
// don't visit them twice
if (visited.add(importedModule)) {
for (ModuleImport imp: importedModule.getImports()){
// only consider modules it exported back to us
if (imp.isExport()
&& (imp.getModule() == targetModule
|| includedImplicitly(imp.getModule(),
targetModule, visited))) {
return true;
}
}
}
return false;
}
private static Module getModule(Element element){
Package typePackage = element.getUnit().getPackage();
return typePackage != null ? typePackage.getModule() : null;
}
}