Home | History | Annotate | Download | only in javaparsermodel
      1 package com.github.javaparser.symbolsolver.javaparsermodel;
      2 
      3 import com.github.javaparser.ast.CompilationUnit;
      4 import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration;
      5 import com.github.javaparser.ast.body.FieldDeclaration;
      6 import com.github.javaparser.ast.body.Parameter;
      7 import com.github.javaparser.ast.body.VariableDeclarator;
      8 import com.github.javaparser.ast.expr.*;
      9 import com.github.javaparser.ast.stmt.BlockStmt;
     10 import com.github.javaparser.ast.stmt.ExpressionStmt;
     11 import com.github.javaparser.ast.stmt.ReturnStmt;
     12 import com.github.javaparser.ast.type.UnknownType;
     13 import com.github.javaparser.resolution.MethodUsage;
     14 import com.github.javaparser.resolution.UnsolvedSymbolException;
     15 import com.github.javaparser.resolution.declarations.ResolvedClassDeclaration;
     16 import com.github.javaparser.resolution.declarations.ResolvedMethodDeclaration;
     17 import com.github.javaparser.resolution.declarations.ResolvedReferenceTypeDeclaration;
     18 import com.github.javaparser.resolution.declarations.ResolvedTypeDeclaration;
     19 import com.github.javaparser.resolution.types.ResolvedArrayType;
     20 import com.github.javaparser.resolution.types.ResolvedPrimitiveType;
     21 import com.github.javaparser.resolution.types.ResolvedType;
     22 import com.github.javaparser.resolution.types.ResolvedVoidType;
     23 import com.github.javaparser.symbolsolver.core.resolution.Context;
     24 import com.github.javaparser.symbolsolver.javaparsermodel.declarations.JavaParserSymbolDeclaration;
     25 import com.github.javaparser.symbolsolver.logic.FunctionalInterfaceLogic;
     26 import com.github.javaparser.symbolsolver.logic.InferenceContext;
     27 import com.github.javaparser.symbolsolver.model.resolution.SymbolReference;
     28 import com.github.javaparser.symbolsolver.model.resolution.TypeSolver;
     29 import com.github.javaparser.symbolsolver.model.resolution.Value;
     30 import com.github.javaparser.symbolsolver.model.typesystem.*;
     31 import com.github.javaparser.symbolsolver.reflectionmodel.MyObjectProvider;
     32 import com.github.javaparser.symbolsolver.reflectionmodel.ReflectionClassDeclaration;
     33 import com.github.javaparser.symbolsolver.resolution.SymbolSolver;
     34 import com.github.javaparser.symbolsolver.resolution.typesolvers.ReflectionTypeSolver;
     35 import com.google.common.collect.ImmutableList;
     36 
     37 import java.util.List;
     38 import java.util.Optional;
     39 import java.util.logging.ConsoleHandler;
     40 import java.util.logging.Level;
     41 import java.util.logging.Logger;
     42 
     43 import static com.github.javaparser.symbolsolver.javaparser.Navigator.requireParentNode;
     44 import static com.github.javaparser.symbolsolver.javaparsermodel.JavaParserFacade.solveGenericTypes;
     45 
     46 public class TypeExtractor extends DefaultVisitorAdapter {
     47 
     48     private static Logger logger = Logger.getLogger(TypeExtractor.class.getCanonicalName());
     49 
     50     static {
     51         logger.setLevel(Level.INFO);
     52         ConsoleHandler consoleHandler = new ConsoleHandler();
     53         consoleHandler.setLevel(Level.INFO);
     54         logger.addHandler(consoleHandler);
     55     }
     56 
     57     private TypeSolver typeSolver;
     58     private JavaParserFacade facade;
     59 
     60     public TypeExtractor(TypeSolver typeSolver, JavaParserFacade facade) {
     61         this.typeSolver = typeSolver;
     62         this.facade = facade;
     63     }
     64 
     65     @Override
     66     public ResolvedType visit(VariableDeclarator node, Boolean solveLambdas) {
     67         if (requireParentNode(node) instanceof FieldDeclaration) {
     68             return facade.convertToUsageVariableType(node);
     69         } else if (requireParentNode(node) instanceof VariableDeclarationExpr) {
     70             return facade.convertToUsageVariableType(node);
     71         }
     72         throw new UnsupportedOperationException(requireParentNode(node).getClass().getCanonicalName());
     73     }
     74 
     75     @Override
     76     public ResolvedType visit(Parameter node, Boolean solveLambdas) {
     77         if (node.getType() instanceof UnknownType) {
     78             throw new IllegalStateException("Parameter has unknown type: " + node);
     79         }
     80         return facade.convertToUsage(node.getType(), node);
     81     }
     82 
     83 
     84     @Override
     85     public ResolvedType visit(ArrayAccessExpr node, Boolean solveLambdas) {
     86         ResolvedType arrayUsageType = node.getName().accept(this, solveLambdas);
     87         if (arrayUsageType.isArray()) {
     88             return ((ResolvedArrayType) arrayUsageType).getComponentType();
     89         }
     90         return arrayUsageType;
     91     }
     92 
     93     @Override
     94     public ResolvedType visit(ArrayCreationExpr node, Boolean solveLambdas) {
     95         ResolvedType res = facade.convertToUsage(node.getElementType(), JavaParserFactory.getContext(node, typeSolver));
     96         for (int i = 0; i < node.getLevels().size(); i++) {
     97             res = new ResolvedArrayType(res);
     98         }
     99         return res;
    100     }
    101 
    102     @Override
    103     public ResolvedType visit(ArrayInitializerExpr node, Boolean solveLambdas) {
    104         throw new UnsupportedOperationException(node.getClass().getCanonicalName());
    105     }
    106 
    107     @Override
    108     public ResolvedType visit(AssignExpr node, Boolean solveLambdas) {
    109         return node.getTarget().accept(this, solveLambdas);
    110     }
    111 
    112     @Override
    113     public ResolvedType visit(BinaryExpr node, Boolean solveLambdas) {
    114         switch (node.getOperator()) {
    115             case PLUS:
    116             case MINUS:
    117             case DIVIDE:
    118             case MULTIPLY:
    119                 return facade.getBinaryTypeConcrete(node.getLeft(), node.getRight(), solveLambdas);
    120             case LESS_EQUALS:
    121             case LESS:
    122             case GREATER:
    123             case GREATER_EQUALS:
    124             case EQUALS:
    125             case NOT_EQUALS:
    126             case OR:
    127             case AND:
    128                 return ResolvedPrimitiveType.BOOLEAN;
    129             case BINARY_AND:
    130             case BINARY_OR:
    131             case SIGNED_RIGHT_SHIFT:
    132             case UNSIGNED_RIGHT_SHIFT:
    133             case LEFT_SHIFT:
    134             case REMAINDER:
    135             case XOR:
    136                 return node.getLeft().accept(this, solveLambdas);
    137             default:
    138                 throw new UnsupportedOperationException("Operator " + node.getOperator().name());
    139         }
    140     }
    141 
    142     @Override
    143     public ResolvedType visit(CastExpr node, Boolean solveLambdas) {
    144         return facade.convertToUsage(node.getType(), JavaParserFactory.getContext(node, typeSolver));
    145     }
    146 
    147     @Override
    148     public ResolvedType visit(ClassExpr node, Boolean solveLambdas) {
    149         // This implementation does not regard the actual type argument of the ClassExpr.
    150         com.github.javaparser.ast.type.Type astType = node.getType();
    151         ResolvedType jssType = facade.convertToUsage(astType, node.getType());
    152         return new ReferenceTypeImpl(new ReflectionClassDeclaration(Class.class, typeSolver), ImmutableList.of(jssType), typeSolver);
    153     }
    154 
    155     @Override
    156     public ResolvedType visit(ConditionalExpr node, Boolean solveLambdas) {
    157         return node.getThenExpr().accept(this, solveLambdas);
    158     }
    159 
    160     @Override
    161     public ResolvedType visit(EnclosedExpr node, Boolean solveLambdas) {
    162         return node.getInner().accept(this, solveLambdas);
    163     }
    164 
    165     /**
    166      * Java Parser can't differentiate between packages, internal types, and fields.
    167      * All three are lumped together into FieldAccessExpr. We need to differentiate them.
    168      */
    169     private ResolvedType solveDotExpressionType(ResolvedReferenceTypeDeclaration parentType, FieldAccessExpr node) {
    170         // Fields and internal type declarations cannot have the same name.
    171         // Thus, these checks will always be mutually exclusive.
    172         if (parentType.hasField(node.getName().getId())) {
    173             return parentType.getField(node.getName().getId()).getType();
    174         } else if (parentType.hasInternalType(node.getName().getId())) {
    175             return new ReferenceTypeImpl(parentType.getInternalType(node.getName().getId()), typeSolver);
    176         } else {
    177             throw new UnsolvedSymbolException(node.getName().getId());
    178         }
    179     }
    180 
    181     @Override
    182     public ResolvedType visit(FieldAccessExpr node, Boolean solveLambdas) {
    183         // We should understand if this is a static access
    184         if (node.getScope() instanceof NameExpr ||
    185                 node.getScope() instanceof FieldAccessExpr) {
    186             Expression staticValue = node.getScope();
    187             SymbolReference<ResolvedTypeDeclaration> typeAccessedStatically = JavaParserFactory.getContext(node, typeSolver).solveType(staticValue.toString(), typeSolver);
    188             if (typeAccessedStatically.isSolved()) {
    189                 // TODO here maybe we have to substitute type typeParametersValues
    190                 return solveDotExpressionType(
    191                         typeAccessedStatically.getCorrespondingDeclaration().asReferenceType(), node);
    192             }
    193         } else if (node.getScope() instanceof ThisExpr) {
    194             // If we are accessing through a 'this' expression, first resolve the type
    195             // corresponding to 'this'
    196             SymbolReference<ResolvedTypeDeclaration> solve = facade.solve((ThisExpr) node.getScope());
    197             // If found get it's declaration and get the field in there
    198             if (solve.isSolved()) {
    199                 ResolvedTypeDeclaration correspondingDeclaration = solve.getCorrespondingDeclaration();
    200                 if (correspondingDeclaration instanceof ResolvedReferenceTypeDeclaration) {
    201                     return solveDotExpressionType(correspondingDeclaration.asReferenceType(), node);
    202                 }
    203             }
    204 
    205         } else if (node.getScope().toString().indexOf('.') > 0) {
    206             // try to find fully qualified name
    207             SymbolReference<ResolvedReferenceTypeDeclaration> sr = typeSolver.tryToSolveType(node.getScope().toString());
    208             if (sr.isSolved()) {
    209                 return solveDotExpressionType(sr.getCorrespondingDeclaration(), node);
    210             }
    211         }
    212         Optional<Value> value = Optional.empty();
    213         try {
    214             value = new SymbolSolver(typeSolver).solveSymbolAsValue(node.getName().getId(), node);
    215         } catch (com.github.javaparser.resolution.UnsolvedSymbolException use) {
    216             // This node may have a package name as part of its fully qualified name.
    217             // We should solve for the type declaration inside this package.
    218             SymbolReference<ResolvedReferenceTypeDeclaration> sref = typeSolver.tryToSolveType(node.toString());
    219             if (sref.isSolved()) {
    220                 return new ReferenceTypeImpl(sref.getCorrespondingDeclaration(), typeSolver);
    221             }
    222         }
    223         if (value.isPresent()) {
    224             return value.get().getType();
    225         }
    226         throw new com.github.javaparser.resolution.UnsolvedSymbolException(node.getName().getId());
    227     }
    228 
    229     @Override
    230     public ResolvedType visit(InstanceOfExpr node, Boolean solveLambdas) {
    231         return ResolvedPrimitiveType.BOOLEAN;
    232     }
    233 
    234     @Override
    235     public ResolvedType visit(StringLiteralExpr node, Boolean solveLambdas) {
    236         return new ReferenceTypeImpl(new ReflectionTypeSolver().solveType(String.class.getCanonicalName()), typeSolver);
    237     }
    238 
    239     @Override
    240     public ResolvedType visit(IntegerLiteralExpr node, Boolean solveLambdas) {
    241         return ResolvedPrimitiveType.INT;
    242     }
    243 
    244     @Override
    245     public ResolvedType visit(LongLiteralExpr node, Boolean solveLambdas) {
    246         return ResolvedPrimitiveType.LONG;
    247     }
    248 
    249     @Override
    250     public ResolvedType visit(CharLiteralExpr node, Boolean solveLambdas) {
    251         return ResolvedPrimitiveType.CHAR;
    252     }
    253 
    254     @Override
    255     public ResolvedType visit(DoubleLiteralExpr node, Boolean solveLambdas) {
    256         if (node.getValue().toLowerCase().endsWith("f")) {
    257             return ResolvedPrimitiveType.FLOAT;
    258         }
    259         return ResolvedPrimitiveType.DOUBLE;
    260     }
    261 
    262     @Override
    263     public ResolvedType visit(BooleanLiteralExpr node, Boolean solveLambdas) {
    264         return ResolvedPrimitiveType.BOOLEAN;
    265     }
    266 
    267     @Override
    268     public ResolvedType visit(NullLiteralExpr node, Boolean solveLambdas) {
    269         return NullType.INSTANCE;
    270     }
    271 
    272     @Override
    273     public ResolvedType visit(MethodCallExpr node, Boolean solveLambdas) {
    274         logger.finest("getType on method call " + node);
    275         // first solve the method
    276         MethodUsage ref = facade.solveMethodAsUsage(node);
    277         logger.finest("getType on method call " + node + " resolved to " + ref);
    278         logger.finest("getType on method call " + node + " return type is " + ref.returnType());
    279         return ref.returnType();
    280         // the type is the return type of the method
    281     }
    282 
    283     @Override
    284     public ResolvedType visit(NameExpr node, Boolean solveLambdas) {
    285         logger.finest("getType on name expr " + node);
    286         Optional<Value> value = new SymbolSolver(typeSolver).solveSymbolAsValue(node.getName().getId(), node);
    287         if (!value.isPresent()) {
    288             throw new com.github.javaparser.resolution.UnsolvedSymbolException("Solving " + node, node.getName().getId());
    289         } else {
    290             return value.get().getType();
    291         }
    292     }
    293 
    294     @Override
    295     public ResolvedType visit(ObjectCreationExpr node, Boolean solveLambdas) {
    296         return facade.convertToUsage(node.getType(), node);
    297     }
    298 
    299     @Override
    300     public ResolvedType visit(ThisExpr node, Boolean solveLambdas) {
    301         // If 'this' is prefixed by a class eg. MyClass.this
    302         if (node.getClassExpr().isPresent()) {
    303             // Get the class name
    304             String className = node.getClassExpr().get().toString();
    305             // Attempt to resolve using a typeSolver
    306             SymbolReference<ResolvedReferenceTypeDeclaration> clazz = typeSolver.tryToSolveType(className);
    307             if (clazz.isSolved()) {
    308                 return new ReferenceTypeImpl(clazz.getCorrespondingDeclaration(), typeSolver);
    309             }
    310             // Attempt to resolve locally in Compilation unit
    311             Optional<CompilationUnit> cu = node.getAncestorOfType(CompilationUnit.class);
    312             if (cu.isPresent()) {
    313                 Optional<ClassOrInterfaceDeclaration> classByName = cu.get().getClassByName(className);
    314                 if (classByName.isPresent()) {
    315                     return new ReferenceTypeImpl(facade.getTypeDeclaration(classByName.get()), typeSolver);
    316                 }
    317             }
    318 
    319         }
    320         return new ReferenceTypeImpl(facade.getTypeDeclaration(facade.findContainingTypeDeclOrObjectCreationExpr(node)), typeSolver);
    321     }
    322 
    323     @Override
    324     public ResolvedType visit(SuperExpr node, Boolean solveLambdas) {
    325         ResolvedTypeDeclaration typeOfNode = facade.getTypeDeclaration(facade.findContainingTypeDecl(node));
    326         if (typeOfNode instanceof ResolvedClassDeclaration) {
    327             return ((ResolvedClassDeclaration) typeOfNode).getSuperClass();
    328         } else {
    329             throw new UnsupportedOperationException(node.getClass().getCanonicalName());
    330         }
    331     }
    332 
    333     @Override
    334     public ResolvedType visit(UnaryExpr node, Boolean solveLambdas) {
    335         switch (node.getOperator()) {
    336             case MINUS:
    337             case PLUS:
    338                 return node.getExpression().accept(this, solveLambdas);
    339             case LOGICAL_COMPLEMENT:
    340                 return ResolvedPrimitiveType.BOOLEAN;
    341             case POSTFIX_DECREMENT:
    342             case PREFIX_DECREMENT:
    343             case POSTFIX_INCREMENT:
    344             case PREFIX_INCREMENT:
    345                 return node.getExpression().accept(this, solveLambdas);
    346             default:
    347                 throw new UnsupportedOperationException(node.getOperator().name());
    348         }
    349     }
    350 
    351     @Override
    352     public ResolvedType visit(VariableDeclarationExpr node, Boolean solveLambdas) {
    353         if (node.getVariables().size() != 1) {
    354             throw new UnsupportedOperationException();
    355         }
    356         return facade.convertToUsageVariableType(node.getVariables().get(0));
    357     }
    358 
    359 
    360     @Override
    361     public ResolvedType visit(LambdaExpr node, Boolean solveLambdas) {
    362         if (requireParentNode(node) instanceof MethodCallExpr) {
    363             MethodCallExpr callExpr = (MethodCallExpr) requireParentNode(node);
    364             int pos = JavaParserSymbolDeclaration.getParamPos(node);
    365             SymbolReference<ResolvedMethodDeclaration> refMethod = facade.solve(callExpr);
    366             if (!refMethod.isSolved()) {
    367                 throw new com.github.javaparser.resolution.UnsolvedSymbolException(requireParentNode(node).toString(), callExpr.getName().getId());
    368             }
    369             logger.finest("getType on lambda expr " + refMethod.getCorrespondingDeclaration().getName());
    370             if (solveLambdas) {
    371 
    372                 // The type parameter referred here should be the java.util.stream.Stream.T
    373                 ResolvedType result = refMethod.getCorrespondingDeclaration().getParam(pos).getType();
    374 
    375                 if (callExpr.getScope().isPresent()) {
    376                     Expression scope = callExpr.getScope().get();
    377 
    378                     // If it is a static call we should not try to get the type of the scope
    379                     boolean staticCall = false;
    380                     if (scope instanceof NameExpr) {
    381                         NameExpr nameExpr = (NameExpr) scope;
    382                         try {
    383                             SymbolReference<ResolvedTypeDeclaration> type = JavaParserFactory.getContext(nameExpr, typeSolver).solveType(nameExpr.getName().getId(), typeSolver);
    384                             if (type.isSolved()) {
    385                                 staticCall = true;
    386                             }
    387                         } catch (Exception e) {
    388 
    389                         }
    390                     }
    391 
    392                     if (!staticCall) {
    393                         ResolvedType scopeType = facade.getType(scope);
    394                         if (scopeType.isReferenceType()) {
    395                             result = scopeType.asReferenceType().useThisTypeParametersOnTheGivenType(result);
    396                         }
    397                     }
    398                 }
    399 
    400                 // We need to replace the type variables
    401                 Context ctx = JavaParserFactory.getContext(node, typeSolver);
    402                 result = solveGenericTypes(result, ctx, typeSolver);
    403 
    404                 //We should find out which is the functional method (e.g., apply) and replace the params of the
    405                 //solveLambdas with it, to derive so the values. We should also consider the value returned by the
    406                 //lambdas
    407                 Optional<MethodUsage> functionalMethod = FunctionalInterfaceLogic.getFunctionalMethod(result);
    408                 if (functionalMethod.isPresent()) {
    409                     LambdaExpr lambdaExpr = node;
    410 
    411                     InferenceContext lambdaCtx = new InferenceContext(MyObjectProvider.INSTANCE);
    412                     InferenceContext funcInterfaceCtx = new InferenceContext(MyObjectProvider.INSTANCE);
    413 
    414                     // At this point parameterType
    415                     // if Function<T=? super Stream.T, ? extends map.R>
    416                     // we should replace Stream.T
    417                     ResolvedType functionalInterfaceType = ReferenceTypeImpl.undeterminedParameters(functionalMethod.get().getDeclaration().declaringType(), typeSolver);
    418 
    419                     lambdaCtx.addPair(result, functionalInterfaceType);
    420 
    421                     ResolvedType actualType;
    422 
    423                     if (lambdaExpr.getBody() instanceof ExpressionStmt) {
    424                         actualType = facade.getType(((ExpressionStmt) lambdaExpr.getBody()).getExpression());
    425                     } else if (lambdaExpr.getBody() instanceof BlockStmt) {
    426                         BlockStmt blockStmt = (BlockStmt) lambdaExpr.getBody();
    427 
    428                         // Get all the return statements in the lambda block
    429                         List<ReturnStmt> returnStmts = blockStmt.findAll(ReturnStmt.class);
    430 
    431                         if (returnStmts.size() > 0) {
    432                             actualType = returnStmts.stream()
    433                                     .map(returnStmt -> returnStmt.getExpression().map(e -> facade.getType(e)).orElse(ResolvedVoidType.INSTANCE))
    434                                     .filter(x -> x != null && !x.isVoid() && !x.isNull())
    435                                     .findFirst()
    436                                     .orElse(ResolvedVoidType.INSTANCE);
    437 
    438                         } else {
    439                             return ResolvedVoidType.INSTANCE;
    440                         }
    441 
    442 
    443                     } else {
    444                         throw new UnsupportedOperationException();
    445                     }
    446 
    447                     ResolvedType formalType = functionalMethod.get().returnType();
    448 
    449                     // Infer the functional interfaces' return vs actual type
    450                     funcInterfaceCtx.addPair(formalType, actualType);
    451                     // Substitute to obtain a new type
    452                     ResolvedType functionalTypeWithReturn = funcInterfaceCtx.resolve(funcInterfaceCtx.addSingle(functionalInterfaceType));
    453 
    454                     // if the functional method returns void anyway
    455                     // we don't need to bother inferring types
    456                     if (!(formalType instanceof ResolvedVoidType)) {
    457                         lambdaCtx.addPair(result, functionalTypeWithReturn);
    458                         result = lambdaCtx.resolve(lambdaCtx.addSingle(result));
    459                     }
    460                 }
    461 
    462                 return result;
    463             } else {
    464                 return refMethod.getCorrespondingDeclaration().getParam(pos).getType();
    465             }
    466         } else {
    467             throw new UnsupportedOperationException("The type of a lambda expr depends on the position and its return value");
    468         }
    469     }
    470 
    471     @Override
    472     public ResolvedType visit(MethodReferenceExpr node, Boolean solveLambdas) {
    473         if (requireParentNode(node) instanceof MethodCallExpr) {
    474             MethodCallExpr callExpr = (MethodCallExpr) requireParentNode(node);
    475             int pos = JavaParserSymbolDeclaration.getParamPos(node);
    476             SymbolReference<ResolvedMethodDeclaration> refMethod = facade.solve(callExpr, false);
    477             if (!refMethod.isSolved()) {
    478                 throw new com.github.javaparser.resolution.UnsolvedSymbolException(requireParentNode(node).toString(), callExpr.getName().getId());
    479             }
    480             logger.finest("getType on method reference expr " + refMethod.getCorrespondingDeclaration().getName());
    481             //logger.finest("Method param " + refMethod.getCorrespondingDeclaration().getParam(pos));
    482             if (solveLambdas) {
    483                 MethodUsage usage = facade.solveMethodAsUsage(callExpr);
    484                 ResolvedType result = usage.getParamType(pos);
    485                 // We need to replace the type variables
    486                 Context ctx = JavaParserFactory.getContext(node, typeSolver);
    487                 result = solveGenericTypes(result, ctx, typeSolver);
    488 
    489                 //We should find out which is the functional method (e.g., apply) and replace the params of the
    490                 //solveLambdas with it, to derive so the values. We should also consider the value returned by the
    491                 //lambdas
    492                 if (FunctionalInterfaceLogic.getFunctionalMethod(result).isPresent()) {
    493                     MethodReferenceExpr methodReferenceExpr = node;
    494 
    495                     ResolvedType actualType = facade.toMethodUsage(methodReferenceExpr).returnType();
    496                     ResolvedType formalType = FunctionalInterfaceLogic.getFunctionalMethod(result).get().returnType();
    497 
    498                     InferenceContext inferenceContext = new InferenceContext(MyObjectProvider.INSTANCE);
    499                     inferenceContext.addPair(formalType, actualType);
    500                     result = inferenceContext.resolve(inferenceContext.addSingle(result));
    501                 }
    502 
    503                 return result;
    504             }
    505             return refMethod.getCorrespondingDeclaration().getParam(pos).getType();
    506         }
    507         throw new UnsupportedOperationException("The type of a method reference expr depends on the position and its return value");
    508     }
    509 
    510     @Override
    511     public ResolvedType visit(FieldDeclaration node, Boolean solveLambdas) {
    512         if (node.getVariables().size() == 1) {
    513             return node.getVariables().get(0).accept(this, solveLambdas);
    514         }
    515         throw new IllegalArgumentException("Cannot resolve the type of a field with multiple variable declarations. Pick one");
    516     }
    517 }
    518