
org.fulib.scenarios.visitor.resolve.DeclResolver Maven / Gradle / Ivy
package org.fulib.scenarios.visitor.resolve;
import org.fulib.scenarios.ast.CompilationContext;
import org.fulib.scenarios.ast.ScenarioGroup;
import org.fulib.scenarios.ast.decl.*;
import org.fulib.scenarios.ast.expr.Expr;
import org.fulib.scenarios.ast.scope.Scope;
import org.fulib.scenarios.ast.sentence.SentenceList;
import org.fulib.scenarios.ast.type.ClassType;
import org.fulib.scenarios.ast.type.ListType;
import org.fulib.scenarios.ast.type.PrimitiveType;
import org.fulib.scenarios.ast.type.Type;
import org.fulib.scenarios.diagnostic.Marker;
import org.fulib.scenarios.diagnostic.Position;
import org.fulib.scenarios.visitor.ExtractClassDecl;
import org.fulib.scenarios.visitor.TypeConversion;
import org.fulib.scenarios.visitor.describe.DeclDescriber;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Stream;
import static org.fulib.builder.Type.MANY;
import static org.fulib.scenarios.diagnostic.Marker.error;
import static org.fulib.scenarios.diagnostic.Marker.note;
import static org.fulib.scenarios.visitor.TypeComparer.isSuperClass;
public class DeclResolver
{
// =============== Constants ===============
protected static final String ENCLOSING_CLASS = "";
private static final int UNRESOLVED_HINT_DISTANCE_THRESHOLD = 3;
// =============== Static Methods ===============
public static ScenarioGroup resolveGroup(CompilationContext context, String packageDir)
{
return context.getGroups().computeIfAbsent(packageDir, p -> {
return ScenarioGroup.of(context, null, p, new HashMap<>(), new ConcurrentHashMap<>());
});
}
// --------------- Classes ---------------
static ClassDecl getEnclosingClass(Scope scope)
{
return (ClassDecl) scope.resolve(ENCLOSING_CLASS);
}
static ClassDecl resolveClass(Scope scope, String name, Position position)
{
/* TODO find an example that causes this warning.
it "should" not appear because class names are normalized to UpperCamelCase while all other
declarations are lowerCamelCase.
scope.report(warning(position, "class.name.shadow.other.decl", name, kindString(resolved)));
*/
return scope.resolve(name, ClassDecl.class, n -> {
final ClassDecl decl = ClassDecl
.of(null, name, null, PrimitiveType.OBJECT, new LinkedHashMap<>(), new LinkedHashMap<>(),
new ArrayList<>());
decl.setPosition(position);
final ClassType classType = ClassType.of(decl);
classType.setPosition(position);
decl.setType(classType);
return decl;
});
}
// --------------- Methods ---------------
private static MethodDecl getMethod(ClassDecl owner, String name)
{
for (final ClassDecl superClass : owner.getSuperClasses())
{
final MethodDecl decl = getOwnMethod(superClass, name);
if (decl != null)
{
return decl;
}
}
return null;
}
private static MethodDecl getOwnMethod(ClassDecl owner, String name)
{
for (final MethodDecl decl : owner.getMethods())
{
if (name.equals(decl.getName()))
{
return decl;
}
}
return null;
}
static MethodDecl resolveMethod(Scope scope, Position position, ClassDecl owner, String name)
{
final MethodDecl existing = getMethod(owner, name);
return existing != null ? existing : createMethod(scope, position, owner, name);
}
private static MethodDecl createMethod(Scope scope, Position position, ClassDecl owner, String name)
{
if (owner.getExternal())
{
scope.report(error(position, "method.unresolved.external", name, owner.getName()));
return null;
}
else if (owner.getFrozen())
{
scope.report(error(position, "method.unresolved.frozen", name, owner.getName()));
return null;
}
return createMethod(position, owner, name);
}
private static MethodDecl createMethod(Position position, ClassDecl owner, String name)
{
final SentenceList body = SentenceList.of(new ArrayList<>());
final MethodDecl decl = MethodDecl.of(owner, name, new ArrayList<>(), null, body);
decl.setPosition(position);
owner.getMethods().add(decl);
return decl;
}
// --------------- Properties ---------------
static Name getAttributeOrAssociation(Scope scope, Expr receiver, Name name)
{
return getAttributeOrAssociation(scope, receiver, receiver.getType(), name);
}
static Name getAttributeOrAssociation(Scope scope, Type owner, Name name)
{
return getAttributeOrAssociation(scope, null, owner, name);
}
static Name getAttributeOrAssociation(Scope scope, Expr receiver, Type owner, Name name)
{
if (owner == PrimitiveType.ERROR)
{
return name;
}
final ClassDecl ownerClass = owner.accept(ExtractClassDecl.INSTANCE, null);
if (ownerClass != null)
{
return getAttributeOrAssociation(scope, ownerClass, name);
}
final Marker error = error(name.getPosition(), "property.unresolved.primitive", name.getValue(),
owner.getDescription());
if (receiver != null)
{
SentenceResolver.addStringLiteralTypoNotes(scope, receiver, error);
}
scope.report(error);
return name;
}
static Name getAttributeOrAssociation(Scope scope, ClassDecl owner, Name name)
{
if (name.getDecl() != null)
{
return name;
}
final String nameValue = name.getValue();
final Decl decl = getAttributeOrAssociation(owner, nameValue);
if (decl != null)
{
return ResolvedName.of(decl);
}
final Position position = name.getPosition();
final Marker error = error(position, "property.unresolved", owner.getName(), nameValue);
Stream
.concat(owner.getAttributes().keySet().stream(), owner.getAssociations().keySet().stream())
.filter(SentenceResolver.caseInsensitiveLevenshteinDistance(nameValue, UNRESOLVED_HINT_DISTANCE_THRESHOLD))
.forEach(suggestion -> error.note(note(position, "property.typo", suggestion, nameValue)));
scope.report(error);
return name; // unresolved
}
static Decl getAttributeOrAssociation(ClassDecl owner, String name)
{
final AttributeDecl attribute = getAttribute(owner, name);
return attribute != null ? attribute : getAssociation(owner, name);
}
static Decl resolveAttributeOrAssociation(Scope scope, ClassDecl classDecl, String attributeName, Expr rhs,
Position position)
{
final Type attributeType = rhs.getType();
final ClassDecl otherClassDecl = attributeType.accept(ExtractClassDecl.INSTANCE, null);
if (otherClassDecl != null && otherClassDecl.getGroup() == classDecl.getGroup())
{
int cardinality = attributeType instanceof ListType ? MANY : 1;
return resolveAssociation(scope, classDecl, attributeName, cardinality, otherClassDecl, position, rhs);
}
else
{
return resolveAttribute(scope, classDecl, attributeName, attributeType, position, rhs);
}
}
// --------------- Attributes ---------------
static AttributeDecl getAttribute(ClassDecl owner, String name)
{
for (final ClassDecl superClass : owner.getSuperClasses())
{
final AttributeDecl decl = getOwnAttribute(superClass, name);
if (decl != null)
{
return decl;
}
}
return null;
}
static AttributeDecl getOwnAttribute(ClassDecl owner, String name)
{
return owner.getAttributes().get(name);
}
static Decl resolveAttribute(Scope scope, ClassDecl owner, String name, Type type, Position position, Expr rhs)
{
final AttributeDecl existingAttribute = getAttribute(owner, name);
if (existingAttribute != null)
{
final Type existingType = existingAttribute.getType();
if (type != PrimitiveType.ERROR && existingType != PrimitiveType.ERROR //
&& !TypeConversion.isConvertible(type, existingType))
{
final String newDesc = DeclDescriber.describeAttribute(type);
final Marker conflict = conflict(position, owner, name, existingAttribute, newDesc);
SentenceResolver.addStringLiteralTypoNotes(scope, rhs, conflict);
scope.report(conflict);
}
return existingAttribute;
}
final AssociationDecl existingAssociation = getAssociation(owner, name);
if (existingAssociation != null)
{
final String newDesc = DeclDescriber.describeAttribute(type);
final Marker conflict = conflict(position, owner, name, existingAssociation, newDesc);
SentenceResolver.addStringLiteralTypoNotes(scope, rhs, conflict);
scope.report(conflict);
return existingAssociation;
}
return createAttribute(scope, position, owner, name, type);
}
private static AttributeDecl createAttribute(Scope scope, Position position, ClassDecl owner, String name,
Type type)
{
if (owner.getExternal())
{
scope.report(error(position, "attribute.unresolved.external", name, owner.getName()));
return null;
}
else if (owner.getFrozen())
{
scope.report(error(position, "attribute.unresolved.frozen", name, owner.getName()));
return null;
}
return createAttribute(position, owner, name, type);
}
private static AttributeDecl createAttribute(Position position, ClassDecl owner, String name, Type type)
{
final AttributeDecl attribute = AttributeDecl.of(owner, name, type);
attribute.setPosition(position);
owner.getAttributes().put(name, attribute);
return attribute;
}
// --------------- Associations ---------------
static AssociationDecl getAssociation(ClassDecl owner, String name)
{
for (final ClassDecl superClass : owner.getSuperClasses())
{
final AssociationDecl decl = getOwnAssociation(superClass, name);
if (decl != null)
{
return decl;
}
}
return null;
}
static AssociationDecl getOwnAssociation(ClassDecl owner, String name)
{
return owner.getAssociations().get(name);
}
static AssociationDecl resolveAssociation(Scope scope, ClassDecl owner, String name, int cardinality,
ClassDecl otherClass, Position position, Expr rhs)
{
return resolveAssociation(scope, owner, name, cardinality, otherClass, null, 0, position, null, rhs);
}
static AssociationDecl resolveAssociation(Scope scope, ClassDecl owner, String name, int cardinality,
ClassDecl otherClass, String otherName, int otherCardinality, Position position, Position otherPosition, Expr rhs)
{
final AttributeDecl existingAttribute = getAttribute(owner, name);
if (existingAttribute != null)
{
final String newDesc = DeclDescriber.describeAssociation(cardinality, otherClass);
final Marker conflict = conflict(position, owner, name, existingAttribute, newDesc);
SentenceResolver.addStringLiteralTypoNotes(scope, rhs, conflict);
scope.report(conflict);
return null;
}
final AssociationDecl existing = getAssociation(owner, name);
if (existing != null)
{
// uses < because redeclaration as to-one when it was to-many is ok.
// TODO investigate this claim
if (!isSuperClass(existing.getTarget(), otherClass) || existing.getCardinality() < cardinality)
{
final String newDesc = DeclDescriber.describeAssociation(cardinality, otherClass);
final Marker conflict = conflict(position, owner, name, existing, newDesc);
SentenceResolver.addStringLiteralTypoNotes(scope, rhs, conflict);
scope.report(conflict);
}
else if (otherName != null)
{
final AssociationDecl other = existing.getOther();
if (other == null)
{
final Marker error = error(otherPosition, "association.reverse.late", otherName, owner.getName(),
name);
if (existing.getPosition() != null)
{
error.note(firstDeclaration(existing.getPosition(), owner, name));
}
scope.report(error);
}
else if (!otherName.equals(other.getName()) || otherCardinality != other.getCardinality())
{
final String existingDesc = other.accept(DeclDescriber.INSTANCE, null);
final String newDesc = DeclDescriber.describeAssociation(otherCardinality, owner);
final Marker error = error(otherPosition, "association.reverse.conflict", owner.getName(), name);
error.note(note(otherPosition, "conflict.old",
otherClass.getName() + "." + other.getName() + ", " + existingDesc));
error.note(note(otherPosition, "conflict.new", otherClass.getName() + "." + otherName + ", " + newDesc));
if (other.getPosition() != null)
{
error.note(firstDeclaration(other.getPosition(), other.getOwner(), other.getName()));
}
scope.report(error);
}
}
return existing;
}
final AssociationDecl association = createAssociation(scope, position, owner, name, cardinality, otherClass);
if (association == null)
{
// class was external or frozen, error already reported
return null;
}
if (otherClass == owner && name.equals(otherName))
{
if (cardinality != otherCardinality)
{
scope.report(error(position, "association.self.cardinality.mismatch", owner.getName(), name));
}
// self-association
association.setOther(association);
}
else if (otherName != null)
{
final AssociationDecl other = createAssociation(scope, otherPosition, otherClass, otherName,
otherCardinality, owner);
if (other != null)
{
association.setOther(other);
other.setOther(association);
}
}
return association;
}
private static AssociationDecl createAssociation(Scope scope, Position position, ClassDecl owner, String name,
int cardinality, ClassDecl target)
{
if (owner.getExternal())
{
scope.report(error(position, "association.unresolved.external", name, owner.getName()));
return null;
}
else if (owner.getFrozen())
{
scope.report(error(position, "association.unresolved.frozen", name, owner.getName()));
return null;
}
return createAssociation(position, owner, name, cardinality, target);
}
private static AssociationDecl createAssociation(Position position, ClassDecl owner, String name, int cardinality,
ClassDecl target)
{
final Type type = createType(cardinality, target);
final AssociationDecl association = AssociationDecl.of(owner, name, cardinality, target, type, null);
association.setPosition(position);
owner.getAssociations().put(association.getName(), association);
return association;
}
// --------------- Helpers ---------------
private static Type createType(int cardinality, ClassDecl target)
{
return cardinality != 1 ? ListType.of(target.getType()) : target.getType();
}
static Marker firstDeclaration(Position position, ClassDecl owner, String name)
{
return note(position, "property.declaration.first", owner.getName(), name);
}
private static Marker conflict(Position position, ClassDecl owner, String name, Decl existing, String newDesc)
{
final String existingDesc = existing.accept(DeclDescriber.INSTANCE, null);
final Marker error = error(position, "property.redeclaration.conflict", owner.getName(), name);
error.note(note(position, "conflict.old", existingDesc));
error.note(note(position, "conflict.new", newDesc));
final Position existingPosition = existing.getPosition();
if (existingPosition != null)
{
error.note(firstDeclaration(existingPosition, owner, name));
}
return error;
}
private static String kindString(Decl decl)
{
final String simpleName = decl.getClass().getEnclosingClass().getSimpleName();
final String stripped = simpleName.endsWith("Decl") ?
simpleName.substring(0, simpleName.length() - 4) :
simpleName;
final String key = stripped.toLowerCase() + ".kind";
return Marker.localize(key);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy