Skip to content

Commit

Permalink
issue1154: working on compilation errors in tests
Browse files Browse the repository at this point in the history
  • Loading branch information
ftomassetti committed Sep 29, 2017
1 parent 198ba6c commit 18ca9b3
Show file tree
Hide file tree
Showing 18 changed files with 453 additions and 401 deletions.
Expand Up @@ -34,7 +34,6 @@
import com.github.javaparser.symbolsolver.javaparsermodel.declarators.NoSymbolDeclarator; import com.github.javaparser.symbolsolver.javaparsermodel.declarators.NoSymbolDeclarator;
import com.github.javaparser.symbolsolver.javaparsermodel.declarators.ParameterSymbolDeclarator; import com.github.javaparser.symbolsolver.javaparsermodel.declarators.ParameterSymbolDeclarator;
import com.github.javaparser.symbolsolver.javaparsermodel.declarators.VariableSymbolDeclarator; import com.github.javaparser.symbolsolver.javaparsermodel.declarators.VariableSymbolDeclarator;
import com.github.javaparser.symbolsolver.model.declarations.ReferenceTypeDeclaration;
import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; import com.github.javaparser.symbolsolver.model.resolution.TypeSolver;
import com.github.javaparser.symbolsolver.resolution.SymbolDeclarator; import com.github.javaparser.symbolsolver.resolution.SymbolDeclarator;


Expand Down
Expand Up @@ -22,7 +22,9 @@
import com.github.javaparser.resolution.declarations.ResolvedTypeParameterDeclaration; import com.github.javaparser.resolution.declarations.ResolvedTypeParameterDeclaration;
import com.github.javaparser.resolution.types.ResolvedReferenceType; import com.github.javaparser.resolution.types.ResolvedReferenceType;
import com.github.javaparser.resolution.types.ResolvedType; import com.github.javaparser.resolution.types.ResolvedType;
import com.github.javaparser.resolution.types.ResolvedTypeTransformer;
import com.github.javaparser.resolution.types.ResolvedTypeVariable; import com.github.javaparser.resolution.types.ResolvedTypeVariable;
import com.github.javaparser.resolution.types.parametrization.ResolvedTypeParametersMap;
import com.github.javaparser.symbolsolver.javaparsermodel.LambdaArgumentTypePlaceholder; import com.github.javaparser.symbolsolver.javaparsermodel.LambdaArgumentTypePlaceholder;
import com.github.javaparser.symbolsolver.javaparsermodel.declarations.JavaParserTypeVariableDeclaration; import com.github.javaparser.symbolsolver.javaparsermodel.declarations.JavaParserTypeVariableDeclaration;
import com.github.javaparser.symbolsolver.model.resolution.SymbolReference; import com.github.javaparser.symbolsolver.model.resolution.SymbolReference;
Expand Down Expand Up @@ -165,4 +167,45 @@ public boolean mention(List<ResolvedTypeParameterDeclaration> typeParameters) {
return typeParametersValues().stream().anyMatch(tp -> tp.mention(typeParameters)); return typeParametersValues().stream().anyMatch(tp -> tp.mention(typeParameters));
} }


/**
* Execute a transformation on all the type parameters of this element.
*/
@Override
public ResolvedType transformTypeParameters(ResolvedTypeTransformer transformer) {
ResolvedType result = this;
int i = 0;
for (ResolvedType tp : this.typeParametersValues()) {
ResolvedType transformedTp = transformer.transform(tp);
// Identity comparison on purpose
if (transformedTp != tp) {
List<ResolvedType> typeParametersCorrected = result.asReferenceType().typeParametersValues();
typeParametersCorrected.set(i, transformedTp);
result = create(typeDeclaration, typeParametersCorrected);
}
i++;
}
return result;
}

public List<ResolvedReferenceType> getAllAncestors() {
// We need to go through the inheritance line and propagate the type parametes

List<ResolvedReferenceType> ancestors = typeDeclaration.getAllAncestors();

ancestors = ancestors.stream()
.map(a -> typeParametersMap().replaceAll(a).asReferenceType())
.collect(Collectors.toList());

// Avoid repetitions of Object
ancestors.removeIf(a -> a.getQualifiedName().equals(Object.class.getCanonicalName()));
ResolvedReferenceTypeDeclaration objectType = typeSolver.solveType(Object.class.getCanonicalName());
ResolvedReferenceType objectRef = create(objectType);
ancestors.add(objectRef);
return ancestors;
}

public ResolvedReferenceType deriveTypeParameters(ResolvedTypeParametersMap typeParametersMap) {
return create(typeDeclaration, typeParametersMap);
}

} }
Expand Up @@ -22,9 +22,9 @@
import com.github.javaparser.ast.expr.MethodCallExpr; import com.github.javaparser.ast.expr.MethodCallExpr;
import com.github.javaparser.ast.stmt.ReturnStmt; import com.github.javaparser.ast.stmt.ReturnStmt;
import com.github.javaparser.ast.visitor.VoidVisitorAdapter; import com.github.javaparser.ast.visitor.VoidVisitorAdapter;
import com.github.javaparser.resolution.types.ResolvedReferenceType;
import com.github.javaparser.symbolsolver.javaparsermodel.JavaParserFacade; import com.github.javaparser.symbolsolver.javaparsermodel.JavaParserFacade;
import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; import com.github.javaparser.symbolsolver.model.resolution.TypeSolver;
import com.github.javaparser.symbolsolver.model.typesystem.ReferenceType;
import com.github.javaparser.symbolsolver.resolution.typesolvers.CombinedTypeSolver; import com.github.javaparser.symbolsolver.resolution.typesolvers.CombinedTypeSolver;
import com.github.javaparser.symbolsolver.resolution.typesolvers.JavaParserTypeSolver; import com.github.javaparser.symbolsolver.resolution.typesolvers.JavaParserTypeSolver;
import com.github.javaparser.symbolsolver.resolution.typesolvers.ReflectionTypeSolver; import com.github.javaparser.symbolsolver.resolution.typesolvers.ReflectionTypeSolver;
Expand All @@ -48,7 +48,7 @@ public void visit(MethodCallExpr n, JavaParserFacade javaParserFacade) {
super.visit(n, javaParserFacade); super.visit(n, javaParserFacade);
System.out.println(n.toString() + " has type " + javaParserFacade.getType(n).describe()); System.out.println(n.toString() + " has type " + javaParserFacade.getType(n).describe());
if (javaParserFacade.getType(n).isReferenceType()) { if (javaParserFacade.getType(n).isReferenceType()) {
for (ReferenceType ancestor : javaParserFacade.getType(n).asReferenceType().getAllAncestors()) { for (ResolvedReferenceType ancestor : javaParserFacade.getType(n).asReferenceType().getAllAncestors()) {
//System.out.println("Ancestor " + ancestor.describe()); //System.out.println("Ancestor " + ancestor.describe());
} }
} }
Expand Down
@@ -1,13 +1,13 @@
package com.github.javaparser.symbolsolver.logic; package com.github.javaparser.symbolsolver.logic;


import com.github.javaparser.symbolsolver.model.typesystem.Type; import com.github.javaparser.resolution.types.ResolvedType;


/** /**
* @author Federico Tomassetti * @author Federico Tomassetti
*/ */
public class ConfilictingGenericTypesException extends RuntimeException { public class ConfilictingGenericTypesException extends RuntimeException {


public ConfilictingGenericTypesException(Type formalType, Type actualType) { public ConfilictingGenericTypesException(ResolvedType formalType, ResolvedType actualType) {
super(String.format("No matching between %s (formal) and %s (actual)", formalType, actualType)); super(String.format("No matching between %s (formal) and %s (actual)", formalType, actualType));
} }
} }
Expand Up @@ -16,8 +16,8 @@


package com.github.javaparser.symbolsolver.logic; package com.github.javaparser.symbolsolver.logic;


import com.github.javaparser.resolution.types.ResolvedType; import com.github.javaparser.resolution.declarations.ResolvedTypeParameterDeclaration;
import com.github.javaparser.symbolsolver.model.declarations.TypeParameterDeclaration; import com.github.javaparser.resolution.types.*;
import com.github.javaparser.symbolsolver.model.typesystem.*; import com.github.javaparser.symbolsolver.model.typesystem.*;


import java.util.ArrayList; import java.util.ArrayList;
Expand All @@ -41,7 +41,7 @@ public InferenceContext(ObjectProvider objectProvider) {


private Map<String, InferenceVariableType> inferenceVariableTypeMap = new HashMap<>(); private Map<String, InferenceVariableType> inferenceVariableTypeMap = new HashMap<>();


private InferenceVariableType inferenceVariableTypeForTp(TypeParameterDeclaration tp) { private InferenceVariableType inferenceVariableTypeForTp(ResolvedTypeParameterDeclaration tp) {
if (!inferenceVariableTypeMap.containsKey(tp.getName())) { if (!inferenceVariableTypeMap.containsKey(tp.getName())) {
InferenceVariableType inferenceVariableType = new InferenceVariableType(nextInferenceVariableId++, objectProvider); InferenceVariableType inferenceVariableType = new InferenceVariableType(nextInferenceVariableId++, objectProvider);
inferenceVariableTypes.add(inferenceVariableType); inferenceVariableTypes.add(inferenceVariableType);
Expand All @@ -66,19 +66,19 @@ public ResolvedType addSingle(ResolvedType actual) {
return placeInferenceVariables(actual); return placeInferenceVariables(actual);
} }


private void registerCorrespondance(Type formalType, Type actualType) { private void registerCorrespondance(ResolvedType formalType, ResolvedType actualType) {
if (formalType.isReferenceType() && actualType.isReferenceType()) { if (formalType.isReferenceType() && actualType.isReferenceType()) {
ReferenceType formalTypeAsReference = formalType.asReferenceType(); ResolvedReferenceType formalTypeAsReference = formalType.asReferenceType();
ReferenceType actualTypeAsReference = actualType.asReferenceType(); ResolvedReferenceType actualTypeAsReference = actualType.asReferenceType();


if (!formalTypeAsReference.getQualifiedName().equals(actualTypeAsReference.getQualifiedName())) { if (!formalTypeAsReference.getQualifiedName().equals(actualTypeAsReference.getQualifiedName())) {
List<ReferenceType> ancestors = actualTypeAsReference.getAllAncestors(); List<ResolvedReferenceType> ancestors = actualTypeAsReference.getAllAncestors();
final String formalParamTypeQName = formalTypeAsReference.getQualifiedName(); final String formalParamTypeQName = formalTypeAsReference.getQualifiedName();
List<Type> correspondingFormalType = ancestors.stream().filter((a) -> a.getQualifiedName().equals(formalParamTypeQName)).collect(Collectors.toList()); List<ResolvedType> correspondingFormalType = ancestors.stream().filter((a) -> a.getQualifiedName().equals(formalParamTypeQName)).collect(Collectors.toList());
if (correspondingFormalType.isEmpty()) { if (correspondingFormalType.isEmpty()) {
ancestors = formalTypeAsReference.getAllAncestors(); ancestors = formalTypeAsReference.getAllAncestors();
final String actualParamTypeQname = actualTypeAsReference.getQualifiedName(); final String actualParamTypeQname = actualTypeAsReference.getQualifiedName();
List<Type> correspondingActualType = ancestors.stream().filter(a -> a.getQualifiedName().equals(actualParamTypeQname)).collect(Collectors.toList()); List<ResolvedType> correspondingActualType = ancestors.stream().filter(a -> a.getQualifiedName().equals(actualParamTypeQname)).collect(Collectors.toList());
if (correspondingActualType.isEmpty()){ if (correspondingActualType.isEmpty()){
throw new ConfilictingGenericTypesException(formalType, actualType); throw new ConfilictingGenericTypesException(formalType, actualType);
} }
Expand All @@ -94,7 +94,7 @@ private void registerCorrespondance(Type formalType, Type actualType) {
// nothing to do // nothing to do
} else { } else {
int i = 0; int i = 0;
for (Type formalTypeParameter : formalTypeAsReference.typeParametersValues()) { for (ResolvedType formalTypeParameter : formalTypeAsReference.typeParametersValues()) {
registerCorrespondance(formalTypeParameter, actualTypeAsReference.typeParametersValues().get(i)); registerCorrespondance(formalTypeParameter, actualTypeAsReference.typeParametersValues().get(i));
i++; i++;
} }
Expand All @@ -121,8 +121,8 @@ private void registerCorrespondance(Type formalType, Type actualType) {
} }
} }
if (actualType.isWildcard()) { if (actualType.isWildcard()) {
Wildcard formalWildcard = formalType.asWildcard(); ResolvedWildcard formalWildcard = formalType.asWildcard();
Wildcard actualWildcard = actualType.asWildcard(); ResolvedWildcard actualWildcard = actualType.asWildcard();
if (formalWildcard.isBounded() && formalWildcard.getBoundedType() instanceof InferenceVariableType) { if (formalWildcard.isBounded() && formalWildcard.getBoundedType() instanceof InferenceVariableType) {
if (formalWildcard.isSuper() && actualWildcard.isSuper()) { if (formalWildcard.isSuper() && actualWildcard.isSuper()) {
((InferenceVariableType) formalType.asWildcard().getBoundedType()).registerEquivalentType(actualWildcard.getBoundedType()); ((InferenceVariableType) formalType.asWildcard().getBoundedType()).registerEquivalentType(actualWildcard.getBoundedType());
Expand All @@ -138,13 +138,13 @@ private void registerCorrespondance(Type formalType, Type actualType) {
} }
} }
} else if (actualType instanceof InferenceVariableType){ } else if (actualType instanceof InferenceVariableType){
if (formalType instanceof ReferenceType){ if (formalType instanceof ResolvedReferenceType){
((InferenceVariableType) actualType).registerEquivalentType(formalType); ((InferenceVariableType) actualType).registerEquivalentType(formalType);
} else if (formalType instanceof InferenceVariableType){ } else if (formalType instanceof InferenceVariableType){
((InferenceVariableType) actualType).registerEquivalentType(formalType); ((InferenceVariableType) actualType).registerEquivalentType(formalType);
} }
} else if (actualType.isConstraint()){ } else if (actualType.isConstraint()){
LambdaConstraintType constraintType = actualType.asConstraintType(); ResolvedLambdaConstraintType constraintType = actualType.asConstraintType();
if (constraintType.getBound() instanceof InferenceVariableType){ if (constraintType.getBound() instanceof InferenceVariableType){
((InferenceVariableType) constraintType.getBound()).registerEquivalentType(formalType); ((InferenceVariableType) constraintType.getBound()).registerEquivalentType(formalType);
} }
Expand All @@ -159,12 +159,12 @@ private void registerCorrespondance(Type formalType, Type actualType) {
} }
} }


private Type placeInferenceVariables(Type type) { private ResolvedType placeInferenceVariables(ResolvedType type) {
if (type.isWildcard()) { if (type.isWildcard()) {
if (type.asWildcard().isExtends()) { if (type.asWildcard().isExtends()) {
return Wildcard.extendsBound(placeInferenceVariables(type.asWildcard().getBoundedType())); return ResolvedWildcard.extendsBound(placeInferenceVariables(type.asWildcard().getBoundedType()));
} else if (type.asWildcard().isSuper()) { } else if (type.asWildcard().isSuper()) {
return Wildcard.superBound(placeInferenceVariables(type.asWildcard().getBoundedType())); return ResolvedWildcard.superBound(placeInferenceVariables(type.asWildcard().getBoundedType()));
} else { } else {
return type; return type;
} }
Expand All @@ -173,11 +173,11 @@ private Type placeInferenceVariables(Type type) {
} else if (type.isReferenceType()) { } else if (type.isReferenceType()) {
return type.asReferenceType().transformTypeParameters(tp -> placeInferenceVariables(tp)); return type.asReferenceType().transformTypeParameters(tp -> placeInferenceVariables(tp));
} else if (type.isArray()) { } else if (type.isArray()) {
return new ArrayType(placeInferenceVariables(type.asArrayType().getComponentType())); return new ResolvedArrayType(placeInferenceVariables(type.asArrayType().getComponentType()));
} else if (type.isNull() || type.isPrimitive() || type.isVoid()) { } else if (type.isNull() || type.isPrimitive() || type.isVoid()) {
return type; return type;
} else if (type.isConstraint()){ } else if (type.isConstraint()){
return LambdaConstraintType.bound(placeInferenceVariables(type.asConstraintType().getBound())); return ResolvedLambdaConstraintType.bound(placeInferenceVariables(type.asConstraintType().getBound()));
} else if (type instanceof InferenceVariableType) { } else if (type instanceof InferenceVariableType) {
return type; return type;
} else { } else {
Expand All @@ -194,12 +194,12 @@ public ResolvedType resolve(ResolvedType type) {
} else if (type.isNull() || type.isPrimitive() || type.isVoid()) { } else if (type.isNull() || type.isPrimitive() || type.isVoid()) {
return type; return type;
} else if (type.isArray()) { } else if (type.isArray()) {
return new ArrayType(resolve(type.asArrayType().getComponentType())); return new ResolvedArrayType(resolve(type.asArrayType().getComponentType()));
} else if (type.isWildcard()) { } else if (type.isWildcard()) {
if (type.asWildcard().isExtends()) { if (type.asWildcard().isExtends()) {
return Wildcard.extendsBound(resolve(type.asWildcard().getBoundedType())); return ResolvedWildcard.extendsBound(resolve(type.asWildcard().getBoundedType()));
} else if (type.asWildcard().isSuper()) { } else if (type.asWildcard().isSuper()) {
return Wildcard.superBound(resolve(type.asWildcard().getBoundedType())); return ResolvedWildcard.superBound(resolve(type.asWildcard().getBoundedType()));
} else { } else {
return type; return type;
} }
Expand Down
Expand Up @@ -16,11 +16,11 @@


package com.github.javaparser.symbolsolver.logic; package com.github.javaparser.symbolsolver.logic;


import com.github.javaparser.symbolsolver.model.declarations.TypeParameterDeclaration; import com.github.javaparser.resolution.declarations.ResolvedTypeParameterDeclaration;
import com.github.javaparser.symbolsolver.model.typesystem.ReferenceType; import com.github.javaparser.resolution.types.ResolvedReferenceType;
import com.github.javaparser.symbolsolver.model.typesystem.Type; import com.github.javaparser.resolution.types.ResolvedType;
import com.github.javaparser.symbolsolver.model.typesystem.TypeVariable; import com.github.javaparser.resolution.types.ResolvedTypeVariable;
import com.github.javaparser.symbolsolver.model.typesystem.Wildcard; import com.github.javaparser.resolution.types.ResolvedWildcard;


import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
Expand All @@ -31,7 +31,7 @@
* *
* @author Federico Tomassetti * @author Federico Tomassetti
*/ */
public class InferenceVariableType implements Type { public class InferenceVariableType implements ResolvedType {
@Override @Override
public String toString() { public String toString() {
return "InferenceVariableType{" + return "InferenceVariableType{" +
Expand All @@ -40,16 +40,16 @@ public String toString() {
} }


private int id; private int id;
private TypeParameterDeclaration correspondingTp; private ResolvedTypeParameterDeclaration correspondingTp;


public void setCorrespondingTp(TypeParameterDeclaration correspondingTp) { public void setCorrespondingTp(ResolvedTypeParameterDeclaration correspondingTp) {
this.correspondingTp = correspondingTp; this.correspondingTp = correspondingTp;
} }


private Set<Type> equivalentTypes = new HashSet<>(); private Set<ResolvedType> equivalentTypes = new HashSet<>();
private ObjectProvider objectProvider; private ObjectProvider objectProvider;


public void registerEquivalentType(Type type) { public void registerEquivalentType(ResolvedType type) {
this.equivalentTypes.add(type); this.equivalentTypes.add(type);
} }


Expand All @@ -69,14 +69,14 @@ public int hashCode() {
return id; return id;
} }


private Set<Type> superTypes = new HashSet<>(); private Set<ResolvedType> superTypes = new HashSet<>();


public InferenceVariableType(int id, ObjectProvider objectProvider) { public InferenceVariableType(int id, ObjectProvider objectProvider) {
this.id = id; this.id = id;
this.objectProvider = objectProvider; this.objectProvider = objectProvider;
} }


public static InferenceVariableType fromWildcard(Wildcard wildcard, int id, ObjectProvider objectProvider) { public static InferenceVariableType fromWildcard(ResolvedWildcard wildcard, int id, ObjectProvider objectProvider) {
InferenceVariableType inferenceVariableType = new InferenceVariableType(id, objectProvider); InferenceVariableType inferenceVariableType = new InferenceVariableType(id, objectProvider);
if (wildcard.isExtends()) { if (wildcard.isExtends()) {
inferenceVariableType.superTypes.add(wildcard.getBoundedType()); inferenceVariableType.superTypes.add(wildcard.getBoundedType());
Expand All @@ -94,13 +94,13 @@ public String describe() {
} }


@Override @Override
public boolean isAssignableBy(Type other) { public boolean isAssignableBy(ResolvedType other) {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }


private Set<Type> concreteEquivalentTypesAlsoIndirectly(Set<InferenceVariableType> considered, InferenceVariableType inferenceVariableType) { private Set<ResolvedType> concreteEquivalentTypesAlsoIndirectly(Set<InferenceVariableType> considered, InferenceVariableType inferenceVariableType) {
considered.add(inferenceVariableType); considered.add(inferenceVariableType);
Set<Type> result = new HashSet<>(); Set<ResolvedType> result = new HashSet<>();
result.addAll(inferenceVariableType.equivalentTypes.stream().filter(t -> !t.isTypeVariable() && !(t instanceof InferenceVariableType)).collect(Collectors.toSet())); result.addAll(inferenceVariableType.equivalentTypes.stream().filter(t -> !t.isTypeVariable() && !(t instanceof InferenceVariableType)).collect(Collectors.toSet()));
inferenceVariableType.equivalentTypes.stream().filter(t -> t instanceof InferenceVariableType).forEach(t -> { inferenceVariableType.equivalentTypes.stream().filter(t -> t instanceof InferenceVariableType).forEach(t -> {
InferenceVariableType ivt = (InferenceVariableType)t; InferenceVariableType ivt = (InferenceVariableType)t;
Expand All @@ -111,19 +111,19 @@ private Set<Type> concreteEquivalentTypesAlsoIndirectly(Set<InferenceVariableTyp
return result; return result;
} }


public Type equivalentType() { public ResolvedType equivalentType() {
Set<Type> concreteEquivalent = concreteEquivalentTypesAlsoIndirectly(new HashSet<>(), this); Set<ResolvedType> concreteEquivalent = concreteEquivalentTypesAlsoIndirectly(new HashSet<>(), this);
if (concreteEquivalent.isEmpty()) { if (concreteEquivalent.isEmpty()) {
if (correspondingTp == null) { if (correspondingTp == null) {
return objectProvider.object(); return objectProvider.object();
} else { } else {
return new TypeVariable(correspondingTp); return new ResolvedTypeVariable(correspondingTp);
} }
} }
if (concreteEquivalent.size() == 1) { if (concreteEquivalent.size() == 1) {
return concreteEquivalent.iterator().next(); return concreteEquivalent.iterator().next();
} }
Set<Type> notTypeVariables = equivalentTypes.stream() Set<ResolvedType> notTypeVariables = equivalentTypes.stream()
.filter(t -> !t.isTypeVariable() && !hasInferenceVariables(t)) .filter(t -> !t.isTypeVariable() && !hasInferenceVariables(t))
.collect(Collectors.toSet()); .collect(Collectors.toSet());
if (notTypeVariables.size() == 1) { if (notTypeVariables.size() == 1) {
Expand All @@ -139,14 +139,14 @@ public Type equivalentType() {
} }
} }


private boolean hasInferenceVariables(Type type){ private boolean hasInferenceVariables(ResolvedType type){
if (type instanceof InferenceVariableType){ if (type instanceof InferenceVariableType){
return true; return true;
} }


if (type.isReferenceType()){ if (type.isReferenceType()){
ReferenceType refType = type.asReferenceType(); ResolvedReferenceType refType = type.asReferenceType();
for (Type t : refType.typeParametersValues()){ for (ResolvedType t : refType.typeParametersValues()){
if (hasInferenceVariables(t)){ if (hasInferenceVariables(t)){
return true; return true;
} }
Expand All @@ -155,7 +155,7 @@ private boolean hasInferenceVariables(Type type){
} }


if (type.isWildcard()){ if (type.isWildcard()){
Wildcard wildcardType = type.asWildcard(); ResolvedWildcard wildcardType = type.asWildcard();
return hasInferenceVariables(wildcardType.getBoundedType()); return hasInferenceVariables(wildcardType.getBoundedType());
} }


Expand Down

0 comments on commit 18ca9b3

Please sign in to comment.