| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.codehaus.groovy.transform.stc; |
| |
| import groovy.lang.Closure; |
| import groovy.lang.DelegatesTo; |
| import groovy.lang.IntRange; |
| import groovy.lang.Tuple2; |
| import groovy.transform.NamedParam; |
| import groovy.transform.NamedParams; |
| import groovy.transform.TypeChecked; |
| import groovy.transform.TypeCheckingMode; |
| import groovy.transform.stc.ClosureParams; |
| import groovy.transform.stc.ClosureSignatureConflictResolver; |
| import groovy.transform.stc.ClosureSignatureHint; |
| import org.apache.groovy.util.SystemUtil; |
| import org.codehaus.groovy.GroovyBugError; |
| import org.codehaus.groovy.ast.ASTNode; |
| import org.codehaus.groovy.ast.AnnotatedNode; |
| import org.codehaus.groovy.ast.AnnotationNode; |
| import org.codehaus.groovy.ast.ClassCodeVisitorSupport; |
| import org.codehaus.groovy.ast.ClassHelper; |
| import org.codehaus.groovy.ast.ClassNode; |
| import org.codehaus.groovy.ast.CodeVisitorSupport; |
| import org.codehaus.groovy.ast.ConstructorNode; |
| import org.codehaus.groovy.ast.DynamicVariable; |
| import org.codehaus.groovy.ast.FieldNode; |
| import org.codehaus.groovy.ast.GenericsType; |
| import org.codehaus.groovy.ast.GenericsType.GenericsTypeName; |
| import org.codehaus.groovy.ast.GroovyCodeVisitor; |
| import org.codehaus.groovy.ast.InnerClassNode; |
| import org.codehaus.groovy.ast.MethodNode; |
| import org.codehaus.groovy.ast.Parameter; |
| import org.codehaus.groovy.ast.PropertyNode; |
| import org.codehaus.groovy.ast.Variable; |
| import org.codehaus.groovy.ast.expr.AnnotationConstantExpression; |
| import org.codehaus.groovy.ast.expr.ArgumentListExpression; |
| import org.codehaus.groovy.ast.expr.ArrayExpression; |
| import org.codehaus.groovy.ast.expr.AttributeExpression; |
| import org.codehaus.groovy.ast.expr.BinaryExpression; |
| import org.codehaus.groovy.ast.expr.BitwiseNegationExpression; |
| import org.codehaus.groovy.ast.expr.CastExpression; |
| import org.codehaus.groovy.ast.expr.ClassExpression; |
| import org.codehaus.groovy.ast.expr.ClosureExpression; |
| import org.codehaus.groovy.ast.expr.ClosureListExpression; |
| import org.codehaus.groovy.ast.expr.ConstantExpression; |
| import org.codehaus.groovy.ast.expr.ConstructorCallExpression; |
| import org.codehaus.groovy.ast.expr.DeclarationExpression; |
| import org.codehaus.groovy.ast.expr.ElvisOperatorExpression; |
| import org.codehaus.groovy.ast.expr.EmptyExpression; |
| import org.codehaus.groovy.ast.expr.Expression; |
| import org.codehaus.groovy.ast.expr.FieldExpression; |
| import org.codehaus.groovy.ast.expr.LambdaExpression; |
| import org.codehaus.groovy.ast.expr.ListExpression; |
| import org.codehaus.groovy.ast.expr.MapEntryExpression; |
| import org.codehaus.groovy.ast.expr.MapExpression; |
| import org.codehaus.groovy.ast.expr.MethodCall; |
| import org.codehaus.groovy.ast.expr.MethodCallExpression; |
| import org.codehaus.groovy.ast.expr.MethodPointerExpression; |
| import org.codehaus.groovy.ast.expr.MethodReferenceExpression; |
| import org.codehaus.groovy.ast.expr.NotExpression; |
| import org.codehaus.groovy.ast.expr.PostfixExpression; |
| import org.codehaus.groovy.ast.expr.PrefixExpression; |
| import org.codehaus.groovy.ast.expr.PropertyExpression; |
| import org.codehaus.groovy.ast.expr.RangeExpression; |
| import org.codehaus.groovy.ast.expr.SpreadExpression; |
| import org.codehaus.groovy.ast.expr.SpreadMapExpression; |
| import org.codehaus.groovy.ast.expr.StaticMethodCallExpression; |
| import org.codehaus.groovy.ast.expr.TernaryExpression; |
| import org.codehaus.groovy.ast.expr.TupleExpression; |
| import org.codehaus.groovy.ast.expr.UnaryMinusExpression; |
| import org.codehaus.groovy.ast.expr.UnaryPlusExpression; |
| import org.codehaus.groovy.ast.expr.VariableExpression; |
| import org.codehaus.groovy.ast.stmt.BlockStatement; |
| import org.codehaus.groovy.ast.stmt.BreakStatement; |
| import org.codehaus.groovy.ast.stmt.CaseStatement; |
| import org.codehaus.groovy.ast.stmt.CatchStatement; |
| import org.codehaus.groovy.ast.stmt.ContinueStatement; |
| import org.codehaus.groovy.ast.stmt.EmptyStatement; |
| import org.codehaus.groovy.ast.stmt.ExpressionStatement; |
| import org.codehaus.groovy.ast.stmt.ForStatement; |
| import org.codehaus.groovy.ast.stmt.IfStatement; |
| import org.codehaus.groovy.ast.stmt.ReturnStatement; |
| import org.codehaus.groovy.ast.stmt.Statement; |
| import org.codehaus.groovy.ast.stmt.SwitchStatement; |
| import org.codehaus.groovy.ast.stmt.TryCatchStatement; |
| import org.codehaus.groovy.ast.stmt.WhileStatement; |
| import org.codehaus.groovy.ast.tools.GeneralUtils; |
| import org.codehaus.groovy.ast.tools.GenericsUtils; |
| import org.codehaus.groovy.ast.tools.WideningCategories; |
| import org.codehaus.groovy.classgen.ReturnAdder; |
| import org.codehaus.groovy.classgen.asm.InvocationWriter; |
| import org.codehaus.groovy.control.CompilationUnit; |
| import org.codehaus.groovy.control.ErrorCollector; |
| import org.codehaus.groovy.control.ResolveVisitor; |
| import org.codehaus.groovy.control.SourceUnit; |
| import org.codehaus.groovy.control.messages.WarningMessage; |
| import org.codehaus.groovy.runtime.DefaultGroovyMethods; |
| import org.codehaus.groovy.syntax.Token; |
| import org.codehaus.groovy.syntax.TokenUtil; |
| import org.codehaus.groovy.transform.StaticTypesTransformation; |
| import org.codehaus.groovy.transform.trait.Traits; |
| import org.objectweb.asm.Opcodes; |
| |
| import java.lang.reflect.Modifier; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.EnumMap; |
| import java.util.Enumeration; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.LinkedHashMap; |
| import java.util.LinkedHashSet; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Objects; |
| import java.util.Optional; |
| import java.util.Set; |
| import java.util.StringJoiner; |
| import java.util.concurrent.atomic.AtomicLong; |
| import java.util.function.BiPredicate; |
| import java.util.function.Consumer; |
| import java.util.function.Function; |
| import java.util.stream.Collectors; |
| import java.util.stream.IntStream; |
| |
| import static org.apache.groovy.util.BeanUtils.capitalize; |
| import static org.apache.groovy.util.BeanUtils.decapitalize; |
| import static org.codehaus.groovy.ast.ClassHelper.AUTOCLOSEABLE_TYPE; |
| import static org.codehaus.groovy.ast.ClassHelper.BigDecimal_TYPE; |
| import static org.codehaus.groovy.ast.ClassHelper.BigInteger_TYPE; |
| import static org.codehaus.groovy.ast.ClassHelper.Byte_TYPE; |
| import static org.codehaus.groovy.ast.ClassHelper.CLASS_Type; |
| import static org.codehaus.groovy.ast.ClassHelper.CLOSURE_TYPE; |
| import static org.codehaus.groovy.ast.ClassHelper.Character_TYPE; |
| import static org.codehaus.groovy.ast.ClassHelper.Double_TYPE; |
| import static org.codehaus.groovy.ast.ClassHelper.Float_TYPE; |
| import static org.codehaus.groovy.ast.ClassHelper.Integer_TYPE; |
| import static org.codehaus.groovy.ast.ClassHelper.Iterator_TYPE; |
| import static org.codehaus.groovy.ast.ClassHelper.LIST_TYPE; |
| import static org.codehaus.groovy.ast.ClassHelper.Long_TYPE; |
| import static org.codehaus.groovy.ast.ClassHelper.MAP_TYPE; |
| import static org.codehaus.groovy.ast.ClassHelper.Number_TYPE; |
| import static org.codehaus.groovy.ast.ClassHelper.OBJECT_TYPE; |
| import static org.codehaus.groovy.ast.ClassHelper.PATTERN_TYPE; |
| import static org.codehaus.groovy.ast.ClassHelper.RANGE_TYPE; |
| import static org.codehaus.groovy.ast.ClassHelper.SET_TYPE; |
| import static org.codehaus.groovy.ast.ClassHelper.STREAM_TYPE; |
| import static org.codehaus.groovy.ast.ClassHelper.STRING_TYPE; |
| import static org.codehaus.groovy.ast.ClassHelper.Short_TYPE; |
| import static org.codehaus.groovy.ast.ClassHelper.VOID_TYPE; |
| import static org.codehaus.groovy.ast.ClassHelper.boolean_TYPE; |
| import static org.codehaus.groovy.ast.ClassHelper.byte_TYPE; |
| import static org.codehaus.groovy.ast.ClassHelper.char_TYPE; |
| import static org.codehaus.groovy.ast.ClassHelper.double_TYPE; |
| import static org.codehaus.groovy.ast.ClassHelper.dynamicType; |
| import static org.codehaus.groovy.ast.ClassHelper.findSAM; |
| import static org.codehaus.groovy.ast.ClassHelper.float_TYPE; |
| import static org.codehaus.groovy.ast.ClassHelper.getNextSuperClass; |
| import static org.codehaus.groovy.ast.ClassHelper.getUnwrapper; |
| import static org.codehaus.groovy.ast.ClassHelper.getWrapper; |
| import static org.codehaus.groovy.ast.ClassHelper.int_TYPE; |
| import static org.codehaus.groovy.ast.ClassHelper.isBigDecimalType; |
| import static org.codehaus.groovy.ast.ClassHelper.isBigIntegerType; |
| import static org.codehaus.groovy.ast.ClassHelper.isClassType; |
| import static org.codehaus.groovy.ast.ClassHelper.isDynamicTyped; |
| import static org.codehaus.groovy.ast.ClassHelper.isFunctionalInterface; |
| import static org.codehaus.groovy.ast.ClassHelper.isGStringType; |
| import static org.codehaus.groovy.ast.ClassHelper.isNumberType; |
| import static org.codehaus.groovy.ast.ClassHelper.isObjectType; |
| import static org.codehaus.groovy.ast.ClassHelper.isPrimitiveBoolean; |
| import static org.codehaus.groovy.ast.ClassHelper.isPrimitiveByte; |
| import static org.codehaus.groovy.ast.ClassHelper.isPrimitiveChar; |
| import static org.codehaus.groovy.ast.ClassHelper.isPrimitiveDouble; |
| import static org.codehaus.groovy.ast.ClassHelper.isPrimitiveFloat; |
| import static org.codehaus.groovy.ast.ClassHelper.isPrimitiveInt; |
| import static org.codehaus.groovy.ast.ClassHelper.isPrimitiveLong; |
| import static org.codehaus.groovy.ast.ClassHelper.isPrimitiveShort; |
| import static org.codehaus.groovy.ast.ClassHelper.isPrimitiveType; |
| import static org.codehaus.groovy.ast.ClassHelper.isPrimitiveVoid; |
| import static org.codehaus.groovy.ast.ClassHelper.isSAMType; |
| import static org.codehaus.groovy.ast.ClassHelper.isStringType; |
| import static org.codehaus.groovy.ast.ClassHelper.isWrapperByte; |
| import static org.codehaus.groovy.ast.ClassHelper.isWrapperCharacter; |
| import static org.codehaus.groovy.ast.ClassHelper.isWrapperDouble; |
| import static org.codehaus.groovy.ast.ClassHelper.isWrapperFloat; |
| import static org.codehaus.groovy.ast.ClassHelper.isWrapperInteger; |
| import static org.codehaus.groovy.ast.ClassHelper.isWrapperLong; |
| import static org.codehaus.groovy.ast.ClassHelper.isWrapperShort; |
| import static org.codehaus.groovy.ast.ClassHelper.long_TYPE; |
| import static org.codehaus.groovy.ast.ClassHelper.short_TYPE; |
| import static org.codehaus.groovy.ast.tools.ClosureUtils.getParametersSafe; |
| import static org.codehaus.groovy.ast.tools.ClosureUtils.getResolveStrategyName; |
| import static org.codehaus.groovy.ast.tools.ClosureUtils.hasImplicitParameter; |
| import static org.codehaus.groovy.ast.tools.GeneralUtils.args; |
| import static org.codehaus.groovy.ast.tools.GeneralUtils.binX; |
| import static org.codehaus.groovy.ast.tools.GeneralUtils.callX; |
| import static org.codehaus.groovy.ast.tools.GeneralUtils.castX; |
| import static org.codehaus.groovy.ast.tools.GeneralUtils.classX; |
| import static org.codehaus.groovy.ast.tools.GeneralUtils.constX; |
| import static org.codehaus.groovy.ast.tools.GeneralUtils.ctorX; |
| import static org.codehaus.groovy.ast.tools.GeneralUtils.defaultValueX; |
| import static org.codehaus.groovy.ast.tools.GeneralUtils.elvisX; |
| import static org.codehaus.groovy.ast.tools.GeneralUtils.getGetterName; |
| import static org.codehaus.groovy.ast.tools.GeneralUtils.getSetterName; |
| import static org.codehaus.groovy.ast.tools.GeneralUtils.indexX; |
| import static org.codehaus.groovy.ast.tools.GeneralUtils.isOrImplements; |
| import static org.codehaus.groovy.ast.tools.GeneralUtils.nullX; |
| import static org.codehaus.groovy.ast.tools.GeneralUtils.propX; |
| import static org.codehaus.groovy.ast.tools.GeneralUtils.thisPropX; |
| import static org.codehaus.groovy.ast.tools.GeneralUtils.varX; |
| import static org.codehaus.groovy.ast.tools.GenericsUtils.makeClassSafe0; |
| import static org.codehaus.groovy.ast.tools.ParameterUtils.isVargs; |
| import static org.codehaus.groovy.ast.tools.WideningCategories.isBigDecCategory; |
| import static org.codehaus.groovy.ast.tools.WideningCategories.isBigIntCategory; |
| import static org.codehaus.groovy.ast.tools.WideningCategories.isDouble; |
| import static org.codehaus.groovy.ast.tools.WideningCategories.isDoubleCategory; |
| import static org.codehaus.groovy.ast.tools.WideningCategories.isFloat; |
| import static org.codehaus.groovy.ast.tools.WideningCategories.isFloatingCategory; |
| import static org.codehaus.groovy.ast.tools.WideningCategories.isIntCategory; |
| import static org.codehaus.groovy.ast.tools.WideningCategories.isLongCategory; |
| import static org.codehaus.groovy.ast.tools.WideningCategories.isNumberCategory; |
| import static org.codehaus.groovy.ast.tools.WideningCategories.lowestUpperBound; |
| import static org.codehaus.groovy.runtime.ArrayGroovyMethods.asBoolean; |
| import static org.codehaus.groovy.runtime.ArrayGroovyMethods.init; |
| import static org.codehaus.groovy.syntax.Types.ASSIGN; |
| import static org.codehaus.groovy.syntax.Types.COMPARE_EQUAL; |
| import static org.codehaus.groovy.syntax.Types.COMPARE_NOT_EQUAL; |
| import static org.codehaus.groovy.syntax.Types.COMPARE_NOT_IN; |
| import static org.codehaus.groovy.syntax.Types.COMPARE_NOT_INSTANCEOF; |
| import static org.codehaus.groovy.syntax.Types.COMPARE_TO; |
| import static org.codehaus.groovy.syntax.Types.DIVIDE; |
| import static org.codehaus.groovy.syntax.Types.DIVIDE_EQUAL; |
| import static org.codehaus.groovy.syntax.Types.ELVIS_EQUAL; |
| import static org.codehaus.groovy.syntax.Types.EQUAL; |
| import static org.codehaus.groovy.syntax.Types.FIND_REGEX; |
| import static org.codehaus.groovy.syntax.Types.INTDIV; |
| import static org.codehaus.groovy.syntax.Types.INTDIV_EQUAL; |
| import static org.codehaus.groovy.syntax.Types.KEYWORD_IN; |
| import static org.codehaus.groovy.syntax.Types.KEYWORD_INSTANCEOF; |
| import static org.codehaus.groovy.syntax.Types.MINUS_MINUS; |
| import static org.codehaus.groovy.syntax.Types.MOD; |
| import static org.codehaus.groovy.syntax.Types.MOD_EQUAL; |
| import static org.codehaus.groovy.syntax.Types.PLUS_PLUS; |
| import static org.codehaus.groovy.syntax.Types.REMAINDER; |
| import static org.codehaus.groovy.syntax.Types.REMAINDER_EQUAL; |
| import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.ArrayList_TYPE; |
| import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.Collection_TYPE; |
| import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.LinkedHashMap_TYPE; |
| import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.LinkedHashSet_TYPE; |
| import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.Matcher_TYPE; |
| import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.NUMBER_OPS; |
| import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.UNKNOWN_PARAMETER_TYPE; |
| import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.allParametersAndArgumentsMatchWithDefaultParams; |
| import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.applyGenericsConnections; |
| import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.applyGenericsContext; |
| import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.boundUnboundedWildcards; |
| import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.checkCompatibleAssignmentTypes; |
| import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.checkPossibleLossOfPrecision; |
| import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.chooseBestMethod; |
| import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.evaluateExpression; |
| import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.extractGenericsConnections; |
| import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.extractGenericsParameterMapOfThis; |
| import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.filterMethodsByVisibility; |
| import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.findDGMMethodsForClassNode; |
| import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.findSetters; |
| import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.findTargetVariable; |
| import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.fullyResolve; |
| import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.fullyResolveType; |
| import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.getCombinedBoundType; |
| import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.getCombinedGenericsType; |
| import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.getGenericsWithoutArray; |
| import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.getOperationName; |
| import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.implementsInterfaceOrIsSubclassOf; |
| import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.isArrayOp; |
| import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.isAssignableTo; |
| import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.isAssignment; |
| import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.isBeingCompiled; |
| import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.isBitOperator; |
| import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.isBoolIntrinsicOp; |
| import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.isClassClassNodeWrappingConcreteType; |
| import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.isCompareToBoolean; |
| import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.isGStringOrGStringStringLUB; |
| import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.isOperationInGroup; |
| import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.isParameterizedWithGStringOrGStringString; |
| import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.isParameterizedWithString; |
| import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.isPowerOperator; |
| import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.isShiftOperation; |
| import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.isTraitSelf; |
| import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.isUsingGenericsOrIsArrayUsingGenerics; |
| import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.isWildcardLeftHandSide; |
| import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.lastArgMatchesVarg; |
| import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.missesGenericsTypes; |
| import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.prettyPrintType; |
| import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.prettyPrintTypeName; |
| import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.resolveClassNodeGenerics; |
| import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.toMethodParametersString; |
| import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.typeCheckMethodArgumentWithGenerics; |
| import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.typeCheckMethodsWithGenerics; |
| import static org.codehaus.groovy.transform.stc.StaticTypesMarker.CLOSURE_ARGUMENTS; |
| import static org.codehaus.groovy.transform.stc.StaticTypesMarker.DECLARATION_INFERRED_TYPE; |
| import static org.codehaus.groovy.transform.stc.StaticTypesMarker.DELEGATION_METADATA; |
| import static org.codehaus.groovy.transform.stc.StaticTypesMarker.DIRECT_METHOD_CALL_TARGET; |
| import static org.codehaus.groovy.transform.stc.StaticTypesMarker.DYNAMIC_RESOLUTION; |
| import static org.codehaus.groovy.transform.stc.StaticTypesMarker.IMPLICIT_RECEIVER; |
| import static org.codehaus.groovy.transform.stc.StaticTypesMarker.INFERRED_RETURN_TYPE; |
| import static org.codehaus.groovy.transform.stc.StaticTypesMarker.INFERRED_TYPE; |
| import static org.codehaus.groovy.transform.stc.StaticTypesMarker.PARAMETER_TYPE; |
| import static org.codehaus.groovy.transform.stc.StaticTypesMarker.PV_FIELDS_ACCESS; |
| import static org.codehaus.groovy.transform.stc.StaticTypesMarker.PV_FIELDS_MUTATION; |
| import static org.codehaus.groovy.transform.stc.StaticTypesMarker.PV_METHODS_ACCESS; |
| import static org.codehaus.groovy.transform.stc.StaticTypesMarker.READONLY_PROPERTY; |
| import static org.codehaus.groovy.transform.stc.StaticTypesMarker.SUPER_MOP_METHOD_REQUIRED; |
| import static org.codehaus.groovy.transform.stc.StaticTypesMarker.TYPE; |
| |
| /** |
| * The main class code visitor responsible for static type checking. It will perform various inspections like checking |
| * assignment types, type inference, ... Eventually, class nodes may be annotated with inferred type information. |
| */ |
| public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport { |
| |
| private static final boolean DEBUG_GENERATED_CODE = SystemUtil.getBooleanSafe("groovy.stc.debug"); |
| private static final AtomicLong UNIQUE_LONG = new AtomicLong(); |
| |
| protected static final Object ERROR_COLLECTOR = ErrorCollector.class; |
| protected static final List<MethodNode> EMPTY_METHODNODE_LIST = Collections.emptyList(); |
| protected static final ClassNode TYPECHECKED_CLASSNODE = ClassHelper.make(TypeChecked.class); |
| protected static final ClassNode[] TYPECHECKING_ANNOTATIONS = new ClassNode[]{TYPECHECKED_CLASSNODE}; |
| protected static final ClassNode TYPECHECKING_INFO_NODE = ClassHelper.make(TypeChecked.TypeCheckingInfo.class); |
| protected static final ClassNode DGM_CLASSNODE = ClassHelper.make(DefaultGroovyMethods.class); |
| protected static final int CURRENT_SIGNATURE_PROTOCOL_VERSION = 1; |
| protected static final Expression CURRENT_SIGNATURE_PROTOCOL = new ConstantExpression(CURRENT_SIGNATURE_PROTOCOL_VERSION, true); |
| protected static final MethodNode GET_DELEGATE = CLOSURE_TYPE.getGetterMethod("getDelegate"); |
| protected static final MethodNode GET_OWNER = CLOSURE_TYPE.getGetterMethod("getOwner"); |
| protected static final MethodNode GET_THISOBJECT = CLOSURE_TYPE.getGetterMethod("getThisObject"); |
| protected static final ClassNode DELEGATES_TO = ClassHelper.make(DelegatesTo.class); |
| protected static final ClassNode DELEGATES_TO_TARGET = ClassHelper.make(DelegatesTo.Target.class); |
| protected static final ClassNode CLOSUREPARAMS_CLASSNODE = ClassHelper.make(ClosureParams.class); |
| protected static final ClassNode NAMED_PARAMS_CLASSNODE = ClassHelper.make(NamedParams.class); |
| protected static final ClassNode NAMED_PARAM_CLASSNODE = ClassHelper.make(NamedParam.class); |
| @Deprecated(forRemoval = true, since = "4.0.0") |
| protected static final ClassNode LINKEDHASHMAP_CLASSNODE = LinkedHashMap_TYPE; |
| protected static final ClassNode ENUMERATION_TYPE = ClassHelper.make(Enumeration.class); |
| protected static final ClassNode MAP_ENTRY_TYPE = ClassHelper.make(Map.Entry.class); |
| protected static final ClassNode ITERABLE_TYPE = ClassHelper.ITERABLE_TYPE; |
| |
| private static final List<ClassNode> TUPLE_TYPES = Arrays.stream(ClassHelper.TUPLE_CLASSES).map(ClassHelper::makeWithoutCaching).collect(Collectors.toList()); |
| |
| public static final MethodNode CLOSURE_CALL_NO_ARG = CLOSURE_TYPE.getDeclaredMethod("call", Parameter.EMPTY_ARRAY); |
| public static final MethodNode CLOSURE_CALL_ONE_ARG = CLOSURE_TYPE.getDeclaredMethod("call", new Parameter[]{new Parameter(OBJECT_TYPE, "arg")}); |
| public static final MethodNode CLOSURE_CALL_VARGS = CLOSURE_TYPE.getDeclaredMethod("call", new Parameter[]{new Parameter(OBJECT_TYPE.makeArray(), "args")}); |
| |
| public static final Statement GENERATED_EMPTY_STATEMENT = EmptyStatement.INSTANCE; |
| |
| protected final ReturnAdder.ReturnStatementListener returnListener = returnStatement -> { |
| if (returnStatement.isReturningNullOrVoid()) return; |
| ClassNode returnType = checkReturnType(returnStatement); |
| if (this.typeCheckingContext.getEnclosingClosure() != null) { |
| addClosureReturnType(returnType); |
| } else if (this.typeCheckingContext.getEnclosingMethod() == null) { |
| throw new GroovyBugError("Unexpected return statement at " + returnStatement.getLineNumber() + ":" + returnStatement.getColumnNumber() + " " + returnStatement.getText()); |
| } |
| }; |
| |
| protected final ReturnAdder returnAdder = new ReturnAdder(returnListener); |
| |
| protected FieldNode currentField; |
| protected PropertyNode currentProperty; |
| protected DefaultTypeCheckingExtension extension; |
| protected TypeCheckingContext typeCheckingContext; |
| |
| public StaticTypeCheckingVisitor(final SourceUnit source, final ClassNode classNode) { |
| this.typeCheckingContext = new TypeCheckingContext(this); |
| this.typeCheckingContext.pushEnclosingClassNode(classNode); |
| this.typeCheckingContext.pushTemporaryTypeInfo(); |
| this.typeCheckingContext.pushErrorCollector( |
| source.getErrorCollector()); |
| this.typeCheckingContext.source = source; |
| |
| this.extension = new DefaultTypeCheckingExtension(this); |
| this.extension.addHandler(new EnumTypeCheckingExtension(this)); |
| this.extension.addHandler(new TraitTypeCheckingExtension(this)); |
| } |
| |
| public void setCompilationUnit(final CompilationUnit compilationUnit) { |
| typeCheckingContext.setCompilationUnit(compilationUnit); |
| } |
| |
| @Override |
| protected SourceUnit getSourceUnit() { |
| return typeCheckingContext.getSource(); |
| } |
| |
| public void initialize() { |
| extension.setup(); |
| } |
| |
| /** |
| * Returns array of type checking annotations. Subclasses may override this |
| * method in order to provide additional types which must be looked up when |
| * checking if a method or a class node should be skipped. |
| * <p> |
| * The default implementation returns {@link TypeChecked}. |
| */ |
| protected ClassNode[] getTypeCheckingAnnotations() { |
| return TYPECHECKING_ANNOTATIONS; |
| } |
| |
| /** |
| * Returns the current type checking context. The context is used internally by the type |
| * checker during type checking to store various state data. |
| * |
| * @return the type checking context |
| */ |
| public TypeCheckingContext getTypeCheckingContext() { |
| return typeCheckingContext; |
| } |
| |
| public void addTypeCheckingExtension(final TypeCheckingExtension extension) { |
| this.extension.addHandler(extension); |
| } |
| |
| private List<TypeCheckingExtension> getTypeCheckingExtensions(final AnnotatedNode classOrMethod) { |
| List<Expression> ex = new ArrayList<>(); |
| for (ClassNode type : getTypeCheckingAnnotations()) { |
| for (AnnotationNode anno : classOrMethod.getAnnotations(type)) { |
| Expression extensions = anno.getMember("extensions"); |
| if (extensions instanceof ConstantExpression) { |
| ex.add(extensions); |
| } else if (extensions instanceof ListExpression) { |
| ex.addAll(((ListExpression) extensions).getExpressions()); |
| } |
| } |
| } |
| if (ex.isEmpty()) return Collections.emptyList(); |
| return ex.stream().filter(e -> e instanceof ConstantExpression).map(pathOrType -> |
| new GroovyTypeCheckingExtensionSupport(this, pathOrType.getText(), typeCheckingContext.getCompilationUnit()) |
| ).collect(Collectors.toList()); |
| } |
| |
| private <Node extends AnnotatedNode> void doWithTypeCheckingExtensions(final Node classOrMethod, final Consumer<Node> visitor) { |
| List<TypeCheckingExtension> extensions = getTypeCheckingExtensions(classOrMethod); |
| if (extensions.isEmpty()) { |
| visitor.accept(classOrMethod); |
| } else { // GROOVY-10770: extension composition |
| List<TypeCheckingExtension> added = new ArrayList<>(); |
| for (TypeCheckingExtension ext : extensions) { |
| if (extension.addHandler(ext)) { |
| added.add(ext); |
| ext.setup(); |
| } |
| } |
| try { |
| visitor.accept(classOrMethod); |
| } finally { |
| for (TypeCheckingExtension ext : added) { |
| extension.removeHandler(ext); |
| ext.finish(); |
| } |
| } |
| } |
| } |
| |
| //-------------------------------------------------------------------------- |
| |
| @Override |
| public void visitClass(final ClassNode node) { |
| if (shouldSkipClassNode(node)) return; |
| if (!extension.beforeVisitClass(node)) { |
| Object type = node.getNodeMetaData(INFERRED_TYPE); |
| if (type != null) { |
| // transformation has already been run on this class node |
| // so use a silent collector in order not to duplicate errors |
| typeCheckingContext.pushErrorCollector(); |
| } |
| typeCheckingContext.pushEnclosingClassNode(node); |
| Set<MethodNode> oldSet = typeCheckingContext.alreadyVisitedMethods; |
| typeCheckingContext.alreadyVisitedMethods = new LinkedHashSet<>(); |
| |
| doWithTypeCheckingExtensions(node, super::visitClass); |
| node.getInnerClasses().forEachRemaining(this::visitClass); |
| |
| typeCheckingContext.alreadyVisitedMethods = oldSet; |
| typeCheckingContext.popEnclosingClassNode(); |
| if (type != null) { |
| typeCheckingContext.popErrorCollector(); |
| } |
| |
| node.putNodeMetaData(INFERRED_TYPE, node); |
| node.putNodeMetaData(StaticTypeCheckingVisitor.class, Boolean.TRUE); |
| // mark all methods as visited. We can't do this in visitMethod because the type checker |
| // works in a two pass sequence and we don't want to skip the second pass |
| node.getMethods().forEach(n -> n.putNodeMetaData(StaticTypeCheckingVisitor.class, Boolean.TRUE)); |
| node.getDeclaredConstructors().forEach(n -> n.putNodeMetaData(StaticTypeCheckingVisitor.class, Boolean.TRUE)); |
| } |
| extension.afterVisitClass(node); |
| } |
| |
| protected boolean shouldSkipClassNode(final ClassNode node) { |
| return Boolean.TRUE.equals(node.getNodeMetaData(StaticTypeCheckingVisitor.class)) || isSkipMode(node); |
| } |
| |
| protected boolean shouldSkipMethodNode(final MethodNode node) { |
| return Boolean.TRUE.equals(node.getNodeMetaData(StaticTypeCheckingVisitor.class)) || isSkipMode(node); |
| } |
| |
| public boolean isSkipMode(final AnnotatedNode node) { |
| if (node == null) return false; |
| for (ClassNode tca : getTypeCheckingAnnotations()) { |
| List<AnnotationNode> annotations = node.getAnnotations(tca); |
| if (annotations != null) { |
| for (AnnotationNode annotation : annotations) { |
| Expression value = annotation.getMember("value"); |
| if (value != null) { |
| if (value instanceof ConstantExpression) { |
| ConstantExpression ce = (ConstantExpression) value; |
| if (TypeCheckingMode.SKIP.toString().equals(ce.getValue().toString())) return true; |
| } else if (value instanceof PropertyExpression) { |
| PropertyExpression pe = (PropertyExpression) value; |
| if (TypeCheckingMode.SKIP.toString().equals(pe.getPropertyAsString())) return true; |
| } |
| } |
| } |
| } |
| } |
| if (node instanceof MethodNode) { |
| return isSkipMode(node.getDeclaringClass()); |
| } |
| return isSkippedInnerClass(node); |
| } |
| |
| /** |
| * Tests if a node is an inner class node, and if it is, then checks if the enclosing method is skipped. |
| * |
| * @return true if the inner class node should be skipped |
| */ |
| protected boolean isSkippedInnerClass(final AnnotatedNode node) { |
| if (node instanceof ClassNode) { |
| ClassNode type = (ClassNode) node; |
| if (type.getOuterClass() != null) { |
| MethodNode enclosingMethod = type.getEnclosingMethod(); |
| if (enclosingMethod != null && isSkipMode(enclosingMethod)) { |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| @Override |
| public void visitClassExpression(final ClassExpression expression) { |
| super.visitClassExpression(expression); |
| ClassNode cn = expression.getNodeMetaData(INFERRED_TYPE); |
| if (cn == null) { |
| storeType(expression, getType(expression)); |
| } |
| } |
| |
| private static ClassNode getOutermost(ClassNode cn) { |
| while (cn.getOuterClass() != null) { |
| cn = cn.getOuterClass(); |
| } |
| return cn; |
| } |
| |
| private static void addPrivateFieldOrMethodAccess(final Expression source, final ClassNode cn, final StaticTypesMarker key, final ASTNode accessedMember) { |
| cn.getNodeMetaData(key, x -> new LinkedHashSet<>()).add(accessedMember); |
| source.putNodeMetaData(key, accessedMember); |
| } |
| |
| /** |
| * Checks for private field access from inner or outer class. |
| */ |
| private void checkOrMarkPrivateAccess(final Expression source, final FieldNode fn, final boolean lhsOfAssignment) { |
| if (fn != null && fn.isPrivate() && !fn.isSynthetic()) { |
| ClassNode declaringClass = fn.getDeclaringClass(); |
| ClassNode enclosingClass = typeCheckingContext.getEnclosingClassNode(); |
| if (declaringClass == enclosingClass && typeCheckingContext.getEnclosingClosure() == null) return; |
| if (declaringClass == enclosingClass || getOutermost(declaringClass) == getOutermost(enclosingClass)) { |
| StaticTypesMarker accessKind = lhsOfAssignment ? PV_FIELDS_MUTATION : PV_FIELDS_ACCESS; |
| addPrivateFieldOrMethodAccess(source, declaringClass, accessKind, fn); |
| } |
| } |
| } |
| |
| /** |
| * Checks for private method call from inner or outer class. |
| */ |
| private void checkOrMarkPrivateAccess(final Expression source, final MethodNode mn) { |
| ClassNode declaringClass = mn.getDeclaringClass(); |
| ClassNode enclosingClassNode = typeCheckingContext.getEnclosingClassNode(); |
| if (declaringClass != enclosingClassNode || typeCheckingContext.getEnclosingClosure() != null) { |
| int mods = mn.getModifiers(); |
| boolean sameModule = declaringClass.getModule() == enclosingClassNode.getModule(); |
| String packageName = declaringClass.getPackageName(); |
| if (packageName == null) { |
| packageName = ""; |
| } |
| if (Modifier.isPrivate(mods) && sameModule) { |
| addPrivateFieldOrMethodAccess(source, declaringClass, PV_METHODS_ACCESS, mn); |
| } else if (Modifier.isProtected(mods) && !packageName.equals(enclosingClassNode.getPackageName()) |
| && !implementsInterfaceOrIsSubclassOf(enclosingClassNode, declaringClass)) { |
| ClassNode cn = enclosingClassNode; |
| while ((cn = cn.getOuterClass()) != null) { |
| if (implementsInterfaceOrIsSubclassOf(cn, declaringClass)) { |
| addPrivateFieldOrMethodAccess(source, cn, PV_METHODS_ACCESS, mn); |
| break; |
| } |
| } |
| } |
| } |
| } |
| |
| @Override |
| public void visitVariableExpression(final VariableExpression vexp) { |
| super.visitVariableExpression(vexp); |
| if (storeTypeForSuper(vexp)) return; |
| if (storeTypeForThis(vexp)) return; |
| |
| final String name = vexp.getName(); |
| final Variable accessedVariable = vexp.getAccessedVariable(); |
| final TypeCheckingContext.EnclosingClosure enclosingClosure = typeCheckingContext.getEnclosingClosure(); |
| |
| if (accessedVariable instanceof DynamicVariable) { |
| // a dynamic variable is either a closure property, a class member referenced from a closure, or an undeclared variable |
| |
| if (enclosingClosure != null) { |
| switch (name) { |
| case "delegate": |
| DelegationMetadata dm = getDelegationMetadata(enclosingClosure.getClosureExpression()); |
| if (dm != null) { |
| storeType(vexp, dm.getType()); |
| return; |
| } |
| // falls through |
| case "owner": |
| if (typeCheckingContext.getEnclosingClosureStack().size() > 1) { |
| storeType(vexp, CLOSURE_TYPE); |
| return; |
| } |
| // falls through |
| case "thisObject": |
| storeType(vexp, typeCheckingContext.getEnclosingClassNode()); |
| return; |
| case "parameterTypes": |
| storeType(vexp, CLASS_Type.makeArray()); |
| return; |
| case "maximumNumberOfParameters": |
| case "resolveStrategy": |
| case "directive": |
| storeType(vexp, int_TYPE); |
| return; |
| } |
| } |
| |
| if (tryVariableExpressionAsProperty(vexp, name)) return; |
| |
| if (!extension.handleUnresolvedVariableExpression(vexp)) { |
| addStaticTypeError("The variable [" + name + "] is undeclared.", vexp); |
| } |
| } else if (accessedVariable instanceof FieldNode) { |
| if (tryVariableExpressionAsProperty(vexp, name)) { |
| ClassNode temporaryType = getInferredTypeFromTempInfo(vexp, null); // GROOVY-9454 |
| if (temporaryType == null) { |
| storeType(vexp, getType(vexp)); |
| } else if (!isObjectType(temporaryType)) { |
| vexp.putNodeMetaData(INFERRED_TYPE, temporaryType); |
| } |
| if (vexp.getAccessedVariable() != accessedVariable) { // GROOVY-11360: field hidden by dynamic property |
| if (vexp.getLineNumber() > 0 && !typeCheckingContext.reportedErrors.contains(((long) vexp.getLineNumber()) << 16 + vexp.getColumnNumber())) { |
| String text = "The field: " + accessedVariable.getName() + " of class: " + prettyPrintTypeName(((FieldNode) accessedVariable).getDeclaringClass()) + |
| " is hidden by a dynamic property. A qualifier is required to reference it."; |
| Token token = new Token(0, name, vexp.getLineNumber(), vexp.getColumnNumber()); // ASTNode to CSTNode |
| typeCheckingContext.getErrorCollector().addWarning(new WarningMessage(WarningMessage.POSSIBLE_ERRORS, text, token, getSourceUnit())); |
| } |
| } |
| } else if (!extension.handleUnresolvedVariableExpression(vexp)) { // GROOVY-11356 |
| addStaticTypeError("No such property: " + name + " for class: " + prettyPrintTypeName(typeCheckingContext.getEnclosingClassNode()), vexp); |
| } |
| } else if (accessedVariable instanceof PropertyNode) { |
| // we must be careful -- the property node may be of a wrong type: |
| // if a class contains a getter and a setter of different types or |
| // overloaded setters, the type of the property node is arbitrary! |
| if (tryVariableExpressionAsProperty(vexp, name)) { |
| BinaryExpression enclosingBinaryExpression = typeCheckingContext.getEnclosingBinaryExpression(); |
| if (enclosingBinaryExpression != null) { |
| Expression leftExpression = enclosingBinaryExpression.getLeftExpression(); |
| SetterInfo setterInfo = removeSetterInfo(leftExpression); |
| if (setterInfo != null) { |
| Expression rightExpression = enclosingBinaryExpression.getRightExpression(); |
| ensureValidSetter(vexp, leftExpression, rightExpression, setterInfo); |
| } |
| } |
| } else if (!extension.handleUnresolvedVariableExpression(vexp)) { // GROOVY-11356 |
| addStaticTypeError("No such property: " + name + " for class: " + prettyPrintTypeName(typeCheckingContext.getEnclosingClassNode()), vexp); |
| } |
| } else if (accessedVariable != null) { |
| VariableExpression localVariable; |
| if (accessedVariable instanceof Parameter) { |
| Parameter prm = (Parameter) accessedVariable; |
| localVariable = new ParameterVariableExpression(prm); |
| } else { |
| localVariable = (VariableExpression) accessedVariable; |
| } |
| |
| ClassNode inferredType = localVariable.getNodeMetaData(INFERRED_TYPE); |
| inferredType = getInferredTypeFromTempInfo(localVariable, inferredType); |
| if (inferredType != null && !isObjectType(inferredType) && !inferredType.equals(accessedVariable.getOriginType())) { |
| vexp.putNodeMetaData(INFERRED_TYPE, inferredType); |
| } |
| } |
| } |
| |
| private boolean storeTypeForSuper(final VariableExpression vexp) { |
| if (vexp == VariableExpression.SUPER_EXPRESSION) return true; |
| if (!vexp.isSuperExpression()) return false; |
| storeType(vexp, makeSuper()); |
| return true; |
| } |
| |
| private boolean storeTypeForThis(final VariableExpression vexp) { |
| if (vexp == VariableExpression.THIS_EXPRESSION) return true; |
| if (!vexp.isThisExpression()) return false; |
| // GROOVY-6904, GROOVY-9422: non-static inner class constructor call sets type |
| storeType(vexp, !isObjectType(vexp.getType()) ? vexp.getType() : makeThis()); |
| return true; |
| } |
| |
| private boolean tryVariableExpressionAsProperty(final VariableExpression vexp, final String dynName) { |
| PropertyExpression pexp = thisPropX(true, dynName); |
| if (existsProperty(pexp, !typeCheckingContext.isTargetOfEnclosingAssignment(vexp))) { |
| vexp.copyNodeMetaData(pexp.getObjectExpression()); |
| for (Object key : new Object[]{IMPLICIT_RECEIVER, READONLY_PROPERTY, PV_FIELDS_ACCESS, PV_FIELDS_MUTATION, DECLARATION_INFERRED_TYPE, DIRECT_METHOD_CALL_TARGET}) { |
| Object val = pexp.getNodeMetaData(key); |
| if (val != null) vexp.putNodeMetaData(key, val); |
| } |
| ClassNode type = pexp.getNodeMetaData(INFERRED_TYPE); |
| if (vexp.isClosureSharedVariable()) { |
| type = wrapTypeIfNecessary(type); |
| } |
| if (type == null) type = OBJECT_TYPE; |
| vexp.putNodeMetaData(INFERRED_TYPE, type); // GROOVY-11007 |
| String receiver = vexp.getNodeMetaData(IMPLICIT_RECEIVER); |
| Boolean dynamic = pexp.getNodeMetaData(DYNAMIC_RESOLUTION); |
| // GROOVY-7701, GROOVY-7996: correct false assumption made by VariableScopeVisitor |
| if (((receiver != null && !receiver.endsWith("owner")) || Boolean.TRUE.equals(dynamic)) |
| && !(vexp.getAccessedVariable() instanceof DynamicVariable)) { |
| vexp.setAccessedVariable(new DynamicVariable(dynName, false)); |
| } |
| return true; |
| } |
| return false; |
| } |
| |
| @Override |
| public void visitPropertyExpression(final PropertyExpression expression) { |
| if (existsProperty(expression, !typeCheckingContext.isTargetOfEnclosingAssignment(expression))) return; |
| |
| if (!extension.handleUnresolvedProperty(expression)) { |
| var objectExpression = expression.getObjectExpression(); |
| var objectExpressionType = (objectExpression instanceof ClassExpression |
| ? objectExpression.getType() : wrapTypeIfNecessary(getType(objectExpression))); |
| objectExpressionType = findCurrentInstanceOfClass(objectExpression, objectExpressionType); |
| addStaticTypeError("No such property: " + expression.getPropertyAsString() + " for class: " + prettyPrintTypeName(objectExpressionType), expression); |
| } |
| } |
| |
| @Override |
| public void visitAttributeExpression(final AttributeExpression expression) { |
| if (existsProperty(expression, true)) return; |
| |
| if (!extension.handleUnresolvedAttribute(expression)) { |
| var objectExpression = expression.getObjectExpression(); |
| var objectExpressionType = (objectExpression instanceof ClassExpression |
| ? objectExpression.getType() : wrapTypeIfNecessary(getType(objectExpression))); |
| objectExpressionType = findCurrentInstanceOfClass(objectExpression, objectExpressionType); |
| addStaticTypeError("No such attribute: " + expression.getPropertyAsString() + " for class: " + prettyPrintTypeName(objectExpressionType), expression); |
| } |
| } |
| |
| @Override |
| public void visitRangeExpression(final RangeExpression expression) { |
| super.visitRangeExpression(expression); |
| ClassNode fromType = getWrapper(getType(expression.getFrom())); |
| ClassNode toType = getWrapper(getType(expression.getTo())); |
| if (isWrapperInteger(fromType) && isWrapperInteger(toType)) { |
| storeType(expression, ClassHelper.make(IntRange.class)); |
| } else { |
| ClassNode rangeType = RANGE_TYPE.getPlainNodeReference(); |
| rangeType.setGenericsTypes(new GenericsType[]{new GenericsType(lowestUpperBound(fromType, toType))}); |
| storeType(expression, rangeType); |
| } |
| } |
| |
| @Override |
| public void visitNotExpression(final NotExpression expression) { |
| typeCheckingContext.pushTemporaryTypeInfo(); |
| super.visitNotExpression(expression); |
| // GROOVY-9455: !(x instanceof T) shouldn't propagate T as inferred type |
| typeCheckingContext.temporaryIfBranchTypeInformation.pop().forEach(this::putNotInstanceOfTypeInfo); |
| } |
| |
| @Override |
| public void visitBinaryExpression(final BinaryExpression expression) { |
| BinaryExpression enclosingBinaryExpression = typeCheckingContext.getEnclosingBinaryExpression(); |
| typeCheckingContext.pushEnclosingBinaryExpression(expression); |
| try { |
| int op = expression.getOperation().getType(); |
| Expression leftExpression = expression.getLeftExpression(); |
| Expression rightExpression = expression.getRightExpression(); |
| |
| leftExpression.visit(this); |
| SetterInfo setterInfo = removeSetterInfo(leftExpression); |
| ClassNode lType = null; |
| if (setterInfo != null) { |
| if (ensureValidSetter(expression, leftExpression, rightExpression, setterInfo)) { |
| return; |
| } |
| lType = getType(leftExpression); |
| } else { |
| if (op != EQUAL && op != ELVIS_EQUAL) { |
| lType = getType(leftExpression); |
| } else { |
| lType = getOriginalDeclarationType(leftExpression); |
| |
| applyTargetType(lType, rightExpression); |
| } |
| rightExpression.visit(this); |
| } |
| |
| ClassNode rType = isNullConstant(rightExpression) && !isPrimitiveType(lType) |
| ? UNKNOWN_PARAMETER_TYPE // null to primitive type is handled elsewhere |
| : getInferredTypeFromTempInfo(rightExpression, getType(rightExpression)); |
| ClassNode resultType; |
| |
| if (op == KEYWORD_IN || op == COMPARE_NOT_IN) { |
| // for the "in" or "!in" operator, the receiver and the arguments are reversed |
| BinaryExpression reverseExpression = binX(rightExpression, expression.getOperation(), leftExpression); |
| resultType = getResultType(rType, op, lType, reverseExpression); |
| if (resultType == null) resultType = boolean_TYPE; // GROOVY-10239 |
| storeTargetMethod(expression, reverseExpression.getNodeMetaData(DIRECT_METHOD_CALL_TARGET)); |
| } else { |
| resultType = getResultType(lType, op, rType, expression); |
| if (op == ELVIS_EQUAL) { |
| // TODO: Should this transform and visit be done before left and right are visited above? |
| Expression fullExpression = new ElvisOperatorExpression(leftExpression, rightExpression); |
| fullExpression.setSourcePosition(expression); |
| fullExpression.visit(this); |
| |
| resultType = getType(fullExpression); |
| } |
| } |
| if (resultType == null) { |
| resultType = lType; |
| } |
| |
| if (isArrayOp(op)) { |
| if (leftExpression instanceof VariableExpression) {//GROOVY-6782 |
| if (leftExpression.getNodeMetaData(INFERRED_TYPE) == null) { |
| leftExpression.removeNodeMetaData(INFERRED_RETURN_TYPE); |
| storeType(leftExpression, lType); |
| } |
| } |
| if (!lType.isArray() |
| && enclosingBinaryExpression != null |
| && enclosingBinaryExpression.getLeftExpression() == expression |
| && isAssignment(enclosingBinaryExpression.getOperation().getType())) { |
| // left hand side of a subscript assignment: map['foo'] = ... |
| Expression enclosingExpressionRHS = enclosingBinaryExpression.getRightExpression(); |
| if (!(enclosingExpressionRHS instanceof ClosureExpression)) { |
| enclosingExpressionRHS.visit(this); |
| } |
| ClassNode[] arguments = {rType, getType(enclosingExpressionRHS)}; |
| List<MethodNode> methods = findMethod(lType, "putAt", arguments); |
| if (methods.isEmpty()) { |
| addNoMatchingMethodError(lType, "putAt", arguments, enclosingBinaryExpression); |
| } else if (methods.size() == 1) { |
| typeCheckMethodsWithGenericsOrFail(lType, arguments, methods.get(0), enclosingExpressionRHS); |
| } |
| } |
| } |
| |
| boolean isEmptyDeclaration = (expression instanceof DeclarationExpression |
| && (rightExpression instanceof EmptyExpression || rType == UNKNOWN_PARAMETER_TYPE)); |
| if (!isEmptyDeclaration && isAssignment(op)) { |
| if (rightExpression instanceof ConstructorCallExpression) |
| inferDiamondType((ConstructorCallExpression) rightExpression, lType); |
| |
| // handle unchecked assignment: List<Type> list = [] |
| resultType = adjustForTargetType(resultType, lType); |
| |
| ClassNode originType = getOriginalDeclarationType(leftExpression); |
| typeCheckAssignment(expression, leftExpression, originType, rightExpression, resultType); |
| // check for implicit conversion like "String a = 123", "int[] b = [1,2,3]", "List c = [].stream()", etc. |
| if (!implementsInterfaceOrIsSubclassOf(wrapTypeIfNecessary(resultType), wrapTypeIfNecessary(originType))) { |
| resultType = originType; |
| } else if (isPrimitiveType(originType) && resultType.equals(getWrapper(originType))) { |
| resultType = originType; // retain primitive semantics |
| } else { |
| // GROOVY-7549: RHS type may not be accessible to enclosing class |
| int modifiers = resultType.getModifiers(); |
| ClassNode enclosingType = typeCheckingContext.getEnclosingClassNode(); |
| if (!Modifier.isPublic(modifiers) && !enclosingType.equals(resultType) |
| && !getOutermost(enclosingType).equals(getOutermost(resultType)) |
| && (Modifier.isPrivate(modifiers) || !Objects.equals(enclosingType.getPackageName(), resultType.getPackageName()))) { |
| resultType = originType; // TODO: Find accessible type in hierarchy of resultType? |
| } else if (GenericsUtils.hasUnresolvedGenerics(resultType)) { // GROOVY-9033, GROOVY-10089, et al. |
| Map<GenericsTypeName, GenericsType> enclosing = extractGenericsParameterMapOfThis(typeCheckingContext); |
| resultType = fullyResolveType(resultType, Optional.ofNullable(enclosing).orElseGet(Collections::emptyMap)); |
| } |
| } |
| |
| // track conditional assignment |
| if (leftExpression instanceof VariableExpression |
| && typeCheckingContext.ifElseForWhileAssignmentTracker != null) { |
| Variable accessedVariable = ((VariableExpression) leftExpression).getAccessedVariable(); |
| if (accessedVariable instanceof Parameter) { |
| accessedVariable = new ParameterVariableExpression((Parameter) accessedVariable); |
| } |
| if (accessedVariable instanceof VariableExpression) { |
| recordAssignment((VariableExpression) accessedVariable, resultType); |
| } |
| } |
| |
| storeType(leftExpression, resultType); |
| |
| // propagate closure parameter type information |
| if (leftExpression instanceof VariableExpression) { |
| if (rightExpression instanceof ClosureExpression) { |
| leftExpression.putNodeMetaData(CLOSURE_ARGUMENTS, ((ClosureExpression) rightExpression).getParameters()); |
| } else if (rightExpression instanceof VariableExpression |
| && ((VariableExpression) rightExpression).getAccessedVariable() instanceof Expression |
| && ((Expression) ((VariableExpression) rightExpression).getAccessedVariable()).getNodeMetaData(CLOSURE_ARGUMENTS) != null) { |
| Variable targetVariable = findTargetVariable((VariableExpression) leftExpression); |
| if (targetVariable instanceof ASTNode) { |
| ((ASTNode) targetVariable).putNodeMetaData(CLOSURE_ARGUMENTS, ((Expression) ((VariableExpression) rightExpression).getAccessedVariable()).getNodeMetaData(CLOSURE_ARGUMENTS)); |
| } |
| } |
| } |
| } else if (op == KEYWORD_INSTANCEOF) { |
| pushInstanceOfTypeInfo(leftExpression, rightExpression); |
| } else if (op == COMPARE_NOT_INSTANCEOF) { // GROOVY-6429, GROOVY-8321, GROOVY-8412, GROOVY-8523, GROOVY-9931 |
| putNotInstanceOfTypeInfo(extractTemporaryTypeInfoKey(leftExpression), Collections.singleton(rightExpression.getType())); |
| } |
| if (!isEmptyDeclaration) { |
| storeType(expression, resultType); |
| } |
| |
| validateResourceInARM(expression, resultType); |
| |
| // GROOVY-5874: if left expression is a closure shared variable, a second pass should be done |
| if (leftExpression instanceof VariableExpression && ((VariableExpression) leftExpression).isClosureSharedVariable()) { |
| typeCheckingContext.secondPassExpressions.add(new SecondPassExpression<>(expression)); |
| } |
| } finally { |
| typeCheckingContext.popEnclosingBinaryExpression(); |
| } |
| } |
| |
| private void validateResourceInARM(final BinaryExpression expression, final ClassNode lType) { |
| if (expression instanceof DeclarationExpression |
| && TryCatchStatement.isResource(expression) |
| && !isOrImplements(lType, AUTOCLOSEABLE_TYPE)) { |
| addError("Resource[" + lType.getName() + "] in ARM should be of type AutoCloseable", expression); |
| } |
| } |
| |
| private void applyTargetType(final ClassNode target, final Expression source) { |
| if (isClosureWithType(target)) { |
| if (source instanceof ClosureExpression) { |
| GenericsType returnType = target.getGenericsTypes()[0]; |
| storeInferredReturnType(source, getCombinedBoundType(returnType)); |
| } |
| } else if (isFunctionalInterface(target)) { |
| if (source instanceof ClosureExpression) { |
| inferParameterAndReturnTypesOfClosureOnRHS(target, (ClosureExpression) source); |
| } else if (source instanceof MapExpression) { // GROOVY-7141 |
| List<MapEntryExpression> spec = ((MapExpression) source).getMapEntryExpressions(); |
| if (spec.size() == 1 && spec.get(0).getValueExpression() instanceof ClosureExpression |
| && findSAM(target).getName().equals(spec.get(0).getKeyExpression().getText())) { |
| inferParameterAndReturnTypesOfClosureOnRHS(target, (ClosureExpression) spec.get(0).getValueExpression()); |
| } |
| } else if (source instanceof MethodReferenceExpression) { |
| LambdaExpression lambda = constructLambdaExpressionForMethodReference(target, (MethodReferenceExpression) source); |
| |
| inferParameterAndReturnTypesOfClosureOnRHS(target, lambda); |
| source.putNodeMetaData(PARAMETER_TYPE, lambda.getNodeMetaData(PARAMETER_TYPE)); |
| source.putNodeMetaData(CLOSURE_ARGUMENTS, extractTypesFromParameters(lambda.getParameters())); |
| } |
| } |
| } |
| |
| private void inferParameterAndReturnTypesOfClosureOnRHS(final ClassNode lhsType, final ClosureExpression rhsExpression) { |
| ClassNode[] samParameterTypes; |
| { |
| Tuple2<ClassNode[], ClassNode> typeInfo = GenericsUtils.parameterizeSAM(lhsType); |
| storeInferredReturnType(rhsExpression, typeInfo.getV2()); |
| samParameterTypes = typeInfo.getV1(); |
| } |
| |
| Parameter[] closureParameters = getParametersSafe(rhsExpression); |
| if (samParameterTypes.length == 1 && hasImplicitParameter(rhsExpression)) { |
| Variable it = rhsExpression.getVariableScope().getDeclaredVariable("it"); // GROOVY-7141 |
| closureParameters = new Parameter[]{it instanceof Parameter ? (Parameter) it : new Parameter(dynamicType(),"it")}; |
| } |
| |
| int n = closureParameters.length; |
| ClassNode[] expectedTypes = samParameterTypes; |
| out: if ((samParameterTypes.length == 1 && isOrImplements(samParameterTypes[0], LIST_TYPE)) // GROOVY-11092 |
| && (n != 1 || !typeCheckMethodArgumentWithGenerics(GenericsUtils.nonGeneric(closureParameters[0].getOriginType()), samParameterTypes[0], false))) { |
| int itemCount = TUPLE_TYPES.indexOf(samParameterTypes[0]); |
| if (itemCount >= 0) {//Tuple[0-16] |
| if (itemCount != n) break out; |
| GenericsType[] spec = samParameterTypes[0].getGenericsTypes(); |
| if (spec != null) { // edge case: raw type or Tuple0 falls through |
| expectedTypes = Arrays.stream(spec).map(GenericsType::getType).toArray(ClassNode[]::new); |
| break out; |
| } |
| } |
| expectedTypes = new ClassNode[n]; |
| Arrays.fill(expectedTypes, inferLoopElementType(samParameterTypes[0])); |
| } |
| |
| if (n == expectedTypes.length) { |
| for (int i = 0; i < n; i += 1) { |
| Parameter parameter = closureParameters[i]; |
| if (parameter.isDynamicTyped()) { |
| parameter.setType(expectedTypes[i]); // GROOVY-11083, GROOVY-11085, et al. |
| } else { |
| checkParamType(parameter, expectedTypes[i], i == n-1, rhsExpression instanceof LambdaExpression); |
| } |
| } |
| rhsExpression.putNodeMetaData(CLOSURE_ARGUMENTS, expectedTypes); |
| } else { |
| addStaticTypeError("Wrong number of parameters for method target: " + toMethodParametersString(findSAM(lhsType).getName(), samParameterTypes), rhsExpression); |
| } |
| |
| rhsExpression.putNodeMetaData(PARAMETER_TYPE, expectedTypes == samParameterTypes ? lhsType : null); |
| } |
| |
| private void checkParamType(final Parameter source, final ClassNode target, final boolean isLast, final boolean lambda) { |
| if (/*lambda ? !source.getOriginType().isDerivedFrom(target) :*/!typeCheckMethodArgumentWithGenerics(source.getOriginType(), target, isLast)) |
| addStaticTypeError("Expected type " + prettyPrintType(target) + " for " + (lambda ? "lambda" : "closure") + " parameter: " + source.getName(), source); |
| } |
| |
| /** |
| * Given a binary expression corresponding to an assignment, will check that |
| * the type of the RHS matches one of the possible setters and if not, throw |
| * a type checking error. |
| * |
| * @param expression the assignment expression |
| * @param leftExpression left expression of the assignment |
| * @param rightExpression right expression of the assignment |
| * @param setterInfo possible setters |
| * @return {@code false} if valid setter found or {@code true} if type checking error created |
| */ |
| private boolean ensureValidSetter(final Expression expression, final Expression leftExpression, final Expression rightExpression, final SetterInfo setterInfo) { |
| // for expressions like foo = { ... } |
| // we know that the RHS type is a closure |
| // but we must check if the binary expression is an assignment |
| // because we need to check if a setter uses @DelegatesTo |
| VariableExpression receiver = varX("%", setterInfo.receiverType); |
| receiver.setType(setterInfo.receiverType); // same as origin type |
| |
| Function<Expression, MethodNode> setterCall = (value) -> { |
| typeCheckingContext.pushEnclosingBinaryExpression(null); // GROOVY-10628: LHS re-purposed |
| try { |
| MethodCallExpression call = callX(receiver, setterInfo.name, value); |
| call.setImplicitThis(false); |
| visitMethodCallExpression(call); |
| return call.getNodeMetaData(DIRECT_METHOD_CALL_TARGET); |
| } finally { |
| typeCheckingContext.popEnclosingBinaryExpression(); |
| } |
| }; |
| |
| Function<MethodNode, ClassNode> setterType = (setter) -> { |
| ClassNode type = setter.getParameters()[0].getOriginType(); |
| if (!setter.isStatic() && !(setter instanceof ExtensionMethodNode) && GenericsUtils.hasUnresolvedGenerics(type)) { |
| type = applyGenericsContext(extractPlaceHolders(setterInfo.receiverType, setter.getDeclaringClass()), type); |
| } |
| return type; |
| }; |
| |
| Expression valueExpression = rightExpression; |
| // for "x op= y", find type as if it was "x = x op y" |
| if (isCompoundAssignment(expression)) { |
| Token op = ((BinaryExpression) expression).getOperation(); |
| if (op.getType() == ELVIS_EQUAL) { // GROOVY-10419: "x ?= y" |
| valueExpression = elvisX(leftExpression, rightExpression); |
| } else { |
| op = Token.newSymbol(TokenUtil.removeAssignment(op.getType()), op.getStartLine(), op.getStartColumn()); |
| valueExpression = binX(leftExpression, op, rightExpression); |
| } |
| } |
| |
| MethodNode methodTarget = setterCall.apply(valueExpression); |
| if (methodTarget == null && !isCompoundAssignment(expression)) { |
| // if no direct match, try implicit conversion |
| for (MethodNode setter : setterInfo.setters) { |
| ClassNode lType = setterType.apply(setter); |
| ClassNode rType = getDeclaredOrInferredType(valueExpression); |
| if (lType.isArray() && valueExpression instanceof ListExpression) { |
| rType = inferLoopElementType(rType).makeArray(); // GROOVY-7506 |
| } |
| if (checkCompatibleAssignmentTypes(lType, rType, valueExpression, false)) { |
| methodTarget = setterCall.apply(castX(lType, valueExpression)); |
| if (methodTarget != null) { |
| break; |
| } |
| } |
| } |
| } |
| |
| if (methodTarget != null) { |
| for (MethodNode setter : setterInfo.setters) { |
| if (setter == methodTarget) { |
| leftExpression.putNodeMetaData(DIRECT_METHOD_CALL_TARGET, methodTarget); |
| leftExpression.removeNodeMetaData(INFERRED_TYPE); // clear assumption |
| storeType(leftExpression, setterType.apply(methodTarget)); |
| break; |
| } |
| } |
| return false; |
| } else { |
| ClassNode firstSetterType = setterType.apply(setterInfo.setters.get(0)); |
| addAssignmentError(firstSetterType, getType(valueExpression), expression); |
| return true; |
| } |
| } |
| |
| private static boolean isClosureWithType(final ClassNode type) { |
| return CLOSURE_TYPE.equals(type) && Optional.ofNullable(type.getGenericsTypes()).filter(gts -> gts != null && gts.length == 1).isPresent(); |
| } |
| |
| private static boolean isCompoundAssignment(final Expression exp) { |
| if (exp instanceof BinaryExpression) { |
| Token op = ((BinaryExpression) exp).getOperation(); |
| return isAssignment(op.getType()) && op.getType() != EQUAL; |
| } |
| return false; |
| } |
| |
| protected ClassNode getOriginalDeclarationType(final Expression lhs) { |
| Variable var = null; |
| if (lhs instanceof FieldExpression) { |
| var = ((FieldExpression) lhs).getField(); |
| } else if (lhs instanceof VariableExpression) { |
| var = findTargetVariable((VariableExpression) lhs); |
| } |
| return var != null && !(var instanceof DynamicVariable) ? var.getOriginType() : getType(lhs); |
| } |
| |
| protected void inferDiamondType(final ConstructorCallExpression cce, final ClassNode lType) { |
| ClassNode cceType = cce.getType(), inferredType = lType; |
| // check if constructor call expression makes use of the diamond operator |
| if (cceType.getGenericsTypes() != null && cceType.getGenericsTypes().length == 0) { |
| ArgumentListExpression argumentList = InvocationWriter.makeArgumentList(cce.getArguments()); |
| ConstructorNode constructor = cce.getNodeMetaData(DIRECT_METHOD_CALL_TARGET); |
| if (!argumentList.getExpressions().isEmpty() && constructor != null) { |
| ClassNode type = GenericsUtils.parameterizeType(cceType, cceType); |
| type = inferReturnTypeGenerics(type, constructor, argumentList); |
| if (lType.getGenericsTypes() != null // GROOVY-10367: nothing to inspect |
| && (type.toString(false).indexOf('#') > 0 // GROOVY-9983, GROOVY-10291, GROOVY-10368: unresolved generic |
| // GROOVY-6232, GROOVY-9956: if cce not assignment compatible, process target as additional type witness |
| || checkCompatibleAssignmentTypes(lType, type, cce) && !GenericsUtils.buildWildcardType(lType).isCompatibleWith(type))) { |
| // allow covariance of each type parameter, but maintain semantics for nested generics |
| |
| ClassNode pType = GenericsUtils.parameterizeType(lType, type); |
| GenericsType[] lhs = pType.getGenericsTypes(), rhs = type.getGenericsTypes(); |
| if (lhs == null || rhs == null || lhs.length != rhs.length) throw new GroovyBugError( |
| "Parameterization failed: " + prettyPrintType(pType) + " ~ " + prettyPrintType(type)); |
| |
| if (IntStream.range(0, lhs.length).allMatch(i -> |
| GenericsUtils.buildWildcardType(getCombinedBoundType(lhs[i])).isCompatibleWith(rhs[i].getType()))) { |
| type = pType; // lType proved to be a viable type witness |
| } |
| } |
| inferredType = type; |
| } |
| // GROOVY-10344, GROOVY-10847: "T t = new C<>()" where "T" may extend another type parameter |
| while (inferredType.isGenericsPlaceHolder() && asBoolean(inferredType.getGenericsTypes())) { |
| inferredType = getCombinedBoundType(inferredType.getGenericsTypes()[0]); |
| } |
| adjustGenerics(inferredType, cceType); |
| storeType(cce, cceType); |
| } |
| } |
| |
| private void adjustGenerics(final ClassNode source, final ClassNode target) { |
| GenericsType[] genericsTypes = source.getGenericsTypes(); |
| if (genericsTypes == null) { // Map foo = new HashMap<>() |
| genericsTypes = target.redirect().getGenericsTypes().clone(); |
| for (int i = 0, n = genericsTypes.length; i < n; i += 1) { |
| GenericsType gt = genericsTypes[i]; |
| // GROOVY-10055: handle diamond or raw |
| ClassNode cn = gt.getUpperBounds() != null |
| ? gt.getUpperBounds()[0] : gt.getType().redirect(); |
| genericsTypes[i] = cn.getPlainNodeReference().asGenericsType(); |
| } |
| } else { |
| // GROOVY-11192: mapping between source and target type parameter(s) |
| if (!source.equals(target)) { |
| assert source.isInterface() ? target.implementsInterface(source) : target.isDerivedFrom(source); |
| ClassNode mapped = adjustForTargetType(target, source); |
| genericsTypes = mapped.getGenericsTypes(); |
| } |
| genericsTypes = genericsTypes.clone(); |
| for (int i = 0, n = genericsTypes.length; i < n; i += 1) { |
| GenericsType gt = genericsTypes[i]; |
| genericsTypes[i] = new GenericsType(gt.getType(), |
| gt.getUpperBounds(), gt.getLowerBound()); |
| genericsTypes[i].setWildcard(gt.isWildcard()); // GROOVY-10310 |
| } |
| } |
| target.setGenericsTypes(genericsTypes); |
| } |
| |
| private boolean typeCheckMultipleAssignmentAndContinue(final Expression leftExpression, Expression rightExpression) { |
| if (rightExpression instanceof VariableExpression || rightExpression instanceof PropertyExpression || rightExpression instanceof MethodCall) { |
| ClassNode inferredType = Optional.ofNullable(getType(rightExpression)).orElseGet(rightExpression::getType); |
| GenericsType[] genericsTypes = inferredType.getGenericsTypes(); |
| ListExpression listExpression = new ListExpression(); |
| listExpression.setSourcePosition(rightExpression); |
| |
| // convert Tuple[1-16] bearing expressions to mock list for checking |
| for (int n = TUPLE_TYPES.indexOf(inferredType), i = 0; i < n; i += 1) { |
| ClassNode type = (genericsTypes != null ? genericsTypes[i].getType() : OBJECT_TYPE); |
| listExpression.addExpression(varX("v" + (i + 1), type)); |
| } |
| if (!listExpression.getExpressions().isEmpty()) { |
| rightExpression = listExpression; |
| } |
| } else if (rightExpression instanceof RangeExpression) { |
| ListExpression listExpression = new ListExpression(); |
| listExpression.setSourcePosition(rightExpression); |
| ClassNode type = getType(((RangeExpression) rightExpression).getFrom()); |
| TupleExpression tuple = (TupleExpression) leftExpression; |
| for (int i = 0; i < tuple.getExpressions().size(); i++) { |
| Expression expression = indexX(rightExpression, constX(i, true)); |
| expression.putNodeMetaData(StaticTypesMarker.INFERRED_TYPE, type); |
| listExpression.addExpression(expression); |
| } |
| if (!listExpression.getExpressions().isEmpty()) { |
| rightExpression = listExpression; |
| } |
| } |
| |
| if (!(rightExpression instanceof ListExpression)) { |
| addStaticTypeError("Multiple assignments without list or tuple on the right-hand side are unsupported in static type checking mode", rightExpression); |
| return false; |
| } |
| |
| TupleExpression tuple = (TupleExpression) leftExpression; |
| ListExpression values = (ListExpression) rightExpression; |
| List<Expression> tupleExpressions = tuple.getExpressions(); |
| List<Expression> valueExpressions = values.getExpressions(); |
| |
| if (tupleExpressions.size() > valueExpressions.size()) { |
| addStaticTypeError("Incorrect number of values. Expected:" + tupleExpressions.size() + " Was:" + valueExpressions.size(), values); |
| return false; |
| } |
| |
| for (int i = 0, n = tupleExpressions.size(); i < n; i += 1) { |
| ClassNode valueType = getType(valueExpressions.get(i)); |
| ClassNode targetType = getType(tupleExpressions.get(i)); |
| if (!isAssignableTo(valueType, targetType)) { |
| addStaticTypeError("Cannot assign value of type " + prettyPrintType(valueType) + " to variable of type " + prettyPrintType(targetType), rightExpression); |
| return false; |
| } |
| storeType(tupleExpressions.get(i), valueType); |
| } |
| |
| return true; |
| } |
| |
| private ClassNode adjustTypeForSpreading(final ClassNode rightExpressionType, final Expression leftExpression) { |
| // given "list*.foo = 100" or "map*.value = 100", then the assignment must be checked against [100], not 100 |
| if (leftExpression instanceof PropertyExpression && ((PropertyExpression) leftExpression).isSpreadSafe()) { |
| return extension.buildListType(rightExpressionType); |
| } |
| return rightExpressionType; |
| } |
| |
| private boolean addedReadOnlyPropertyError(final Expression expr) { |
| // if expr is of READONLY_PROPERTY_RETURN type, then it means we are on a missing property |
| if (expr.getNodeMetaData(READONLY_PROPERTY) == null) return false; |
| String name; |
| if (expr instanceof VariableExpression) { |
| name = ((VariableExpression) expr).getName(); |
| } else { |
| name = ((PropertyExpression) expr).getPropertyAsString(); |
| } |
| addStaticTypeError("Cannot set read-only property: " + name, expr); |
| return true; |
| } |
| |
| private void addPrecisionErrors(final ClassNode leftRedirect, final ClassNode lhsType, final ClassNode rhsType, final Expression rightExpression) { |
| if (isNumberType(leftRedirect)) { |
| if (isNumberType(rhsType) && checkPossibleLossOfPrecision(leftRedirect, rhsType, rightExpression)) { |
| addStaticTypeError("Possible loss of precision from " + prettyPrintType(rhsType) + " to " + prettyPrintType(lhsType), rightExpression); |
| } |
| return; |
| } |
| if (!leftRedirect.isArray()) return; |
| // left type is an array, check the right component types |
| if (rightExpression instanceof ListExpression) { |
| ClassNode leftComponentType = leftRedirect.getComponentType(); |
| for (Expression expression : ((ListExpression) rightExpression).getExpressions()) { |
| ClassNode rightComponentType = getType(expression); |
| if (!checkCompatibleAssignmentTypes(leftComponentType, rightComponentType) && !(isNullConstant(expression) && !isPrimitiveType(leftComponentType))) { |
| addStaticTypeError("Cannot assign value of type " + prettyPrintType(rightComponentType) + " into array of type " + prettyPrintType(lhsType), rightExpression); |
| } |
| } |
| } else if (rhsType.redirect().isArray()) { |
| ClassNode leftComponentType = leftRedirect.getComponentType(); |
| ClassNode rightComponentType = rhsType.redirect().getComponentType(); |
| if (!checkCompatibleAssignmentTypes(leftComponentType, rightComponentType)) { |
| addStaticTypeError("Cannot assign value of type " + prettyPrintType(rightComponentType) + " into array of type " + prettyPrintType(lhsType), rightExpression); |
| } |
| } |
| } |
| |
| private void addListAssignmentConstructorErrors(final ClassNode leftRedirect, final ClassNode leftExpressionType, final ClassNode inferredRightExpressionType, final Expression rightExpression, final Expression assignmentExpression) { |
| if (isWildcardLeftHandSide(leftRedirect) && !isClassType(leftRedirect)) return; // GROOVY-6802, GROOVY-6803 |
| // if left type is not a list but right type is a list, then we're in the case of a groovy |
| // constructor type : Dimension d = [100,200] |
| // In that case, more checks can be performed |
| if (!implementsInterfaceOrIsSubclassOf(LIST_TYPE, leftRedirect) |
| && (!leftRedirect.isAbstract() || leftRedirect.isArray()) |
| && !ArrayList_TYPE.isDerivedFrom(leftRedirect) && !LinkedHashSet_TYPE.isDerivedFrom(leftRedirect)) { |
| ClassNode[] types = getArgumentTypes(args(((ListExpression) rightExpression).getExpressions())); |
| MethodNode methodNode = checkGroovyStyleConstructor(leftRedirect, types, assignmentExpression); |
| if (methodNode != null) { |
| rightExpression.putNodeMetaData(DIRECT_METHOD_CALL_TARGET, methodNode); |
| } |
| } else if (implementsInterfaceOrIsSubclassOf(inferredRightExpressionType, LIST_TYPE) |
| && !implementsInterfaceOrIsSubclassOf(inferredRightExpressionType, leftRedirect)) { |
| if (!extension.handleIncompatibleAssignment(leftExpressionType, inferredRightExpressionType, assignmentExpression)) { |
| addAssignmentError(leftExpressionType, inferredRightExpressionType, assignmentExpression); |
| } |
| } |
| } |
| |
| private void addMapAssignmentConstructorErrors(final ClassNode leftRedirect, final Expression leftExpression, final MapExpression rightExpression) { |
| if (!isConstructorAbbreviation(leftRedirect, rightExpression) |
| // GROOVY-6802, GROOVY-6803: Object, String or [Bb]oolean target |
| || (isWildcardLeftHandSide(leftRedirect) && !isClassType(leftRedirect))) { |
| return; |
| } |
| |
| // groovy constructor shorthand: A a = [x:2, y:3] |
| ClassNode[] argTypes = {getType(rightExpression)}; |
| checkGroovyStyleConstructor(leftRedirect, argTypes, rightExpression); |
| // perform additional type checking on arguments |
| checkGroovyConstructorMap(leftExpression, leftRedirect, rightExpression); |
| } |
| |
| private void checkTypeGenerics(final ClassNode leftExpressionType, final ClassNode rightExpressionType, final Expression rightExpression) { |
| if (leftExpressionType.isUsingGenerics() |
| && !missesGenericsTypes(rightExpressionType) |
| && !(rightExpression instanceof ClosureExpression) // GROOVY-10277 |
| && !isNullConstant(rightExpression) && !UNKNOWN_PARAMETER_TYPE.equals(rightExpressionType) |
| && !GenericsUtils.buildWildcardType(leftExpressionType).isCompatibleWith(wrapTypeIfNecessary(rightExpressionType))) |
| addStaticTypeError("Incompatible generic argument types. Cannot assign " + prettyPrintType(rightExpressionType) + " to: " + prettyPrintType(leftExpressionType), rightExpression); |
| } |
| |
| private boolean hasGStringStringError(final ClassNode leftExpressionType, final ClassNode wrappedRHS, final Expression rightExpression) { |
| if (isParameterizedWithString(leftExpressionType) && isParameterizedWithGStringOrGStringString(wrappedRHS)) { |
| addStaticTypeError("You are trying to use a GString in place of a String in a type which explicitly declares accepting String. " + |
| "Make sure to call toString() on all GString values.", rightExpression); |
| return true; |
| } |
| return false; |
| } |
| |
| private static boolean isConstructorAbbreviation(final ClassNode leftType, final Expression rightExpression) { |
| if (rightExpression instanceof ListExpression) { |
| return !(ArrayList_TYPE.isDerivedFrom(leftType) || ArrayList_TYPE.implementsInterface(leftType) |
| || LinkedHashSet_TYPE.isDerivedFrom(leftType) || LinkedHashSet_TYPE.implementsInterface(leftType)); |
| } |
| if (rightExpression instanceof MapExpression) { |
| return !(LinkedHashMap_TYPE.isDerivedFrom(leftType) || LinkedHashMap_TYPE.implementsInterface(leftType)); |
| } |
| return false; |
| } |
| |
| protected void typeCheckAssignment(final BinaryExpression assignmentExpression, final Expression leftExpression, final ClassNode leftExpressionType, final Expression rightExpression, final ClassNode rightExpressionType) { |
| if (leftExpression instanceof TupleExpression) { |
| if (!typeCheckMultipleAssignmentAndContinue(leftExpression, rightExpression)) return; |
| } |
| |
| // TODO: need errors for write-only too! |
| if (addedReadOnlyPropertyError(leftExpression)) return; |
| |
| ClassNode rType = adjustTypeForSpreading(rightExpressionType, leftExpression); |
| |
| if (!checkCompatibleAssignmentTypes(leftExpressionType, rType, rightExpression)) { |
| if (!extension.handleIncompatibleAssignment(leftExpressionType, rType, assignmentExpression)) { |
| addAssignmentError(leftExpressionType, rightExpressionType, rightExpression); |
| } |
| } else { |
| ClassNode lTypeRedirect = leftExpressionType.redirect(); |
| addPrecisionErrors(lTypeRedirect, leftExpressionType, rType, rightExpression); |
| if (rightExpression instanceof ListExpression) { |
| addListAssignmentConstructorErrors(lTypeRedirect, leftExpressionType, rightExpressionType, rightExpression, assignmentExpression); |
| } else if (rightExpression instanceof MapExpression) { |
| addMapAssignmentConstructorErrors(lTypeRedirect, leftExpression, (MapExpression)rightExpression); |
| } |
| if (!hasGStringStringError(leftExpressionType, rType, rightExpression) && !isConstructorAbbreviation(leftExpressionType, rightExpression)) { |
| checkTypeGenerics(leftExpressionType, rType, rightExpression); |
| } |
| } |
| } |
| |
| protected void checkGroovyConstructorMap(final Expression receiver, final ClassNode receiverType, final MapExpression mapExpression) { |
| for (MapEntryExpression entryExpression : mapExpression.getMapEntryExpressions()) { |
| Expression keyExpression = entryExpression.getKeyExpression(); |
| if (!(keyExpression instanceof ConstantExpression)) { |
| addStaticTypeError("Dynamic keys in map-style constructors are unsupported in static type checking", keyExpression); |
| } else { |
| String propName = keyExpression.getText(); |
| Set<ClassNode> propertyTypes = new HashSet<>(); |
| Expression valueExpression = entryExpression.getValueExpression(); |
| typeCheckingContext.pushEnclosingBinaryExpression(assignX(keyExpression, valueExpression, entryExpression)); |
| if (!existsProperty(propX(varX("_", receiverType), propName), false, new PropertyLookup(receiverType, propertyTypes::add))) { |
| typeCheckingContext.popEnclosingBinaryExpression(); |
| addStaticTypeError("No such property: " + propName + " for class: " + prettyPrintTypeName(receiverType), receiver); |
| } else { |
| ClassNode valueType = getType(valueExpression); |
| BinaryExpression kv = typeCheckingContext.popEnclosingBinaryExpression(); |
| if (propertyTypes.stream().noneMatch(targetType -> checkCompatibleAssignmentTypes(targetType, getResultType(targetType, ASSIGN, valueType, kv), valueExpression))) { |
| ClassNode targetType = propertyTypes.size() == 1 ? propertyTypes.iterator().next() : new UnionTypeClassNode(propertyTypes.toArray(ClassNode.EMPTY_ARRAY)); |
| if (!extension.handleIncompatibleAssignment(targetType, valueType, entryExpression)) { |
| addAssignmentError(targetType, valueType, entryExpression); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| @Deprecated(forRemoval = true, since = "4.0.0") |
| protected static boolean hasRHSIncompleteGenericTypeInfo(final ClassNode inferredRightExpressionType) { |
| boolean replaceType = false; |
| GenericsType[] genericsTypes = inferredRightExpressionType.getGenericsTypes(); |
| if (genericsTypes != null) { |
| for (GenericsType genericsType : genericsTypes) { |
| if (genericsType.isPlaceholder()) { |
| replaceType = true; |
| break; |
| } |
| } |
| } |
| return replaceType; |
| } |
| |
| /** |
| * Checks that a constructor style expression is valid regarding the number |
| * of arguments and the argument types. |
| * |
| * @param node the class node for which we will try to find a matching constructor |
| * @param arguments the constructor arguments |
| */ |
| protected MethodNode checkGroovyStyleConstructor(final ClassNode node, final ClassNode[] arguments, final ASTNode origin) { |
| if (isObjectType(node) || isDynamicTyped(node)) { |
| // in that case, we are facing a list constructor assigned to a def or object |
| return null; |
| } |
| List<? extends MethodNode> constructors = node.getDeclaredConstructors(); |
| if (constructors.isEmpty() && arguments.length == 0) { |
| return null; |
| } |
| constructors = findMethod(node, "<init>", arguments); |
| if (constructors.isEmpty()) { |
| if (isBeingCompiled(node) && !node.isAbstract() && arguments.length == 1 && arguments[0].equals(LinkedHashMap_TYPE)) { |
| // there will be a default hash map constructor added later |
| return new ConstructorNode(Opcodes.ACC_PUBLIC, new Parameter[]{new Parameter(LinkedHashMap_TYPE, "args")}, ClassNode.EMPTY_ARRAY, EmptyStatement.INSTANCE); |
| } else { |
| addNoMatchingMethodError(node, "<init>", arguments, origin); |
| return null; |
| } |
| } else if (constructors.size() > 1) { |
| addStaticTypeError("Ambiguous constructor call " + prettyPrintTypeName(node) + toMethodParametersString("", arguments), origin); |
| return null; |
| } |
| return constructors.get(0); |
| } |
| |
| protected boolean existsProperty(final PropertyExpression pexp, final boolean checkForReadOnly) { |
| return existsProperty(pexp, checkForReadOnly, null); |
| } |
| |
| /** |
| * Checks whether a property exists on the receiver, or on any of the possible receiver classes (found in the |
| * temporary type information table) |
| * |
| * @param pexp a property expression |
| * @param readMode if true, look for property read, else for property set |
| * @param visitor if not null, when the property node is found, visit it with the provided visitor |
| * @return true if the property is defined in any of the possible receiver classes |
| */ |
| protected boolean existsProperty(final PropertyExpression pexp, final boolean readMode, final ClassCodeVisitorSupport visitor) { |
| super.visitPropertyExpression(pexp); |
| |
| String propertyName = pexp.getPropertyAsString(); |
| if (propertyName == null) return false; |
| |
| Expression objectExpression = pexp.getObjectExpression(); |
| ClassNode objectExpressionType = getType(objectExpression); |
| if (objectExpression instanceof ConstructorCallExpression) { |
| ClassNode rawType = objectExpressionType.getPlainNodeReference(); |
| inferDiamondType((ConstructorCallExpression) objectExpression, rawType); |
| } |
| // enclosing excludes classes that skip STC |
| Set<ClassNode> enclosingTypes = new LinkedHashSet<>(); |
| enclosingTypes.add(typeCheckingContext.getEnclosingClassNode()); |
| enclosingTypes.addAll(enclosingTypes.iterator().next().getOuterClasses()); |
| |
| boolean staticOnlyAccess = isClassClassNodeWrappingConcreteType(objectExpressionType); |
| if (staticOnlyAccess) { |
| if ("this".equals(propertyName)) { |
| // handle "Outer.this" for any level of nesting |
| ClassNode outer = objectExpressionType.getGenericsTypes()[0].getType(); |
| |
| ClassNode found = null; |
| for (ClassNode enclosingType : enclosingTypes) { |
| if (!enclosingType.isStaticClass() && outer.equals(enclosingType.getOuterClass())) { |
| found = enclosingType; |
| break; |
| } |
| } |
| if (found != null) { |
| storeType(pexp, outer); |
| return true; |
| } |
| } else if ("super".equals(propertyName)) { |
| // GROOVY-8299: handle "Iface.super" for interface default methods |
| ClassNode enclosingType = typeCheckingContext.getEnclosingClassNode(); |
| ClassNode accessor = objectExpressionType.getGenericsTypes()[0].getType(); |
| if (accessor.isInterface() && enclosingType.implementsInterface(accessor)) { |
| storeType(pexp, accessor); |
| return true; |
| } |
| return false; |
| } |
| } |
| |
| boolean foundGetterOrSetter = false; |
| String capName = capitalize(propertyName); |
| Set<ClassNode> handledNodes = new HashSet<>(); |
| List<Receiver<String>> receivers = new ArrayList<>(); |
| addReceivers(receivers, makeOwnerList(objectExpression), pexp.isImplicitThis()); |
| |
| for (Receiver<String> receiver : receivers) { |
| ClassNode receiverType = receiver.getType(); |
| |
| if (receiverType.isArray() && "length".equals(propertyName)) { |
| pexp.putNodeMetaData(READONLY_PROPERTY, Boolean.TRUE); |
| storeType(pexp, int_TYPE); |
| if (visitor != null) { |
| FieldNode length = new FieldNode("length", Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL, int_TYPE, receiverType, null); |
| length.setDeclaringClass(receiverType); |
| visitor.visitField(length); |
| } |
| return true; |
| } |
| |
| LinkedList<ClassNode> queue = new LinkedList<>(); |
| queue.add(receiverType); |
| if (isPrimitiveType(receiverType)) { |
| queue.add(getWrapper(receiverType)); |
| } |
| while (!queue.isEmpty()) { |
| ClassNode current = queue.remove(); |
| if (!handledNodes.add(current)) continue; |
| |
| FieldNode field = current.getDeclaredField(propertyName); |
| if (field == null) { |
| if (current.getSuperClass() != null) |
| queue.addFirst(current.getSuperClass()); |
| Collections.addAll(queue, current.getInterfaces()); |
| } |
| |
| boolean staticOnly = (receiver.getData() == null ? staticOnlyAccess : false); |
| // in case of a lookup on java.lang.Class, look for instance methods on Class |
| // as well; in case of static property access Class<Type> and Type are listed |
| if (isClassClassNodeWrappingConcreteType(current)) staticOnly = false; |
| |
| field = allowStaticAccessToMember(field, staticOnly); |
| |
| // skip property/accessor checks for "x.@field" |
| if (pexp instanceof AttributeExpression) { |
| if (field != null && storeField(field, pexp, receiverType, visitor, receiver.getData(), !readMode)) { |
| return true; |
| } |
| continue; |
| } |
| |
| // skip property/accessor checks for "field", "this.field", "this.with { field }", etc. in declaring class of field |
| if (field != null && enclosingTypes.contains(current)) { |
| if (storeField(field, pexp, receiverType, visitor, receiver.getData(), !readMode)) { |
| return true; |
| } |
| } |
| |
| MethodNode getter = current.getGetterMethod("is" + capName); |
| getter = allowStaticAccessToMember(getter, staticOnly); |
| if (getter == null) getter = current.getGetterMethod(getGetterName(propertyName)); |
| getter = allowStaticAccessToMember(getter, staticOnly); |
| List<MethodNode> setters = findSetters(current, getSetterName(propertyName), /*voidOnly:*/false); |
| setters = allowStaticAccessToMember(setters, staticOnly); |
| |
| if (readMode && getter != null && visitor != null) visitor.visitMethod(getter); |
| |
| PropertyNode property = current.getProperty(propertyName); |
| property = allowStaticAccessToMember(property, staticOnly); |
| // prefer explicit getter or setter over property if receiver is not 'this' |
| if (property == null || !enclosingTypes.contains(receiverType)) { |
| ClassNode enclosingType = enclosingTypes.iterator().next(); |
| if (readMode) { |
| if (getter != null && hasAccessToMember(enclosingType, getter.getDeclaringClass(), getter.getModifiers())) { |
| ClassNode returnType = inferReturnTypeGenerics(receiverType, getter, ArgumentListExpression.EMPTY_ARGUMENTS); |
| storeInferredTypeForPropertyExpression(pexp, returnType); |
| storeTargetMethod(pexp, getter); |
| String delegationData = receiver.getData(); |
| if (delegationData != null) { |
| pexp.putNodeMetaData(IMPLICIT_RECEIVER, delegationData); |
| } |
| return true; |
| } else { |
| getter = null; // GROOVY-11319 |
| } |
| } else { |
| if (setters.stream().anyMatch(setter -> hasAccessToMember(enclosingType, setter.getDeclaringClass(), setter.getModifiers()))) { |
| if (visitor != null) { |
| for (MethodNode setter : setters) { |
| // visiting setter will not infer the property type since return type is void, so visit a dummy field instead |
| FieldNode virtual = new FieldNode(propertyName, 0, setter.getParameters()[0].getOriginType(), current, null); |
| virtual.setDeclaringClass(setter.getDeclaringClass()); |
| visitor.visitField(virtual); |
| } |
| } |
| SetterInfo info = new SetterInfo(current, getSetterName(propertyName), setters); |
| BinaryExpression enclosingBinaryExpression = typeCheckingContext.getEnclosingBinaryExpression(); |
| if (enclosingBinaryExpression != null) { |
| putSetterInfo(enclosingBinaryExpression.getLeftExpression(), info); |
| } |
| String delegationData = receiver.getData(); |
| if (delegationData != null) { |
| pexp.putNodeMetaData(IMPLICIT_RECEIVER, delegationData); |
| } |
| pexp.removeNodeMetaData(READONLY_PROPERTY); |
| return true; |
| } else if (field == null && getter != null && hasAccessToMember(enclosingType, getter.getDeclaringClass(), getter.getModifiers())) { |
| pexp.putNodeMetaData(READONLY_PROPERTY, Boolean.TRUE); // GROOVY-9127 |
| } else { |
| setters.clear(); // GROOVY-11319 |
| } |
| } |
| } |
| |
| if (property != null && storeProperty(property, pexp, receiverType, visitor, receiver.getData(), !readMode)) return true; |
| |
| if (field != null && storeField(field, pexp, receiverType, visitor, receiver.getData(), !readMode)) return true; |
| |
| foundGetterOrSetter = (foundGetterOrSetter || getter != null || !setters.isEmpty()); |
| } |
| |
| // GROOVY-5568: the property may be defined by DGM |
| for (ClassNode dgmReceiver : isPrimitiveType(receiverType) ? new ClassNode[]{receiverType, getWrapper(receiverType)} : new ClassNode[]{receiverType}) { |
| Set<MethodNode> methods = findDGMMethodsForClassNode(getSourceUnit().getClassLoader(), dgmReceiver, "get" + capName); |
| for (MethodNode method : findDGMMethodsForClassNode(getSourceUnit().getClassLoader(), dgmReceiver, "is" + capName)) { |
| if (isPrimitiveBoolean(method.getReturnType())) methods.add(method); |
| } |
| if (staticOnlyAccess && receiver.getData() == null && !isClassType(receiver.getType())) { |
| // GROOVY-10820: ensure static extension when property accessed in static manner |
| methods.removeIf(method -> !((ExtensionMethodNode) method).isStaticExtension()); |
| } |
| if (isUsingGenericsOrIsArrayUsingGenerics(dgmReceiver)) { |
| methods.removeIf(method -> // GROOVY-10075: "List<Integer>" vs "List<String>" |
| !typeCheckMethodsWithGenerics(dgmReceiver, ClassNode.EMPTY_ARRAY, method) |
| ); |
| } |
| if (!methods.isEmpty()) { |
| List<MethodNode> bestMethods = chooseBestMethod(dgmReceiver, methods, ClassNode.EMPTY_ARRAY); |
| if (bestMethods.size() == 1) { |
| MethodNode getter = bestMethods.get(0); |
| if (visitor != null) { |
| visitor.visitMethod(getter); |
| } |
| ClassNode returnType = inferReturnTypeGenerics(dgmReceiver, getter, ArgumentListExpression.EMPTY_ARGUMENTS); |
| storeInferredTypeForPropertyExpression(pexp, returnType); |
| if (readMode) storeTargetMethod(pexp, getter); |
| return true; |
| } |
| } |
| } |
| |
| // GROOVY-7996: check if receiver declares get(String), getProperty(String), propertyMissing(String) or $static_propertyMissing(String) |
| if (pexp.isImplicitThis() && !receiverType.isArray() && !receiverType.isScriptBody() && !isPrimitiveType(getUnwrapper(receiverType))) { |
| MethodNode mopMethod; |
| if (readMode) { |
| Parameter[] name = {new Parameter(STRING_TYPE, "name")}; |
| mopMethod = receiverType.getMethod("get", name); |
| if (mopMethod == null) mopMethod = receiverType.getMethod("getProperty", name); |
| if (mopMethod == null || mopMethod.isStatic() || mopMethod.isSynthetic()) mopMethod = receiverType.getMethod("propertyMissing", name); |
| } else { |
| Parameter[] nameAndValue = {new Parameter(STRING_TYPE, "name"), new Parameter(OBJECT_TYPE, "value")}; |
| mopMethod = receiverType.getMethod("set", nameAndValue); |
| if (mopMethod == null) mopMethod = receiverType.getMethod("setProperty", nameAndValue); |
| if (mopMethod == null || mopMethod.isStatic() || mopMethod.isSynthetic()) mopMethod = receiverType.getMethod("propertyMissing", nameAndValue); |
| } |
| if (mopMethod != null && !mopMethod.isStatic() && !mopMethod.isSynthetic()) { |
| pexp.putNodeMetaData(DYNAMIC_RESOLUTION, Boolean.TRUE); |
| pexp.removeNodeMetaData(DECLARATION_INFERRED_TYPE); |
| pexp.removeNodeMetaData(INFERRED_TYPE); |
| if (visitor != null) |
| visitor.visitMethod(mopMethod); |
| return true; |
| } |
| } |
| } |
| |
| for (Receiver<String> receiver : receivers) { |
| ClassNode receiverType = receiver.getType(); |
| ClassNode propertyType = getTypeForMapPropertyExpression(receiverType, pexp); |
| if (propertyType == null) |
| propertyType = getTypeForListPropertyExpression(receiverType, pexp); |
| if (propertyType == null) |
| propertyType = getTypeForSpreadExpression(receiverType, pexp); |
| if (propertyType == null) |
| continue; |
| if (visitor != null) { |
| // TODO: type inference on maps and lists, if possible |
| PropertyNode node = new PropertyNode(propertyName, Opcodes.ACC_PUBLIC, propertyType, receiver.getType(), null, null, null); |
| node.setDeclaringClass(receiver.getType()); |
| visitor.visitProperty(node); |
| } |
| storeType(pexp, propertyType); |
| String delegationData = receiver.getData(); |
| if (delegationData != null) pexp.putNodeMetaData(IMPLICIT_RECEIVER, delegationData); |
| return true; |
| } |
| |
| if (pexp.isImplicitThis() && isThisExpression(objectExpression)) { |
| Iterator<ClassNode> iter = enclosingTypes.iterator(); // first enclosing is "this" type |
| boolean staticOnly = Modifier.isStatic(iter.next().getModifiers()) || staticOnlyAccess; |
| while (iter.hasNext()) { |
| ClassNode outer = iter.next(); |
| // GROOVY-7994, GROOVY-11198: try "this.propertyName" as "Outer.propertyName" or "Outer.this.propertyName" |
| PropertyExpression pe = propX(staticOnly ? classX(outer) : propX(classX(outer), "this"), pexp.getProperty()); |
| if (existsProperty(pe, readMode, visitor)) { |
| pexp.copyNodeMetaData(pe); |
| return true; |
| } |
| staticOnly = staticOnly || Modifier.isStatic(outer.getModifiers()); |
| } |
| } |
| |
| return foundGetterOrSetter; |
| } |
| |
| private static boolean hasAccessToMember(final ClassNode accessor, final ClassNode receiver, final int modifiers) { |
| if (Modifier.isPublic(modifiers) |
| || accessor.equals(receiver) |
| || accessor.getOuterClasses().contains(receiver)) { |
| return true; |
| } |
| if (!Modifier.isPrivate(modifiers) && Objects.equals(accessor.getPackageName(), receiver.getPackageName())) { |
| return true; |
| } |
| return Modifier.isProtected(modifiers) && accessor.isDerivedFrom(receiver); |
| } |
| |
| private ClassNode getTypeForMultiValueExpression(final ClassNode compositeType, final Expression prop) { |
| GenericsType[] gts = compositeType.getGenericsTypes(); |
| ClassNode itemType = (gts != null && gts.length == 1 ? getCombinedBoundType(gts[0]) : OBJECT_TYPE); |
| |
| List<ClassNode> propertyTypes = new ArrayList<>(); |
| if (existsProperty(propX(varX("_", itemType), prop), true, new PropertyLookup(itemType, propertyTypes::add))) { |
| return extension.buildListType(propertyTypes.get(0)); |
| } |
| return null; |
| } |
| |
| private ClassNode getTypeForSpreadExpression(final ClassNode testClass, final PropertyExpression pexp) { |
| if (pexp.isSpreadSafe()) { |
| MethodCallExpression mce = callX(varX("_", testClass), "iterator"); |
| mce.setImplicitThis(false); |
| mce.visit(this); |
| ClassNode iteratorType = getType(mce); |
| if (isOrImplements(iteratorType, Iterator_TYPE)) { |
| return getTypeForMultiValueExpression(iteratorType, pexp.getProperty()); |
| } |
| } |
| return null; |
| } |
| |
| private ClassNode getTypeForListPropertyExpression(final ClassNode testClass, final PropertyExpression pexp) { |
| if (isOrImplements(testClass, LIST_TYPE)) { |
| ClassNode listType = testClass.equals(LIST_TYPE) ? testClass |
| : GenericsUtils.parameterizeType(testClass, LIST_TYPE); |
| return getTypeForMultiValueExpression(listType, pexp.getProperty()); |
| } |
| return null; |
| } |
| |
| private ClassNode getTypeForMapPropertyExpression(final ClassNode testClass, final PropertyExpression pexp) { |
| if (isOrImplements(testClass, MAP_TYPE)) { |
| ClassNode mapType = testClass.equals(MAP_TYPE) ? testClass |
| : GenericsUtils.parameterizeType(testClass, MAP_TYPE); |
| GenericsType[] gts = mapType.getGenericsTypes();//<K,V> params |
| if (gts == null || gts.length != 2) gts = new GenericsType[] { |
| OBJECT_TYPE.asGenericsType(), OBJECT_TYPE.asGenericsType() |
| }; |
| |
| if (!pexp.isSpreadSafe()) { |
| return getCombinedBoundType(gts[1]); |
| } else { |
| // map*.property syntax acts on Entry |
| switch (pexp.getPropertyAsString()) { |
| case "key": |
| pexp.putNodeMetaData(READONLY_PROPERTY,Boolean.TRUE); // GROOVY-10326 |
| return makeClassSafe0(LIST_TYPE, gts[0]); |
| case "value": |
| GenericsType v = gts[1]; |
| if (!v.isWildcard() |
| && !Modifier.isFinal(v.getType().getModifiers()) |
| && typeCheckingContext.isTargetOfEnclosingAssignment(pexp)) { |
| v = GenericsUtils.buildWildcardType(v.getType()); // GROOVY-10325 |
| } |
| return makeClassSafe0(LIST_TYPE, v); |
| default: |
| addStaticTypeError("Spread operator on map only allows one of [key,value]", pexp); |
| } |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Filters search result to prevent access to instance members from a static |
| * context. |
| * <p> |
| * Return null if the given member is not static, but we want to access in a |
| * static way (staticOnly=true). If we want to access in a non-static way we |
| * always return the member, since then access to static members and |
| * non-static members is allowed. |
| * |
| * @return {@code member} or null |
| */ |
| private <T> T allowStaticAccessToMember(final T member, final boolean staticOnly) { |
| if (member == null || !staticOnly) return member; |
| |
| if (member instanceof List) { |
| @SuppressWarnings("unchecked") |
| T list = (T) ((List<MethodNode>) member).stream() |
| .map(m -> allowStaticAccessToMember(m, true)) |
| .filter(Objects::nonNull).collect(Collectors.toList()); |
| return list; |
| } |
| |
| boolean isStatic; |
| if (member instanceof FieldNode) { |
| isStatic = ((FieldNode) member).isStatic(); |
| } else if (member instanceof PropertyNode) { |
| isStatic = ((PropertyNode) member).isStatic(); |
| } else { // assume member instanceof MethodNode |
| isStatic = isStaticInContext((MethodNode) member); |
| } |
| return (isStatic ? member : null); |
| } |
| |
| /** |
| * Is the method called in a static or non-static manner? |
| */ |
| private boolean isStaticInContext(final MethodNode method) { |
| return method instanceof ExtensionMethodNode ? ((ExtensionMethodNode) method).isStaticExtension() : method.isStatic(); |
| } |
| |
| private boolean storeField(final FieldNode field, final PropertyExpression expressionToStoreOn, final ClassNode receiver, final ClassCodeVisitorSupport visitor, final String delegationData, final boolean lhsOfAssignment) { |
| if (visitor != null) visitor.visitField(field); |
| checkOrMarkPrivateAccess(expressionToStoreOn, field, lhsOfAssignment); |
| boolean superField = isSuperExpression(expressionToStoreOn.getObjectExpression()); |
| boolean accessible = (!superField && receiver.equals(field.getDeclaringClass()) && !field.getDeclaringClass().isAbstract()) // GROOVY-7300, GROOVY-11358 |
| || hasAccessToMember(typeCheckingContext.getEnclosingClassNode(), field.getDeclaringClass(), field.getModifiers()); |
| |
| if (!accessible) { |
| if (expressionToStoreOn instanceof AttributeExpression) { |
| addStaticTypeError("Cannot access field: " + field.getName() + " of class: " + prettyPrintTypeName(field.getDeclaringClass()), expressionToStoreOn.getProperty()); |
| } else if (!field.isProtected()) { // private or package-private |
| return false; |
| } |
| } |
| |
| storeWithResolve(field.getOriginType(), receiver, field.getDeclaringClass(), field.isStatic(), expressionToStoreOn); |
| if (delegationData != null) { |
| expressionToStoreOn.putNodeMetaData(IMPLICIT_RECEIVER, delegationData); |
| } |
| if (field.isFinal()) { |
| MethodNode enclosing = typeCheckingContext.getEnclosingMethod(); |
| if (enclosing == null || !enclosing.getName().endsWith("init>")) |
| expressionToStoreOn.putNodeMetaData(READONLY_PROPERTY, Boolean.TRUE); // GROOVY-5450 |
| } else if (accessible) { |
| expressionToStoreOn.removeNodeMetaData(READONLY_PROPERTY); // GROOVY-9127 |
| } |
| return true; |
| } |
| |
| private boolean storeProperty(final PropertyNode property, final PropertyExpression expression, final ClassNode receiver, final ClassCodeVisitorSupport visitor, final String delegationData, final boolean lhsOfAssignment) { |
| if (visitor != null) visitor.visitProperty(property); |
| |
| ClassNode propertyType = property.getOriginType(); |
| storeWithResolve(propertyType, receiver, property.getDeclaringClass(), property.isStatic(), expression); |
| |
| if (delegationData != null) { |
| expression.putNodeMetaData(IMPLICIT_RECEIVER, delegationData); |
| } |
| if (Modifier.isFinal(property.getModifiers())) { |
| expression.putNodeMetaData(READONLY_PROPERTY, Boolean.TRUE); |
| } else { |
| expression.removeNodeMetaData(READONLY_PROPERTY); |
| } |
| |
| String methodName; |
| ClassNode returnType; |
| Parameter[] parameters; |
| if (!lhsOfAssignment) { |
| methodName = property.getGetterNameOrDefault(); |
| returnType = propertyType; |
| parameters = Parameter.EMPTY_ARRAY; |
| } else { |
| methodName = property.getSetterNameOrDefault(); |
| returnType = VOID_TYPE; |
| parameters = new Parameter[] {new Parameter(propertyType, "value")}; |
| } |
| MethodNode accessMethod = new MethodNode(methodName, Opcodes.ACC_PUBLIC | (property.isStatic() ? Opcodes.ACC_STATIC : 0), returnType, parameters, ClassNode.EMPTY_ARRAY, null); |
| accessMethod.setDeclaringClass(property.getDeclaringClass()); |
| accessMethod.setSynthetic(true); |
| |
| expression.putNodeMetaData(DIRECT_METHOD_CALL_TARGET, accessMethod); // GROOVY-11029 |
| extension.onMethodSelection(expression, accessMethod); |
| return true; |
| } |
| |
| private void storeWithResolve(ClassNode type, final ClassNode receiver, final ClassNode declaringClass, final boolean isStatic, final Expression expressionToStoreOn) { |
| if (!isStatic && GenericsUtils.hasUnresolvedGenerics(type)) { |
| type = resolveGenericsWithContext(extractPlaceHolders(receiver, declaringClass), type); |
| } |
| if (expressionToStoreOn instanceof PropertyExpression) { |
| storeInferredTypeForPropertyExpression((PropertyExpression) expressionToStoreOn, type); |
| } else { |
| storeType(expressionToStoreOn, type); |
| } |
| } |
| |
| private ClassNode resolveGenericsWithContext(final Map<GenericsTypeName, GenericsType> resolvedPlaceholders, final ClassNode currentType) { |
| Map<GenericsTypeName, GenericsType> placeholdersFromContext = extractGenericsParameterMapOfThis(typeCheckingContext); |
| return resolveClassNodeGenerics(resolvedPlaceholders, placeholdersFromContext, currentType); |
| } |
| |
| private void storeInferredTypeForPropertyExpression(final PropertyExpression pexp, final ClassNode type) { |
| if (pexp.isSpreadSafe()) { |
| storeType(pexp, extension.buildListType(type)); |
| } else { |
| storeType(pexp, type); |
| } |
| } |
| |
| @Override |
| public void visitProperty(final PropertyNode node) { |
| boolean osc = typeCheckingContext.isInStaticContext; |
| try { |
| typeCheckingContext.isInStaticContext = node.isInStaticContext(); |
| currentProperty = node; |
| visitAnnotations(node); |
| visitClassCodeContainer(node.getGetterBlock()); |
| visitClassCodeContainer(node.getSetterBlock()); |
| } finally { |
| currentProperty = null; |
| typeCheckingContext.isInStaticContext = osc; |
| } |
| } |
| |
| @Override |
| public void visitField(final FieldNode node) { |
| boolean osc = typeCheckingContext.isInStaticContext; |
| try { |
| typeCheckingContext.isInStaticContext = node.isInStaticContext(); |
| currentField = node; |
| visitAnnotations(node); |
| visitInitialExpression(node.getInitialExpression(), new FieldExpression(node), node); |
| } finally { |
| currentField = null; |
| typeCheckingContext.isInStaticContext = osc; |
| } |
| } |
| |
| private void visitInitialExpression(final Expression value, final Expression target, final ASTNode origin) { |
| if (value != null) { |
| ClassNode lType = target.getType(); |
| applyTargetType(lType, value); // GROOVY-9977 |
| |
| typeCheckingContext.pushEnclosingBinaryExpression(assignX(target, value, origin)); |
| |
| value.visit(this); |
| ClassNode rType = getType(value); |
| if (value instanceof ConstructorCallExpression) { |
| inferDiamondType((ConstructorCallExpression) value, lType); |
| } |
| |
| BinaryExpression dummy = typeCheckingContext.popEnclosingBinaryExpression(); |
| typeCheckAssignment(dummy, target, lType, value, getResultType(lType, ASSIGN, rType, dummy)); |
| } |
| } |
| |
| @Override |
| public void visitForLoop(final ForStatement forLoop) { |
| // collect every variable expression used in the loop body |
| Map<VariableExpression, ClassNode> varTypes = new HashMap<>(); |
| forLoop.getLoopBlock().visit(new VariableExpressionTypeMemoizer(varTypes)); |
| |
| // visit body |
| Map<VariableExpression, List<ClassNode>> oldTracker = pushAssignmentTracking(); |
| Expression collectionExpression = forLoop.getCollectionExpression(); |
| if (collectionExpression instanceof ClosureListExpression) { |
| // for (int i=0; i<...; i++) style loop |
| super.visitForLoop(forLoop); |
| } else { |
| visitStatement(forLoop); |
| collectionExpression.visit(this); |
| ClassNode collectionType = getType(collectionExpression); |
| ClassNode forLoopVariableType = forLoop.getVariableType(); |
| ClassNode componentType; |
| if (isWrapperCharacter(getWrapper(forLoopVariableType)) && isStringType(collectionType)) { |
| // we allow auto-coercion here |
| componentType = forLoopVariableType; |
| } else { |
| componentType = inferLoopElementType(collectionType); |
| } |
| if (getUnwrapper(componentType) == forLoopVariableType) { |
| // prefer primitive type over boxed type |
| componentType = forLoopVariableType; |
| } |
| if (!checkCompatibleAssignmentTypes(forLoopVariableType, componentType)) { |
| addStaticTypeError("Cannot loop with element of type " + prettyPrintType(forLoopVariableType) + " with collection of type " + prettyPrintType(collectionType), forLoop); |
| } |
| if (!isDynamicTyped(forLoopVariableType)) { |
| // user has specified a type, prefer it over the inferred type |
| componentType = forLoopVariableType; |
| } |
| typeCheckingContext.controlStructureVariables.put(forLoop.getVariable(), componentType); |
| try { |
| forLoop.getLoopBlock().visit(this); |
| } finally { |
| typeCheckingContext.controlStructureVariables.remove(forLoop.getVariable()); |
| } |
| } |
| if (isSecondPassNeededForControlStructure(varTypes, oldTracker)) { |
| visitForLoop(forLoop); |
| } |
| } |
| |
| /** |
| * Returns the inferred loop element type given a loop collection type. Used, |
| * for example, to infer the element type of a {@code for (e in list)} loop. |
| * |
| * @param collectionType the type of the collection |
| * @return the inferred component type |
| * @see #inferComponentType |
| */ |
| public static ClassNode inferLoopElementType(final ClassNode collectionType) { |
| ClassNode componentType; |
| if (collectionType.isArray()) { // GROOVY-11335 |
| componentType = collectionType.getComponentType(); |
| |
| } else if (isOrImplements(collectionType, ITERABLE_TYPE)) { |
| ClassNode col = GenericsUtils.parameterizeType(collectionType, ITERABLE_TYPE); |
| componentType = getCombinedBoundType(col.getGenericsTypes()[0]); |
| |
| } else if (isOrImplements(collectionType, MAP_TYPE)) { // GROOVY-6240 |
| ClassNode col = GenericsUtils.parameterizeType(collectionType, MAP_TYPE); |
| componentType = makeClassSafe0(MAP_ENTRY_TYPE, col.getGenericsTypes()); |
| |
| } else if (isOrImplements(collectionType, STREAM_TYPE)) { // GROOVY-10476 |
| ClassNode col = GenericsUtils.parameterizeType(collectionType, STREAM_TYPE); |
| componentType = getCombinedBoundType(col.getGenericsTypes()[0]); |
| |
| } else if (isOrImplements(collectionType, Iterator_TYPE)) { // GROOVY-10712 |
| ClassNode col = GenericsUtils.parameterizeType(collectionType, Iterator_TYPE); |
| componentType = getCombinedBoundType(col.getGenericsTypes()[0]); |
| |
| } else if (isOrImplements(collectionType, ENUMERATION_TYPE)) { // GROOVY-6123 |
| ClassNode col = GenericsUtils.parameterizeType(collectionType, ENUMERATION_TYPE); |
| componentType = getCombinedBoundType(col.getGenericsTypes()[0]); |
| |
| } else if (isStringType(collectionType)) { |
| componentType = STRING_TYPE; |
| } else { |
| componentType = OBJECT_TYPE; |
| } |
| return componentType; |
| } |
| |
| protected boolean isSecondPassNeededForControlStructure(final Map<VariableExpression, ClassNode> startTypes, final Map<VariableExpression, List<ClassNode>> oldTracker) { |
| for (Map.Entry<VariableExpression, ClassNode> entry : popAssignmentTracking(oldTracker).entrySet()) { |
| Variable key = findTargetVariable(entry.getKey()); |
| if (startTypes.containsKey(key) |
| && !startTypes.get(key).equals(entry.getValue())) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| @Override |
| public void visitWhileLoop(final WhileStatement loop) { |
| Map<VariableExpression, List<ClassNode>> oldTracker = pushAssignmentTracking(); |
| super.visitWhileLoop(loop); |
| popAssignmentTracking(oldTracker); |
| } |
| |
| @Override |
| public void visitBitwiseNegationExpression(final BitwiseNegationExpression expression) { |
| super.visitBitwiseNegationExpression(expression); |
| ClassNode type = getType(expression); |
| ClassNode typeRe = type.redirect(); |
| ClassNode resultType; |
| if (isBigIntCategory(typeRe)) { |
| // allow any internal number that is not a floating point one |
| resultType = type; |
| } else if (isStringType(typeRe) || isGStringType(typeRe)) { |
| resultType = PATTERN_TYPE; |
| } else if (typeRe.equals(ArrayList_TYPE)) { |
| resultType = ArrayList_TYPE; |
| } else if (typeRe.equals(PATTERN_TYPE)) { |
| resultType = PATTERN_TYPE; |
| } else { |
| MethodNode mn = findMethodOrFail(expression, type, "bitwiseNegate"); |
| if (mn != null) { |
| resultType = mn.getReturnType(); |
| } else { |
| resultType = OBJECT_TYPE; |
| } |
| } |
| storeType(expression, resultType); |
| } |
| |
| @Override |
| public void visitUnaryPlusExpression(final UnaryPlusExpression expression) { |
| super.visitUnaryPlusExpression(expression); |
| negativeOrPositiveUnary(expression, "positive"); |
| } |
| |
| @Override |
| public void visitUnaryMinusExpression(final UnaryMinusExpression expression) { |
| super.visitUnaryMinusExpression(expression); |
| negativeOrPositiveUnary(expression, "negative"); |
| } |
| |
| @Override |
| public void visitPostfixExpression(final PostfixExpression expression) { |
| Expression operand = expression.getExpression(); |
| int operator = expression.getOperation().getType(); |
| visitPrefixOrPostixExpression(expression, operand, operator); |
| } |
| |
| @Override |
| public void visitPrefixExpression(final PrefixExpression expression) { |
| Expression operand = expression.getExpression(); |
| int operator = expression.getOperation().getType(); |
| visitPrefixOrPostixExpression(expression, operand, operator); |
| } |
| |
| private void visitPrefixOrPostixExpression(final Expression origin, final Expression operand, final int operator) { |
| Optional<Token> token = TokenUtil.asAssignment(operator); |
| // push "operand += 1" or "operand -= 1" onto stack for LHS checks |
| token.ifPresent(value -> typeCheckingContext.pushEnclosingBinaryExpression(binX(operand, value, constX(1)))); |
| try { |
| operand.visit(this); |
| SetterInfo setterInfo = removeSetterInfo(operand); |
| if (setterInfo != null) { |
| BinaryExpression rewrite = typeCheckingContext.getEnclosingBinaryExpression(); |
| rewrite.setSourcePosition(origin); |
| if (ensureValidSetter(rewrite, operand, rewrite.getRightExpression(), setterInfo)) { |
| return; |
| } |
| } |
| |
| ClassNode operandType = getType(operand); |
| boolean isPostfix = (origin instanceof PostfixExpression); |
| String name = (operator == PLUS_PLUS ? "next" : operator == MINUS_MINUS ? "previous" : null); |
| |
| if (name != null && isNumberType(operandType)) { |
| if (!isPrimitiveType(operandType)) { |
| MethodNode node = findMethodOrFail(varX("_dummy_", operandType), operandType, name); |
| if (node != null) { |
| storeTargetMethod(origin, node); |
| storeType(origin, isPostfix ? operandType : getMathWideningClassNode(operandType)); |
| return; |
| } |
| } |
| storeType(origin, operandType); |
| return; |
| } |
| if (name != null && operandType.isDerivedFrom(Number_TYPE)) { |
| // special case for numbers, improve type checking as we can expect ++ and -- to return the same type |
| MethodNode node = findMethodOrFail(operand, operandType, name); |
| if (node != null) { |
| storeTargetMethod(origin, node); |
| storeType(origin, getMathWideningClassNode(operandType)); |
| return; |
| } |
| } |
| if (name == null) { |
| addUnsupportedPreOrPostfixExpressionError(origin); |
| return; |
| } |
| |
| MethodNode node = findMethodOrFail(operand, operandType, name); |
| if (node != null) { |
| storeTargetMethod(origin, node); |
| storeType(origin, isPostfix ? operandType : inferReturnTypeGenerics(operandType, node, ArgumentListExpression.EMPTY_ARGUMENTS)); |
| } |
| } finally { |
| if (token.isPresent()) typeCheckingContext.popEnclosingBinaryExpression(); |
| } |
| } |
| |
| private static ClassNode getMathWideningClassNode(final ClassNode type) { |
| if (isPrimitiveByte(type) || isPrimitiveShort(type) || isPrimitiveInt(type)) { |
| return int_TYPE; |
| } |
| if (isWrapperByte(type) || isWrapperShort(type) || isWrapperInteger(type)) { |
| return Integer_TYPE; |
| } |
| if (isPrimitiveFloat(type)) { |
| return double_TYPE; |
| } |
| if (isWrapperFloat(type)) { |
| return Double_TYPE; |
| } |
| return type; |
| } |
| |
| private void negativeOrPositiveUnary(final Expression expression, final String name) { |
| ClassNode type = getType(expression); |
| ClassNode typeRe = type.redirect(); |
| ClassNode resultType; |
| if (isDoubleCategory(getUnwrapper(typeRe))) { |
| resultType = type; |
| } else if (typeRe.equals(ArrayList_TYPE)) { |
| resultType = ArrayList_TYPE; |
| } else { |
| MethodNode mn = findMethodOrFail(expression, type, name); |
| if (mn != null) { |
| resultType = mn.getReturnType(); |
| } else { |
| resultType = type; |
| } |
| } |
| storeType(expression, resultType); |
| } |
| |
| @Override |
| public void visitExpressionStatement(final ExpressionStatement statement) { |
| typeCheckingContext.pushTemporaryTypeInfo(); |
| super.visitExpressionStatement(statement); |
| Map<?,List<ClassNode>> tti = typeCheckingContext.temporaryIfBranchTypeInformation.pop(); |
| if (!tti.isEmpty() && !typeCheckingContext.temporaryIfBranchTypeInformation.isEmpty()) { |
| tti.forEach((k, tempTypes) -> { |
| if (tempTypes.contains(VOID_TYPE)) |
| typeCheckingContext.temporaryIfBranchTypeInformation.peek() |
| .computeIfAbsent(k, x -> new LinkedList<>()).add(VOID_TYPE); |
| }); |
| } |
| } |
| |
| @Override |
| public void visitReturnStatement(final ReturnStatement statement) { |
| if (typeCheckingContext.getEnclosingClosure() == null) { |
| MethodNode method = typeCheckingContext.getEnclosingMethod(); |
| if (method != null && !method.isVoidMethod() && !method.isDynamicReturnType()) { |
| applyTargetType(method.getReturnType(), statement.getExpression()); // GROOVY-10660 |
| } |
| } |
| super.visitReturnStatement(statement); |
| returnListener.returnStatementAdded(statement); |
| } |
| |
| protected ClassNode checkReturnType(final ReturnStatement statement) { |
| Expression expression = statement.getExpression(); |
| ClassNode type = getType(expression); |
| |
| TypeCheckingContext.EnclosingClosure enclosingClosure = typeCheckingContext.getEnclosingClosure(); |
| if (enclosingClosure != null) { |
| if (enclosingClosure.getClosureExpression().getNodeMetaData(INFERRED_TYPE) != null) return null; // re-visit |
| |
| ClassNode inferredReturnType = getInferredReturnType(enclosingClosure.getClosureExpression()); |
| // GROOVY-9995: return ctor call with diamond operator |
| if (expression instanceof ConstructorCallExpression) { |
| inferDiamondType((ConstructorCallExpression) expression, inferredReturnType != null ? inferredReturnType : /*GROOVY-10080:*/dynamicType()); |
| } |
| if (inferredReturnType != null |
| && !inferredReturnType.equals(type) |
| && !isObjectType(inferredReturnType) |
| && !isPrimitiveVoid(inferredReturnType) |
| && !isPrimitiveBoolean(inferredReturnType) |
| && !GenericsUtils.hasUnresolvedGenerics(inferredReturnType)) { |
| if (isStringType(inferredReturnType) && isGStringOrGStringStringLUB(type)) { |
| type = STRING_TYPE; // GROOVY-9971: convert GString to String at point of return |
| } else if (GenericsUtils.buildWildcardType(wrapTypeIfNecessary(inferredReturnType)).isCompatibleWith(wrapTypeIfNecessary(type))) { |
| type = inferredReturnType; // GROOVY-8310, GROOVY-10082, GROOVY-10091, GROOVY-10128, GROOVY-10306: allow simple covariance |
| } else if (!isPrimitiveVoid(type) && !extension.handleIncompatibleReturnType(statement, type)) { // GROOVY-10277: incompatible return value |
| addStaticTypeError("Cannot return value of type " + prettyPrintType(type) + " for " + (enclosingClosure.getClosureExpression() instanceof LambdaExpression ? "lambda" : "closure") + " expecting " + prettyPrintType(inferredReturnType), expression); |
| } |
| } |
| return type; |
| } |
| |
| MethodNode enclosingMethod = typeCheckingContext.getEnclosingMethod(); |
| if (enclosingMethod != null && !enclosingMethod.isVoidMethod() && !enclosingMethod.isDynamicReturnType()) { |
| ClassNode returnType = enclosingMethod.getReturnType(); |
| if (!isPrimitiveVoid(getUnwrapper(type)) && !checkCompatibleAssignmentTypes(returnType, type, null, false)) { |
| if (!extension.handleIncompatibleReturnType(statement, type)) { |
| addStaticTypeError("Cannot return value of type " + prettyPrintType(type) + " for method returning " + prettyPrintType(returnType), expression); |
| } |
| } else if (implementsInterfaceOrIsSubclassOf(type, returnType)) { |
| BinaryExpression dummy = assignX(varX("{target}", returnType), expression, statement); |
| ClassNode resultType = getResultType(returnType, ASSIGN, type, dummy); // GROOVY-10295 |
| checkTypeGenerics(returnType, resultType, expression); |
| } |
| } |
| return null; |
| } |
| |
| protected void addClosureReturnType(final ClassNode returnType) { |
| if (returnType != null && !isPrimitiveVoid(returnType)) // GROOVY-8202 |
| typeCheckingContext.getEnclosingClosure().addReturnType(returnType); |
| } |
| |
| @Override |
| public void visitConstructorCallExpression(final ConstructorCallExpression call) { |
| if (!extension.beforeMethodCall(call)) { |
| ClassNode receiver; |
| if (call.isThisCall()) { |
| receiver = makeThis(); |
| } else if (call.isSuperCall()) { |
| receiver = makeSuper(); |
| } else { |
| receiver = call.getType(); |
| } |
| Expression arguments = call.getArguments(); |
| ArgumentListExpression argumentList = InvocationWriter.makeArgumentList(arguments); |
| |
| // visit functional arguments *after* method has been chosen |
| visitMethodCallArguments(receiver, argumentList, false, null); |
| final ClassNode[] argumentTypes = getArgumentTypes(argumentList); |
| |
| MethodNode ctor; |
| if (looksLikeNamedArgConstructor(receiver, argumentTypes) |
| && findMethod(receiver, "<init>", argumentTypes).isEmpty() |
| && findMethod(receiver, "<init>", init(argumentTypes)).size() == 1) { |
| ctor = typeCheckMapConstructor(call, receiver, arguments); |
| } else { |
| ctor = findMethodOrFail(call, receiver, "<init>", argumentTypes); |
| visitMethodCallArguments(receiver, argumentList, true, ctor); |
| if (ctor != null) { |
| Parameter[] parameters = ctor.getParameters(); |
| GenericsType[] typeParameters = ctor.getDeclaringClass().getGenericsTypes(); |
| if (typeParameters != null) { // GROOVY-10283, GROOVY-10316, GROOVY-10482, GROOVY-10624, GROOVY-10698 |
| Map<GenericsTypeName, GenericsType> context = extractGenericsConnectionsFromArguments(typeParameters, parameters, argumentList, receiver.getGenericsTypes()); |
| if (!context.isEmpty()) parameters = Arrays.stream(parameters).map(p -> new Parameter(applyGenericsContext(context, p.getType()), p.getName())).toArray(Parameter[]::new); |
| } |
| resolvePlaceholdersFromImplicitTypeHints(argumentTypes, argumentList, parameters); |
| typeCheckMethodsWithGenericsOrFail(receiver, argumentTypes, ctor, call); |
| checkForbiddenSpreadArgument(argumentList, parameters); |
| } else { |
| checkForbiddenSpreadArgument(argumentList); |
| } |
| } |
| if (ctor != null) storeTargetMethod(call, ctor); |
| } |
| |
| // GROOVY-9327: check for AIC in STC method with non-STC enclosing class |
| if (call.isUsingAnonymousInnerClass()) { |
| Set<MethodNode> methods = typeCheckingContext.methodsToBeVisited; |
| if (!methods.isEmpty()) { // indicates specific methods have STC |
| typeCheckingContext.methodsToBeVisited = Collections.emptySet(); |
| |
| ClassNode anonType = call.getType(); |
| visitClass(anonType); // visit anon. inner class inline with method |
| anonType.putNodeMetaData(StaticTypeCheckingVisitor.class, Boolean.TRUE); |
| |
| typeCheckingContext.methodsToBeVisited = methods; |
| } |
| } |
| |
| extension.afterMethodCall(call); |
| } |
| |
| private boolean looksLikeNamedArgConstructor(final ClassNode receiver, final ClassNode[] argumentTypes) { |
| if (argumentTypes.length == 1 || argumentTypes.length == 2 && argumentTypes[0].equals(receiver.getOuterClass())) { |
| return isOrImplements(argumentTypes[argumentTypes.length - 1], MAP_TYPE); |
| } |
| return false; |
| } |
| |
| protected MethodNode typeCheckMapConstructor(final ConstructorCallExpression call, final ClassNode receiver, final Expression arguments) { |
| MethodNode node = null; |
| if (arguments instanceof TupleExpression) { |
| TupleExpression texp = (TupleExpression) arguments; |
| List<Expression> expressions = texp.getExpressions(); |
| // should only get here with size = 2 when inner class constructor |
| if (expressions.size() == 1 || expressions.size() == 2) { |
| Expression expression = expressions.get(expressions.size() - 1); |
| if (expression instanceof MapExpression) { |
| MapExpression argList = (MapExpression) expression; |
| checkGroovyConstructorMap(call, receiver, argList); |
| Parameter[] params = expressions.size() == 1 |
| ? new Parameter[]{new Parameter(MAP_TYPE, "map")} |
| : new Parameter[]{new Parameter(receiver.redirect().getOuterClass(), "$p$"), new Parameter(MAP_TYPE, "map")}; |
| node = new ConstructorNode(Opcodes.ACC_PUBLIC, params, ClassNode.EMPTY_ARRAY, GENERATED_EMPTY_STATEMENT); |
| node.setDeclaringClass(receiver); |
| node.setSynthetic(true); |
| } |
| } |
| } |
| return node; |
| } |
| |
| protected ClassNode[] getArgumentTypes(final ArgumentListExpression argumentList) { |
| return argumentList.getExpressions().stream().map(exp -> |
| isNullConstant(exp) ? UNKNOWN_PARAMETER_TYPE : getType(exp) |
| ).toArray(ClassNode[]::new); |
| } |
| |
| private Map<VariableExpression, ClassNode> scanVars(final ClosureExpression expr) { |
| Map<VariableExpression, ClassNode> varTypes = new HashMap<>(); |
| |
| GroovyCodeVisitor visitor = new VariableExpressionTypeMemoizer(varTypes, true); |
| if (expr.isParameterSpecified()) { |
| for (Parameter p : expr.getParameters()) { |
| if (p.hasInitialExpression()) { |
| p.getInitialExpression().visit(visitor); |
| } |
| } |
| } |
| expr.getCode().visit(visitor); |
| |
| return varTypes; |
| } |
| |
| @Override |
| public void visitClosureExpression(final ClosureExpression expression) { |
| Map<VariableExpression, ClassNode> varTypes = scanVars(expression); |
| Map<VariableExpression, List<ClassNode>> oldTracker = pushAssignmentTracking(); |
| SharedVariableCollector collector = new SharedVariableCollector(getSourceUnit()); |
| expression.visit(collector); // collect every variable expression the closure references |
| Set<VariableExpression> closureSharedVariables = collector.getClosureSharedExpressions(); |
| |
| Map<VariableExpression, Map<StaticTypesMarker, Object>> variableMetadata = new HashMap<>(); |
| if (!closureSharedVariables.isEmpty()) { |
| // GROOVY-6921: do getType in order to update closure shared variables |
| // whose types are inferred thanks to closure parameter type inference |
| for (VariableExpression vexp : closureSharedVariables) getType(vexp); |
| saveVariableExpressionMetadata(closureSharedVariables, variableMetadata); |
| } |
| |
| // perform visit |
| typeCheckingContext.pushEnclosingClosureExpression(expression); |
| DelegationMetadata dmd = getDelegationMetadata(expression); |
| if (dmd != null) { |
| typeCheckingContext.delegationMetadata = newDelegationMetadata(dmd.getType(), dmd.getStrategy()); |
| } else { |
| typeCheckingContext.delegationMetadata = newDelegationMetadata(typeCheckingContext.getEnclosingClassNode(), Closure.OWNER_FIRST); |
| } |
| expression.getCode().visit(this); |
| typeCheckingContext.delegationMetadata = typeCheckingContext.delegationMetadata.getParent(); |
| |
| returnAdder.visitMethod(new MethodNode("dummy", 0, OBJECT_TYPE, Parameter.EMPTY_ARRAY, ClassNode.EMPTY_ARRAY, expression.getCode())); |
| TypeCheckingContext.EnclosingClosure enclosingClosure = typeCheckingContext.popEnclosingClosure(); |
| if (!enclosingClosure.getReturnTypes().isEmpty()) { // populated by ReturnAdder |
| ClassNode returnType = lowestUpperBound(enclosingClosure.getReturnTypes()); |
| storeInferredReturnType(expression, wrapTypeIfNecessary(returnType)); |
| storeType(expression, wrapClosureType(returnType)); |
| } |
| |
| // check types of closure shared variables for change |
| if (isSecondPassNeededForControlStructure(varTypes, oldTracker)) { |
| visitClosureExpression(expression); |
| } |
| |
| // restore original metadata |
| restoreVariableExpressionMetadata(variableMetadata); |
| for (Parameter parameter : getParametersSafe(expression)) { |
| typeCheckingContext.controlStructureVariables.remove(parameter); |
| // GROOVY-10072: visit param default argument expression if present |
| visitInitialExpression(parameter.getInitialExpression(), varX(parameter), parameter); |
| } |
| } |
| |
| @Override |
| public void visitMethodPointerExpression(final MethodPointerExpression expression) { |
| super.visitMethodPointerExpression(expression); |
| Expression nameExpr = expression.getMethodName(); |
| if (nameExpr instanceof ConstantExpression |
| && isStringType(getType(nameExpr))) { |
| String nameText = nameExpr.getText(); |
| |
| if ("new".equals(nameText)) { |
| ClassNode type = getType(expression.getExpression()); |
| if (isClassClassNodeWrappingConcreteType(type)){ |
| type = type.getGenericsTypes()[0].getType(); |
| storeType(expression,wrapClosureType(type)); |
| // GROOVY-10930: check constructor reference |
| ClassNode[] signature = expression.getNodeMetaData(CLOSURE_ARGUMENTS); |
| if (signature != null) { Expression[] mocks = Arrays.stream(signature) |
| .map(t -> typedValueExpression(t)).toArray(Expression[]::new); |
| Expression dummy = ctorX(type, args(mocks)); |
| dummy.setSourcePosition(expression); |
| dummy.visit(this); |
| } |
| } |
| return; |
| } |
| |
| List<Receiver<String>> receivers = new ArrayList<>(); |
| addReceivers(receivers, makeOwnerList(expression.getExpression()), false); |
| |
| ClassNode receiverType = null; |
| List<MethodNode> candidates = EMPTY_METHODNODE_LIST; |
| for (Receiver<String> currentReceiver : receivers) { |
| receiverType = wrapTypeIfNecessary(currentReceiver.getType()); |
| |
| candidates = findMethodsWithGenerated(receiverType, nameText); |
| // GROOVY-10741: check for reference to a property node's method |
| MethodNode generated = findPropertyMethod(receiverType, nameText); |
| if (generated != null && candidates.stream().noneMatch(m -> m.getName().equals(generated.getName()))) { |
| candidates.add(generated); |
| } |
| candidates.addAll(findDGMMethodsForClassNode(getSourceUnit().getClassLoader(), receiverType, nameText)); |
| |
| if (candidates.size() > 1) { |
| candidates = filterMethodCandidates(candidates, expression.getExpression(), expression.getNodeMetaData(CLOSURE_ARGUMENTS)); |
| } |
| if (!candidates.isEmpty()) { |
| break; |
| } |
| } |
| |
| if (candidates.isEmpty()) { |
| candidates = extension.handleMissingMethod( |
| getType(expression.getExpression()), nameText, null, null, null); |
| } else if (candidates.size() > 1) { |
| candidates = extension.handleAmbiguousMethods(candidates, expression); |
| } |
| |
| if (!candidates.isEmpty()) { |
| ClassNode[] arguments = expression.getNodeMetaData(CLOSURE_ARGUMENTS); |
| if (asBoolean(arguments) && asBoolean(receiverType.redirect().getGenericsTypes()) |
| && expression.getExpression() instanceof ClassExpression) { |
| receiverType = GenericsUtils.parameterizeType(arguments[0], receiverType); // GROOVY-11241 |
| } |
| |
| ClassNode ownerType = receiverType; |
| candidates.stream() |
| .map(candidate -> { |
| ClassNode returnType = candidate.getReturnType(); |
| if (!candidate.isStatic() && GenericsUtils.hasUnresolvedGenerics(returnType)) { |
| Map<GenericsTypeName, GenericsType> spec = new HashMap<>(); // GROOVY-11364 |
| extractGenericsConnections(spec, ownerType, candidate.getDeclaringClass()); |
| returnType = applyGenericsContext(spec, returnType); |
| } |
| return returnType; |
| }) |
| .reduce(WideningCategories::lowestUpperBound).ifPresent(returnType -> { |
| ClassNode closureType = wrapClosureType(returnType); |
| storeType(expression, closureType); |
| // GROOVY-10858: check method return type |
| ClassNode targetType = expression.getNodeMetaData(PARAMETER_TYPE); |
| if (!returnType.isGenericsPlaceHolder() && isSAMType(targetType)) { |
| targetType = GenericsUtils.parameterizeSAM(targetType).getV2(); |
| if (!isPrimitiveVoid(targetType) && !checkCompatibleAssignmentTypes(targetType, returnType, null, false)) // TODO: ext.handleIncompatibleReturnType |
| addStaticTypeError("Invalid return type: " + prettyPrintType(returnType) + " is not convertible to " + prettyPrintType(targetType), expression); |
| } |
| }); |
| expression.putNodeMetaData(MethodNode.class, candidates); |
| |
| if (asBoolean(arguments)) { |
| ClassNode[] parameters = collateMethodReferenceParameterTypes(expression, candidates.get(0)); |
| for (int i = 0; i < arguments.length; i += 1) { |
| ClassNode at = arguments[i]; |
| ClassNode pt = parameters[Math.min(i, parameters.length - 1)]; |
| if (!pt.equals(at) && (at.isInterface() ? pt.implementsInterface(at) : pt.isDerivedFrom(at))) |
| arguments[i] = pt; // GROOVY-10734, GROOVY-10807, GROPOVY-11026: expected type is refined |
| } |
| } |
| } else if (!(expression instanceof MethodReferenceExpression) |
| || this.getClass() == StaticTypeCheckingVisitor.class) { |
| ClassNode type = wrapTypeIfNecessary(getType(expression.getExpression())); |
| if (isClassClassNodeWrappingConcreteType(type)) type = type.getGenericsTypes()[0].getType(); |
| addStaticTypeError("Cannot find matching method " + prettyPrintTypeName(type) + "#" + nameText + ". Please check if the declared type is correct and if the method exists.", nameExpr); |
| } |
| } |
| } |
| |
| private static Expression typedValueExpression(final ClassNode type) { |
| if (isObjectType(type) && !type.isGenericsPlaceHolder()) { |
| return nullX(); // GROOVY-10971: unknown argument type |
| } |
| return castX(type, defaultValueX(type)); |
| } |
| |
| private static ClassNode wrapClosureType(final ClassNode returnType) { |
| return makeClassSafe0(CLOSURE_TYPE, wrapTypeIfNecessary(returnType).asGenericsType()); |
| } |
| |
| private static MethodNode findPropertyMethod(final ClassNode type, final String name) { |
| for (ClassNode cn = type; cn != null; cn = cn.getSuperClass()) { |
| for (PropertyNode pn : cn.getProperties()) { |
| if (name.equals(pn.getGetterNameOrDefault())) { |
| MethodNode node = new MethodNode(name, Opcodes.ACC_PUBLIC | (pn.isStatic() ? Opcodes.ACC_STATIC : 0), pn.getType(), Parameter.EMPTY_ARRAY, ClassNode.EMPTY_ARRAY, null); |
| node.setDeclaringClass(pn.getDeclaringClass()); |
| node.setSynthetic(true); |
| return node; |
| } else if (name.equals(pn.getSetterNameOrDefault()) && !Modifier.isFinal(pn.getModifiers())) { |
| MethodNode node = new MethodNode(name, Opcodes.ACC_PUBLIC | (pn.isStatic() ? Opcodes.ACC_STATIC : 0), VOID_TYPE, new Parameter[]{new Parameter(pn.getType(), pn.getName())}, ClassNode.EMPTY_ARRAY, null); |
| node.setDeclaringClass(pn.getDeclaringClass()); |
| node.setSynthetic(true); |
| return node; |
| } |
| } |
| } |
| return null; |
| } |
| |
| private List<MethodNode> filterMethodCandidates(final List<MethodNode> candidates, final Expression objectOrType, /*@Nullable*/ ClassNode[] signature) { |
| List<MethodNode> result = filterMethodsByVisibility(candidates, typeCheckingContext.getEnclosingClassNode()); |
| // assignment or parameter target type may help reduce the list |
| if (result.size() > 1 && signature != null) { |
| ClassNode type = getType(objectOrType); |
| if (!isClassClassNodeWrappingConcreteType(type)) { |
| result = chooseBestMethod(type, result, signature); |
| } else { |
| type = type.getGenericsTypes()[0].getType(); // Class<Type> --> Type |
| Map<Boolean, List<MethodNode>> staticAndNonStatic = result.stream().collect(Collectors.partitioningBy(this::isStaticInContext)); |
| |
| result = new ArrayList<>(result.size()); |
| result.addAll(chooseBestMethod(type, staticAndNonStatic.get(Boolean.TRUE), signature)); |
| if (result.isEmpty() && !staticAndNonStatic.get(Boolean.FALSE).isEmpty()) { // GROOVY-11009 |
| if (signature.length > 0) signature= Arrays.copyOfRange(signature, 1, signature.length); |
| result.addAll(chooseBestMethod(type, staticAndNonStatic.get(Boolean.FALSE), signature)); |
| } |
| } |
| } |
| return result; |
| } |
| |
| protected DelegationMetadata getDelegationMetadata(final ClosureExpression expression) { |
| return expression.getNodeMetaData(DELEGATION_METADATA); |
| } |
| |
| private DelegationMetadata newDelegationMetadata(final ClassNode delegateType, final int resolveStrategy) { |
| return new DelegationMetadata(delegateType, resolveStrategy, typeCheckingContext.delegationMetadata); |
| } |
| |
| protected void restoreVariableExpressionMetadata(final Map<VariableExpression, Map<StaticTypesMarker, Object>> typesBeforeVisit) { |
| if (typesBeforeVisit != null) { |
| typesBeforeVisit.forEach((var, map) -> { |
| for (StaticTypesMarker marker : StaticTypesMarker.values()) { |
| // GROOVY-9344, GROOVY-9516: keep type |
| if (marker == INFERRED_TYPE) continue; |
| |
| Object value = map.get(marker); |
| if (value == null) { |
| var.removeNodeMetaData(marker); |
| } else { |
| var.putNodeMetaData(marker, value); |
| } |
| } |
| var.removeNodeMetaData(DECLARATION_INFERRED_TYPE); // GROOVY-8946 |
| }); |
| } |
| } |
| |
| protected void saveVariableExpressionMetadata(final Set<VariableExpression> closureSharedExpressions, final Map<VariableExpression, Map<StaticTypesMarker, Object>> typesBeforeVisit) { |
| for (VariableExpression ve : closureSharedExpressions) { |
| Variable v; |
| while ((v = ve.getAccessedVariable()) != ve && v instanceof VariableExpression) { |
| ve = (VariableExpression) v; |
| } |
| |
| Map<StaticTypesMarker, Object> metadata = new EnumMap<>(StaticTypesMarker.class); |
| for (StaticTypesMarker marker : StaticTypesMarker.values()) { |
| Object value = ve.getNodeMetaData(marker); |
| if (value != null) { |
| metadata.put(marker, value); |
| } |
| } |
| typesBeforeVisit.put(ve, metadata); |
| } |
| } |
| |
| @Override |
| public void visitConstructor(final ConstructorNode node) { |
| if (shouldSkipMethodNode(node)) { |
| return; |
| } |
| super.visitConstructor(node); |
| } |
| |
| @Override |
| public void visitMethod(final MethodNode node) { |
| if (shouldSkipMethodNode(node)) { |
| return; |
| } |
| if (!extension.beforeVisitMethod(node)) { |
| ErrorCollector collector = node.getNodeMetaData(ERROR_COLLECTOR); |
| if (collector != null) { |
| typeCheckingContext.getErrorCollector().addCollectorContents(collector); |
| } else { |
| startMethodInference(node, typeCheckingContext.getErrorCollector()); |
| } |
| node.removeNodeMetaData(ERROR_COLLECTOR); |
| } |
| extension.afterVisitMethod(node); |
| } |
| |
| protected void startMethodInference(final MethodNode node, final ErrorCollector collector) { |
| // second, we must ensure that this method MUST be statically checked |
| // for example, in a mixed mode where only some methods are statically checked |
| // we must not visit a method which used dynamic dispatch. |
| // We do not check for an annotation because some other AST transformations |
| // may use this visitor without the annotation being explicitly set |
| if ((typeCheckingContext.methodsToBeVisited.isEmpty() || typeCheckingContext.methodsToBeVisited.contains(node)) |
| && typeCheckingContext.alreadyVisitedMethods.add(node)) { // prevent re-visiting method (infinite loop) |
| typeCheckingContext.pushErrorCollector(collector); |
| boolean osc = typeCheckingContext.isInStaticContext; |
| try { |
| // GROOVY-7890: non-static trait method is static in helper type |
| typeCheckingContext.isInStaticContext = isNonStaticHelperMethod(node) ? false : node.isStatic(); |
| |
| super.visitMethod(node); |
| } finally { |
| typeCheckingContext.isInStaticContext = osc; |
| } |
| typeCheckingContext.popErrorCollector(); |
| node.putNodeMetaData(ERROR_COLLECTOR, collector); |
| } |
| } |
| |
| @Override |
| protected void visitConstructorOrMethod(final MethodNode node, final boolean isConstructor) { |
| typeCheckingContext.pushEnclosingMethod(node); |
| final ClassNode returnType = node.getReturnType(); // GROOVY-10660: implicit return case |
| if (!isConstructor && (isClosureWithType(returnType) || isFunctionalInterface(returnType))) { |
| new ReturnAdder(returnStmt -> applyTargetType(returnType, returnStmt.getExpression())).visitMethod(node); |
| } |
| readClosureParameterAnnotation(node); // GROOVY-6603 |
| doWithTypeCheckingExtensions(node, it -> super.visitConstructorOrMethod(it, isConstructor)); |
| if (node.hasDefaultValue()) { |
| visitDefaultParameterArguments(node.getParameters()); |
| } |
| if (!isConstructor) { |
| returnAdder.visitMethod(node); // GROOVY-7753: we cannot count these auto-generated return statements, see `typeCheckingContext.pushEnclosingReturnStatement` |
| } |
| typeCheckingContext.popEnclosingMethod(); |
| } |
| |
| private void readClosureParameterAnnotation(final MethodNode node) { |
| for (Parameter parameter : node.getParameters()) { |
| for (AnnotationNode annotation : parameter.getAnnotations()) { |
| if (annotation.getClassNode().equals(CLOSUREPARAMS_CLASSNODE)) { |
| // GROOVY-6603: propagate closure parameter types |
| Expression value = annotation.getMember("value"); |
| Expression options = annotation.getMember("options"); |
| List<ClassNode[]> signatures = getSignaturesFromHint(node, value, options, annotation); |
| if (signatures.size() == 1) { // TODO: handle multiple signatures |
| parameter.putNodeMetaData(CLOSURE_ARGUMENTS, Arrays.stream(signatures.get(0)).map(t -> new Parameter(t,"")).toArray(Parameter[]::new)); |
| } |
| } |
| } |
| } |
| } |
| |
| private void visitDefaultParameterArguments(final Parameter[] parameters) { |
| for (Parameter parameter : parameters) { |
| if (!parameter.hasInitialExpression()) continue; |
| // GROOVY-10094: visit param default argument expression |
| visitInitialExpression(parameter.getInitialExpression(), varX(parameter), parameter); |
| // GROOVY-10104: remove direct target setting to prevent errors |
| parameter.getInitialExpression().visit(new CodeVisitorSupport() { |
| @Override |
| public void visitMethodCallExpression(final MethodCallExpression mce) { |
| super.visitMethodCallExpression(mce); |
| mce.setMethodTarget(null); |
| } |
| }); |
| } |
| } |
| |
| @Override |
| protected void visitObjectInitializerStatements(final ClassNode node) { |
| // GROOVY-5450: create fake constructor node so final field analysis can allow write within non-static initializer block(s) |
| ConstructorNode init = new ConstructorNode(0, null, null, new BlockStatement(node.getObjectInitializerStatements(), null)); |
| typeCheckingContext.pushEnclosingMethod(init); |
| super.visitObjectInitializerStatements(node); |
| typeCheckingContext.popEnclosingMethod(); |
| } |
| |
| protected void addTypeCheckingInfoAnnotation(final MethodNode node) { |
| // TypeChecked$TypeCheckingInfo cannot be applied on constructors |
| if (node.isConstructor()) return; |
| |
| // if a returned inferred type is available and no @TypeCheckingInfo is on node, then add an |
| // annotation to the method node |
| ClassNode rtype = getInferredReturnType(node); |
| if (rtype != null && node.getAnnotations(TYPECHECKING_INFO_NODE).isEmpty()) { |
| AnnotationNode anno = new AnnotationNode(TYPECHECKING_INFO_NODE); |
| anno.setMember("version", CURRENT_SIGNATURE_PROTOCOL); |
| SignatureCodec codec = SignatureCodecFactory.getCodec(CURRENT_SIGNATURE_PROTOCOL_VERSION, getTransformLoader()); |
| String genericsSignature = codec.encode(rtype); |
| if (genericsSignature != null) { |
| ConstantExpression signature = new ConstantExpression(genericsSignature); |
| signature.setType(STRING_TYPE); |
| anno.setMember("inferredType", signature); |
| node.addAnnotation(anno); |
| } |
| } |
| } |
| |
| @Override |
| public void visitStaticMethodCallExpression(final StaticMethodCallExpression call) { |
| String name = call.getMethod(); |
| if (name == null) { |
| addStaticTypeError("cannot resolve dynamic method name at compile time.", call); |
| return; |
| } |
| ClassNode type = call.getOwnerType(); |
| if (type.isEnum() && name.equals("$INIT")) { // GROOVY-10845: $INIT(Object[]) delegates to constructor |
| Expression target = typeCheckingContext.getEnclosingBinaryExpression().getLeftExpression(); |
| ConstructorCallExpression cce = new ConstructorCallExpression(type, call.getArguments()); |
| cce.setSourcePosition(((FieldExpression) target).getField()); |
| visitConstructorCallExpression(cce); |
| |
| MethodNode init = type.getDeclaredMethods("$INIT").get(0); |
| call.putNodeMetaData(INFERRED_TYPE, init.getReturnType()); |
| call.putNodeMetaData(DIRECT_METHOD_CALL_TARGET, init); |
| return; |
| } |
| if (extension.beforeMethodCall(call)) { |
| extension.afterMethodCall(call); |
| return; |
| } |
| try { |
| ArgumentListExpression argumentList = InvocationWriter.makeArgumentList(call.getArguments()); |
| |
| boolean functorsVisited = false; // visit *after* method selection |
| visitMethodCallArguments(type, argumentList, functorsVisited, null); |
| |
| { |
| ClassNode[] args = getArgumentTypes(argumentList); |
| List<MethodNode> opts = findMethod(type, name, args); |
| if (opts.isEmpty()) { |
| opts = extension.handleMissingMethod(type, name, argumentList, args, call); |
| } |
| if (opts.isEmpty()) { |
| addNoMatchingMethodError(type, name, args, call); |
| } else { |
| opts = disambiguateMethods(opts, type, args, call); |
| if (opts.size() != 1) { |
| addAmbiguousErrorMessage(opts, name, args, call); |
| } else { |
| MethodNode targetMethod = opts.get(0); |
| storeTargetMethod(call, targetMethod); // trigger onMethodSelection |
| if (call.getNodeMetaData(DIRECT_METHOD_CALL_TARGET) == targetMethod) { |
| // GROOVY-8909, GROOVY-8961, GROOVY-9734, GROOVY-9844, GROOVY-9915, et al. |
| resolvePlaceholdersFromImplicitTypeHints(args, argumentList, targetMethod.getParameters()); |
| typeCheckMethodsWithGenericsOrFail(type, args, targetMethod, call); |
| |
| ClassNode returnType = getType(targetMethod); |
| if (isUsingGenericsOrIsArrayUsingGenerics(returnType)) { |
| functorsVisited = true; // visit functional argument(s) with selected method |
| visitMethodCallArguments(type, argumentList, functorsVisited, targetMethod); |
| ClassNode rt = inferReturnTypeGenerics(type, targetMethod, argumentList); |
| if (rt != null && implementsInterfaceOrIsSubclassOf(rt, returnType)) { |
| returnType = rt; |
| } |
| } |
| storeType(call, returnType); |
| } |
| } |
| } |
| } |
| |
| // type-checking extension may have changed or cleared target method |
| MethodNode target = call.getNodeMetaData(DIRECT_METHOD_CALL_TARGET); |
| if (!functorsVisited) { |
| visitMethodCallArguments(type, argumentList, true, target); |
| } |
| if (target != null) { Parameter[] params = target.getParameters(); |
| checkClosureMetadata(argumentList.getExpressions(), params); |
| checkForbiddenSpreadArgument(argumentList, params); |
| } else { |
| checkForbiddenSpreadArgument(argumentList); |
| } |
| } finally { |
| extension.afterMethodCall(call); |
| } |
| } |
| |
| /** |
| * @deprecated this method is unused, replaced with {@link DelegatesTo} inference. |
| */ |
| @Deprecated(forRemoval = true, since = "2.5.0") |
| protected void checkClosureParameters(final Expression callArguments, final ClassNode receiver) { |
| if (callArguments instanceof ArgumentListExpression) { |
| ArgumentListExpression argList = (ArgumentListExpression) callArguments; |
| ClosureExpression closure = (ClosureExpression) argList.getExpression(0); |
| Parameter[] parameters = closure.getParameters(); |
| if (parameters.length > 1) { |
| addStaticTypeError("Unexpected number of parameters for a with call", argList); |
| } else if (parameters.length == 1) { |
| Parameter param = parameters[0]; |
| if (!param.isDynamicTyped() && !isAssignableTo(receiver, param.getType().redirect())) { |
| addStaticTypeError("Expected parameter type: " + prettyPrintType(receiver) + " but was: " + prettyPrintType(param.getType()), param); |
| } |
| } |
| closure.putNodeMetaData(DELEGATION_METADATA, newDelegationMetadata(receiver, Closure.DELEGATE_FIRST)); |
| } |
| } |
| |
| /** |
| * Visits a method call target, to infer the type. Don't report errors right |
| * away, that will be done by a later visitMethod call. |
| */ |
| protected void silentlyVisitMethodNode(final MethodNode directMethodCallCandidate) { |
| // visit is authorized because the classnode belongs to the same source unit |
| ErrorCollector collector = new ErrorCollector(typeCheckingContext.getErrorCollector().getConfiguration()); |
| startMethodInference(directMethodCallCandidate, collector); |
| } |
| |
| protected void visitMethodCallArguments(final ClassNode receiver, final ArgumentListExpression arguments, final boolean visitFunctors, final MethodNode selectedMethod) { |
| Parameter[] parameters; |
| List<Expression> expressions = new ArrayList<>(); |
| if (selectedMethod instanceof ExtensionMethodNode) { |
| parameters = ((ExtensionMethodNode) selectedMethod).getExtensionMethodNode().getParameters(); |
| expressions.add(varX("$self", receiver)); |
| } else { |
| parameters = selectedMethod != null ? selectedMethod.getParameters() : Parameter.EMPTY_ARRAY; |
| } |
| expressions.addAll(arguments.getExpressions()); |
| |
| int nExpressions = expressions.size(); |
| int nthParameter = parameters.length - 1; |
| for (int i = 0; i < nExpressions; i += 1) { |
| Expression expression = expressions.get(i); |
| if (visitFunctors == (expression instanceof ClosureExpression || expression instanceof MethodPointerExpression)) { |
| if (visitFunctors && nthParameter != -1) { // GROOVY-10636: vargs call |
| Parameter target = parameters[Math.min(i, nthParameter)]; |
| ClassNode targetType = target.getType(); |
| if (targetType.isArray() && i >= nthParameter) |
| targetType = targetType.getComponentType(); |
| boolean coerceToMethod = isSAMType(targetType); |
| if (coerceToMethod) { // resolve the target parameter's type |
| Map<GenericsTypeName, GenericsType> context = extractPlaceHoldersVisibleToDeclaration(receiver, selectedMethod, arguments); |
| targetType = applyGenericsContext(context, targetType); |
| expression.putNodeMetaData(PARAMETER_TYPE, targetType); |
| } |
| if (expression instanceof ClosureExpression) { |
| checkClosureWithDelegatesTo(receiver, selectedMethod, args(expressions), parameters, expression, target); |
| if (i > 0 || !(selectedMethod instanceof ExtensionMethodNode)) { |
| inferClosureParameterTypes(receiver, arguments, (ClosureExpression) expression, target, selectedMethod); |
| } |
| if (coerceToMethod && targetType.isInterface()) { // @FunctionalInterface |
| storeInferredReturnType(expression, GenericsUtils.parameterizeSAM(targetType).getV2()); |
| } else if (isClosureWithType(targetType)) { |
| storeInferredReturnType(expression, getCombinedBoundType(targetType.getGenericsTypes()[0])); |
| } |
| } else if (expression instanceof MethodReferenceExpression) { |
| if (coerceToMethod && targetType.isInterface()) { // @FunctionalInterface |
| LambdaExpression lambda = constructLambdaExpressionForMethodReference( |
| targetType, (MethodReferenceExpression) expression); |
| inferClosureParameterTypes(receiver, arguments, lambda, target, selectedMethod); |
| expression.putNodeMetaData(PARAMETER_TYPE, lambda.getNodeMetaData(PARAMETER_TYPE)); |
| expression.putNodeMetaData(CLOSURE_ARGUMENTS, lambda.getNodeMetaData(CLOSURE_ARGUMENTS)); |
| } else { |
| addError("The argument is a method reference, but the parameter type is not a functional interface", expression); |
| } |
| } |
| } |
| expression.visit(this); |
| expression.removeNodeMetaData(DELEGATION_METADATA); |
| } |
| if (i == 0 && parameters.length > 0 && expression instanceof MapExpression) { |
| checkNamedParamsAnnotation(parameters[0], (MapExpression) expression); |
| } |
| } |
| } |
| |
| private static LambdaExpression constructLambdaExpressionForMethodReference(final ClassNode functionalInterface, final MethodReferenceExpression methodReference) { |
| Parameter[] parameters = findSAM(functionalInterface).getParameters(); |
| int nParameters = parameters.length; |
| if (nParameters > 0) { |
| parameters = new Parameter[nParameters]; |
| for (int i = 0; i < nParameters; i += 1) |
| parameters[i] = new Parameter(dynamicType(), "p" + i); |
| } |
| return new LambdaExpression(parameters, GENERATED_EMPTY_STATEMENT); |
| } |
| |
| private void checkNamedParamsAnnotation(final Parameter param, final MapExpression args) { |
| if (!isOrImplements(param.getType(), MAP_TYPE)) return; |
| List<MapEntryExpression> entryExpressions = args.getMapEntryExpressions(); |
| Map<Object, Expression> entries = new LinkedHashMap<>(); |
| for (MapEntryExpression entry : entryExpressions) { |
| Object key = entry.getKeyExpression(); |
| if (key instanceof ConstantExpression) { |
| key = ((ConstantExpression) key).getValue(); |
| } |
| entries.put(key, entry.getValueExpression()); |
| } |
| List<String> collectedNames = new ArrayList<>(); |
| List<AnnotationNode> annotations = param.getAnnotations(NAMED_PARAMS_CLASSNODE); |
| if (annotations != null && !annotations.isEmpty()) { |
| AnnotationNode an = null; |
| for (AnnotationNode next : annotations) { |
| if (next.getClassNode().getName().equals(NamedParams.class.getName())) { |
| an = next; |
| } |
| } |
| if (an != null) { |
| Expression value = an.getMember("value"); |
| if (value instanceof AnnotationConstantExpression) { |
| processNamedParam((AnnotationConstantExpression) value, entries, args, collectedNames); |
| } else if (value instanceof ListExpression) { |
| ListExpression le = (ListExpression) value; |
| for (Expression next : le.getExpressions()) { |
| if (next instanceof AnnotationConstantExpression) { |
| processNamedParam((AnnotationConstantExpression) next, entries, args, collectedNames); |
| } |
| } |
| } |
| } |
| } |
| annotations = param.getAnnotations(NAMED_PARAM_CLASSNODE); |
| if (annotations != null && !annotations.isEmpty()) { |
| for (AnnotationNode next : annotations) { |
| if (next.getClassNode().getName().equals(NamedParam.class.getName())) { |
| processNamedParam(next, entries, args, collectedNames); |
| } |
| } |
| } |
| if (!collectedNames.isEmpty()) { |
| for (Map.Entry<Object, Expression> entry : entries.entrySet()) { |
| if (!collectedNames.contains(entry.getKey())) { |
| addStaticTypeError("unexpected named arg: " + entry.getKey(), args); |
| } |
| } |
| } |
| } |
| |
| private void processNamedParam(final AnnotationConstantExpression value, final Map<Object, Expression> entries, final Expression expression, final List<String> collectedNames) { |
| AnnotationNode namedParam = (AnnotationNode) value.getValue(); |
| if (!namedParam.getClassNode().getName().equals(NamedParam.class.getName())) return; |
| processNamedParam(namedParam, entries, expression, collectedNames); |
| } |
| |
| private void processNamedParam(final AnnotationNode namedParam, final Map<Object, Expression> entries, final Expression expression, final List<String> collectedNames) { |
| String name = null; |
| boolean required = false; |
| ClassNode expectedType = null; |
| ConstantExpression constX = (ConstantExpression) namedParam.getMember("value"); |
| if (constX != null) { |
| name = (String) constX.getValue(); |
| collectedNames.add(name); |
| } |
| constX = (ConstantExpression) namedParam.getMember("required"); |
| if (constX != null) { |
| required = (Boolean) constX.getValue(); |
| } |
| ClassExpression typeX = (ClassExpression) namedParam.getMember("type"); |
| if (typeX != null) { |
| expectedType = typeX.getType(); |
| } |
| if (!entries.containsKey(name)) { |
| if (required) { |
| addStaticTypeError("required named param '" + name + "' not found.", expression); |
| } |
| } else if (expectedType != null) { |
| ClassNode argumentType = getDeclaredOrInferredType(entries.get(name)); |
| if (!isAssignableTo(argumentType, expectedType)) { |
| addStaticTypeError("argument for named param '" + name + "' has type '" + prettyPrintType(argumentType) + "' but expected '" + prettyPrintType(expectedType) + "'.", expression); |
| } |
| } |
| } |
| |
| /** |
| * Performs type inference on closure argument types whenever code like this |
| * is found: <code>foo.collect { it.toUpperCase() }</code>. |
| * <p> |
| * In this case the type checker tries to find if the {@code collect} method |
| * has its {@link Closure} argument annotated with {@link ClosureParams}. If |
| * so, then additional type inference can be performed and the type of |
| * {@code it} may be inferred. |
| * |
| * @param receiver |
| * @param arguments |
| * @param expression closure or lambda expression for which the argument types should be inferred |
| * @param target parameter which may provide {@link ClosureParams} annotation or SAM type |
| * @param method method that declares {@code target} |
| */ |
| protected void inferClosureParameterTypes(final ClassNode receiver, final Expression arguments, final ClosureExpression expression, final Parameter target, final MethodNode method) { |
| List<AnnotationNode> annotations = target.getAnnotations(CLOSUREPARAMS_CLASSNODE); |
| if (annotations != null && !annotations.isEmpty()) { |
| for (AnnotationNode annotation : annotations) { |
| Expression value = annotation.getMember("value"); |
| Expression options = annotation.getMember("options"); |
| Expression conflictResolver = annotation.getMember("conflictResolutionStrategy"); |
| processClosureParams(receiver, arguments, expression, method, value, conflictResolver, options); |
| } |
| } else if (isSAMType(target.getOriginType())) { // SAM-type coercion |
| boolean isConstructor = method.isConstructor(); |
| boolean hasTypeArguments = asBoolean(receiver.getGenericsTypes()); |
| Map<GenericsTypeName, GenericsType> context = isConstructor && !hasTypeArguments ? new HashMap<>() // GROOVY-10699 |
| : extractPlaceHoldersVisibleToDeclaration(receiver, method, arguments); |
| GenericsType[] typeParameters = isConstructor ? method.getDeclaringClass().getGenericsTypes() : applyGenericsContext(context, method.getGenericsTypes()); |
| |
| if (typeParameters != null) { |
| boolean typeParametersResolved = false; |
| // first check for explicit type arguments |
| if (isConstructor) { |
| typeParametersResolved = hasTypeArguments; |
| } else { |
| Expression emc = typeCheckingContext.getEnclosingMethodCall(); |
| if (emc instanceof MethodCallExpression) { |
| MethodCallExpression mce = (MethodCallExpression) emc; |
| if (mce.getArguments() == arguments) { |
| GenericsType[] typeArguments = mce.getGenericsTypes(); |
| if (typeArguments != null) { |
| int n = typeParameters.length; |
| if (n == typeArguments.length) { |
| typeParametersResolved = true; |
| for (int i = 0; i < n; i += 1) { |
| context.put(new GenericsTypeName(typeParameters[i].getName()), typeArguments[i]); |
| } |
| } |
| } |
| } |
| } |
| } |
| if (!typeParametersResolved) { |
| // check for implicit type arguments |
| int i = -1; Parameter[] p = method.getParameters(); |
| for (Expression argument : (ArgumentListExpression) arguments) { i += 1; |
| if (isNullConstant(argument)) continue; |
| |
| ClassNode pType = p[Math.min(i, p.length - 1)].getType(); |
| Map<GenericsTypeName, GenericsType> gc = new HashMap<>(); |
| extractGenericsConnections(gc, wrapTypeIfNecessary(getType(argument)), pType); |
| // GROOVY-10436: extract generics connections from closure parameter declaration(s) |
| if (argument == expression || (argument instanceof ClosureExpression && isSAMType(pType))) { |
| Parameter[] q = getParametersSafe((ClosureExpression) argument); |
| ClassNode[] r = extractTypesFromParameters(q); // maybe typed |
| ClassNode[] s = GenericsUtils.parameterizeSAM(pType).getV1(); |
| for (int j = 0; j < r.length && j < s.length; j += 1) |
| if (!q[j].isDynamicTyped()) extractGenericsConnections(gc, r[j], s[j]); |
| } |
| |
| gc.forEach((key, gt) -> { |
| for (GenericsType tp : typeParameters) { |
| if (tp.getName().equals(key.getName())) { |
| context.putIfAbsent(key, gt); // TODO: merge |
| break; |
| } |
| } |
| }); |
| } |
| |
| for (GenericsType tp : typeParameters) { |
| GenericsTypeName name = new GenericsTypeName(tp.getName()); |
| context.computeIfAbsent(name, x -> fullyResolve(tp, context)); |
| } |
| } |
| } |
| |
| ClassNode[] paramTypes = expression.getNodeMetaData(CLOSURE_ARGUMENTS); |
| if (paramTypes == null) { |
| ClassNode targetType = target.getType(); |
| if (targetType != null && targetType.isGenericsPlaceHolder()) |
| targetType = getCombinedBoundType(targetType.asGenericsType()); |
| targetType = applyGenericsContext(context, targetType); // fill "T" |
| inferParameterAndReturnTypesOfClosureOnRHS(targetType, expression); |
| } |
| } |
| } |
| |
| private void processClosureParams(final ClassNode receiver, final Expression arguments, final ClosureExpression expression, final MethodNode selectedMethod, final Expression hintClass, final Expression resolverClass, final Expression options) { |
| Parameter[] parameters = hasImplicitParameter(expression) ? new Parameter[]{new Parameter(dynamicType(),"it")} : getParametersSafe(expression); |
| |
| List<ClassNode[]> closureSignatures = new LinkedList<>(getSignaturesFromHint(selectedMethod, hintClass, options, expression)); |
| List<ClassNode[]> candidates = new LinkedList<>(); |
| for (var it = closureSignatures.listIterator(); it.hasNext();) { ClassNode[] signature = it.next(); |
| resolveGenericsFromTypeHint(receiver, arguments, selectedMethod, signature); |
| if (parameters.length == signature.length) { |
| candidates.add(signature); |
| } |
| if ((parameters.length != 1 || !isObjectType(parameters[0].getOriginType())) |
| && signature.length == 1 && isOrImplements(signature[0], LIST_TYPE)) { |
| // list element(s) spread across closure parameter(s) : ClosureMetaClass |
| int itemCount = TUPLE_TYPES.indexOf(signature[0]); |
| if (itemCount >= 0) { // GROOVY-11090: Tuple[0-16] |
| if (itemCount != parameters.length) { |
| // for param count error messages |
| it.add(new ClassNode[itemCount]); |
| continue; |
| } |
| GenericsType[] spec = signature[0].getGenericsTypes(); |
| if (spec != null) { // edge case: Tuple0 falls through |
| signature = Arrays.stream(spec).map(GenericsType::getType).toArray(ClassNode[]::new); |
| candidates.add(signature); |
| continue; |
| } |
| } |
| ClassNode itemType = inferLoopElementType(signature[0]); |
| signature = new ClassNode[parameters.length]; |
| Arrays.fill(signature, itemType); |
| candidates.add(signature); |
| } |
| } |
| |
| if (candidates.isEmpty() && !closureSignatures.isEmpty()) { |
| String spec = closureSignatures.stream().mapToInt(sig -> sig.length).distinct() |
| .sorted().mapToObj(Integer::toString).collect(Collectors.joining(" or ")); |
| addError("Incorrect number of parameters. Expected " + spec + " but found " + parameters.length, expression); |
| } |
| |
| if (candidates.size() > 1) { |
| closureSignatures = new ArrayList<>(candidates); |
| for (Iterator<ClassNode[]> candIt = candidates.iterator(); candIt.hasNext(); ) { |
| ClassNode[] inferredTypes = candIt.next(); |
| for (int i = 0; i < inferredTypes.length; i += 1) { |
| ClassNode inferredType = inferredTypes[i], parameterType = parameters[i].getOriginType(); |
| if (parameterType.getGenericsTypes() != null) parameterType = GenericsUtils.nonGeneric(parameterType); |
| if (!typeCheckMethodArgumentWithGenerics(parameterType, inferredType, false)) { candIt.remove(); break; } |
| } |
| } |
| if (candidates.size() > 1 && resolverClass instanceof ClassExpression) { |
| candidates = resolveWithResolver(candidates, receiver, arguments, expression, selectedMethod, resolverClass, options); |
| } |
| if (candidates.isEmpty()) { |
| String actual = toMethodParametersString("", extractTypesFromParameters(parameters)); |
| String expect = closureSignatures.stream().map(sig -> toMethodParametersString("",sig)).collect(Collectors.joining(" or ")); |
| addStaticTypeError("Incorrect parameter type(s). Expected " + expect + " but found " + actual, expression); // at least one fails |
| } else if (candidates.size() > 1) { |
| addError("Ambiguous prototypes for closure. More than one target method matches. Please use explicit argument types.", expression); |
| } |
| } |
| |
| if (candidates.size() == 1) { |
| ClassNode[] inferredTypes = candidates.get(0); |
| if (inferredTypes.length == 1 && hasImplicitParameter(expression)) { |
| expression.putNodeMetaData(CLOSURE_ARGUMENTS, inferredTypes); |
| } else { |
| for (int i = 0; i < inferredTypes.length; i += 1) { |
| if (isDynamicTyped(inferredTypes[i])) continue; // GROOVY-6939 |
| checkParamType(parameters[i], inferredTypes[i], false, false); |
| typeCheckingContext.controlStructureVariables.put(parameters[i], inferredTypes[i]); |
| } |
| } |
| } |
| } |
| |
| private List<ClassNode[]> getSignaturesFromHint(final MethodNode method, final Expression hintType, final Expression options, final ASTNode usage) { |
| String hintTypeName = hintType.getText(); |
| try { |
| @SuppressWarnings("unchecked") |
| // load hint class using compiler's ClassLoader |
| Class<? extends ClosureSignatureHint> hintClass = (Class<? extends ClosureSignatureHint>) getTransformLoader().loadClass(hintTypeName); |
| ClosureSignatureHint hint = hintClass.getDeclaredConstructor().newInstance(); |
| return hint.getClosureSignatures( |
| method instanceof ExtensionMethodNode ? ((ExtensionMethodNode) method).getExtensionMethodNode() : method, |
| typeCheckingContext.getSource(), |
| typeCheckingContext.getCompilationUnit(), |
| convertToStringArray(options), |
| usage); |
| } catch (ReflectiveOperationException e) { |
| throw new GroovyBugError(e); |
| } |
| } |
| |
| private List<ClassNode[]> resolveWithResolver(final List<ClassNode[]> candidates, final ClassNode receiver, final Expression arguments, final ClosureExpression expression, final MethodNode method, final Expression resolverType, final Expression options) { |
| String resolverTypeName = resolverType.getText(); |
| try { |
| @SuppressWarnings("unchecked") |
| // load conflict resolver class using compiler's ClassLoader |
| Class<? extends ClosureSignatureConflictResolver> resolverClass = (Class<? extends ClosureSignatureConflictResolver>) getTransformLoader().loadClass(resolverTypeName); |
| ClosureSignatureConflictResolver resolver = resolverClass.getDeclaredConstructor().newInstance(); |
| return resolver.resolve( |
| candidates, |
| receiver, |
| arguments, |
| expression, |
| method instanceof ExtensionMethodNode ? ((ExtensionMethodNode) method).getExtensionMethodNode() : method, |
| typeCheckingContext.getSource(), |
| typeCheckingContext.getCompilationUnit(), |
| convertToStringArray(options)); |
| } catch (ReflectiveOperationException e) { |
| throw new GroovyBugError(e); |
| } |
| } |
| |
| private static String[] convertToStringArray(final Expression options) { |
| if (options == null) { |
| return ResolveVisitor.EMPTY_STRING_ARRAY; |
| } |
| if (options instanceof ConstantExpression) { |
| return new String[]{options.getText()}; |
| } |
| if (options instanceof ListExpression) { |
| return ((ListExpression) options).getExpressions().stream().map(Expression::getText).toArray(String[]::new); |
| } |
| throw new IllegalArgumentException("Unexpected options for @ClosureParams:" + options); |
| } |
| |
| private ClassLoader getTransformLoader() { |
| return Optional.ofNullable(typeCheckingContext.getCompilationUnit()).map(CompilationUnit::getTransformLoader).orElseGet(() -> getSourceUnit().getClassLoader()); |
| } |
| |
| /** |
| * Computes the inferred types of the closure parameters using the following trick: |
| * <ol> |
| * <li> creates a dummy MethodNode for which the return type is a class node |
| * for which the generic types are the types returned by the hint |
| * <li> calls {@link #inferReturnTypeGenerics} |
| * <li> returns inferred types from the result |
| * </ol> |
| * In practice it could be done differently but it has the main advantage of |
| * reusing existing code, hence reducing the amount of code to debug in case |
| * of failure. |
| */ |
| private void resolveGenericsFromTypeHint(final ClassNode receiver, final Expression arguments, final MethodNode selectedMethod, final ClassNode[] signature) { |
| ClassNode returnType = new ClassNode("ClForInference$" + UNIQUE_LONG.incrementAndGet(), 0, null).getPlainNodeReference(); |
| returnType.setGenericsTypes(Arrays.stream(signature).map(ClassNode::asGenericsType).toArray(GenericsType[]::new)); |
| |
| MethodNode methodNode = selectedMethod instanceof ExtensionMethodNode ? ((ExtensionMethodNode) selectedMethod).getExtensionMethodNode() : selectedMethod; |
| methodNode = new MethodNode("$$", methodNode.getModifiers(), returnType, methodNode.getParameters(), methodNode.getExceptions(), null); |
| methodNode.setDeclaringClass(selectedMethod.getDeclaringClass()); |
| methodNode.setGenericsTypes(selectedMethod.getGenericsTypes()); |
| if (selectedMethod instanceof ExtensionMethodNode) { |
| methodNode = new ExtensionMethodNode( |
| methodNode, |
| methodNode.getName(), |
| methodNode.getModifiers(), |
| returnType, |
| selectedMethod.getParameters(), |
| selectedMethod.getExceptions(), |
| null, |
| ((ExtensionMethodNode) selectedMethod).isStaticExtension() |
| ); |
| methodNode.setDeclaringClass(selectedMethod.getDeclaringClass()); |
| methodNode.setGenericsTypes(selectedMethod.getGenericsTypes()); |
| } |
| |
| GenericsType[] typeArguments = null; |
| Expression emc = typeCheckingContext.getEnclosingMethodCall(); // GROOVY-7789, GROOVY-11168 |
| if (emc instanceof MethodCallExpression) { MethodCallExpression call = (MethodCallExpression) emc; |
| if (arguments == call.getArguments() || InvocationWriter.makeArgumentList(arguments).getExpressions().stream().anyMatch(arg -> |
| arg instanceof ClosureExpression && DefaultGroovyMethods.contains(InvocationWriter.makeArgumentList(call.getArguments()), arg))) |
| typeArguments = call.getGenericsTypes(); |
| } |
| |
| returnType = inferReturnTypeGenerics(receiver, methodNode, arguments, typeArguments); |
| GenericsType[] returnTypeGenerics = returnType.getGenericsTypes(); |
| for (int i = 0, n = returnTypeGenerics.length; i < n; i += 1) { |
| GenericsType gt = returnTypeGenerics[i]; |
| if (gt.isPlaceholder()) { // GROOVY-9968, GROOVY-10528, et al. |
| signature[i] = gt.getUpperBounds() != null ? gt.getUpperBounds()[0] : gt.getType().redirect(); |
| } else { |
| signature[i] = getCombinedBoundType(gt); |
| } |
| } |
| } |
| |
| private void checkClosureWithDelegatesTo(final ClassNode receiver, final MethodNode mn, final ArgumentListExpression arguments, final Parameter[] params, final Expression expression, final Parameter param) { |
| List<AnnotationNode> annotations = param.getAnnotations(DELEGATES_TO); |
| if (annotations != null && !annotations.isEmpty()) { |
| for (AnnotationNode annotation : annotations) { |
| // in theory, there can only be one annotation of that type |
| Expression value = annotation.getMember("value"); |
| Expression strategy = annotation.getMember("strategy"); |
| Expression genericTypeIndex = annotation.getMember("genericTypeIndex"); |
| Expression type = annotation.getMember("type"); |
| Integer stInt = Closure.OWNER_FIRST; |
| if (strategy != null) { |
| stInt = (Integer) evaluateExpression(castX(Integer_TYPE, strategy), getSourceUnit().getConfiguration(), getSourceUnit().getClassLoader()); |
| } |
| if (value instanceof ClassExpression && !value.getType().equals(DELEGATES_TO_TARGET)) { |
| if (genericTypeIndex != null) { |
| addStaticTypeError("Cannot use @DelegatesTo(genericTypeIndex=" + genericTypeIndex.getText() |
| + ") without @DelegatesTo.Target because generic argument types are not available at runtime", value); |
| } |
| // temporarily store the delegation strategy and the delegate type |
| expression.putNodeMetaData(DELEGATION_METADATA, newDelegationMetadata(value.getType(), stInt)); |
| } else if (type != null && !"".equals(type.getText()) && type instanceof ConstantExpression) { |
| String typeString = type.getText(); |
| ClassNode[] resolved = GenericsUtils.parseClassNodesFromString( |
| typeString, |
| getSourceUnit(), |
| typeCheckingContext.getCompilationUnit(), |
| mn, |
| type |
| ); |
| if (resolved != null) { |
| if (resolved.length == 1) { |
| resolveGenericsFromTypeHint(receiver, arguments, mn, resolved); |
| expression.putNodeMetaData(DELEGATION_METADATA, newDelegationMetadata(resolved[0], stInt)); |
| } else { |
| addStaticTypeError("Incorrect type hint found in method " + (mn), type); |
| } |
| } |
| } else { |
| List<Expression> expressions = arguments.getExpressions(); |
| int expressionsSize = expressions.size(); |
| Expression parameter = annotation.getMember("target"); |
| String parameterName = parameter instanceof ConstantExpression ? parameter.getText() : ""; |
| // todo: handle vargs! |
| for (int j = 0, paramsLength = params.length; j < paramsLength; j++) { |
| Parameter methodParam = params[j]; |
| List<AnnotationNode> targets = methodParam.getAnnotations(DELEGATES_TO_TARGET); |
| if (targets != null && targets.size() == 1) { |
| AnnotationNode targetAnnotation = targets.get(0); // @DelegatesTo.Target Obj foo |
| Expression idMember = targetAnnotation.getMember("value"); |
| String id = idMember instanceof ConstantExpression ? idMember.getText() : ""; |
| if (id.equals(parameterName)) { |
| if (j < expressionsSize) { |
| Expression actualArgument = expressions.get(j); |
| ClassNode actualType = getType(actualArgument); |
| if (genericTypeIndex instanceof ConstantExpression) { |
| int gti = Integer.parseInt(genericTypeIndex.getText()); |
| ClassNode paramType = methodParam.getType(); // type annotated with @DelegatesTo.Target |
| GenericsType[] genericsTypes = paramType.getGenericsTypes(); |
| if (genericsTypes == null) { |
| addStaticTypeError("Cannot use @DelegatesTo(genericTypeIndex=" + genericTypeIndex.getText() |
| + ") with a type that doesn't use generics", methodParam); |
| } else if (gti < 0 || gti >= genericsTypes.length) { |
| addStaticTypeError("Index of generic type @DelegatesTo(genericTypeIndex=" + genericTypeIndex.getText() |
| + ") " + (gti < 0 ? "lower" : "greater") + " than those of the selected type", methodParam); |
| } else { |
| ClassNode pType = GenericsUtils.parameterizeType(actualType, paramType); |
| GenericsType[] pTypeGenerics = pType.getGenericsTypes(); |
| if (pTypeGenerics != null && pTypeGenerics.length > gti) { |
| actualType = pTypeGenerics[gti].getType(); |
| } else { |
| addStaticTypeError("Unable to map actual type [" + prettyPrintType(actualType) + "] onto " + prettyPrintType(paramType), methodParam); |
| } |
| } |
| } |
| expression.putNodeMetaData(DELEGATION_METADATA, newDelegationMetadata(actualType, stInt)); |
| break; |
| } |
| } |
| } |
| } |
| if (expression.getNodeMetaData(DELEGATION_METADATA) == null) { |
| addError("Not enough arguments found for a @DelegatesTo method call. Please check that you either use an explicit class or @DelegatesTo.Target with a correct id", annotation); |
| } |
| } |
| } |
| } |
| } |
| |
| protected void addReceivers(final List<Receiver<String>> receivers, final Collection<Receiver<String>> owners, final boolean implicitThis) { |
| if (!implicitThis || typeCheckingContext.delegationMetadata == null) { |
| receivers.addAll(owners); |
| } else { |
| addReceivers(receivers, owners, typeCheckingContext.delegationMetadata, ""); |
| } |
| } |
| |
| private static void addReceivers(final List<Receiver<String>> receivers, final Collection<Receiver<String>> owners, final DelegationMetadata dmd, final String path) { |
| int strategy = dmd.getStrategy(); |
| switch (strategy) { |
| case Closure.DELEGATE_ONLY: |
| case Closure.DELEGATE_FIRST: |
| addDelegateReceiver(receivers, dmd.getType(), path + "delegate"); |
| if (strategy == Closure.DELEGATE_FIRST) { |
| if (dmd.getParent() == null) { |
| receivers.addAll(owners); |
| } else { |
| //receivers.add(new Receiver<String>(CLOSURE_TYPE, path + "owner")); |
| addReceivers(receivers, owners, dmd.getParent(), path + "owner."); |
| } |
| } |
| break; |
| case Closure.OWNER_ONLY: |
| case Closure.OWNER_FIRST: |
| if (dmd.getParent() == null) { |
| receivers.addAll(owners); |
| } else { |
| //receivers.add(new Receiver<String>(CLOSURE_TYPE, path + "owner")); |
| addReceivers(receivers, owners, dmd.getParent(), path + "owner."); |
| } |
| if (strategy == Closure.OWNER_FIRST) { |
| addDelegateReceiver(receivers, dmd.getType(), path + "delegate"); |
| } |
| break; |
| } |
| } |
| |
| private static void addDelegateReceiver(final List<Receiver<String>> receivers, final ClassNode delegate, final String path) { |
| if (isClassClassNodeWrappingConcreteType(delegate)) { // add Type from Class<Type> |
| addDelegateReceiver(receivers, delegate.getGenericsTypes()[0].getType(), path); |
| } |
| if (receivers.stream().map(Receiver::getType).noneMatch(delegate::equals)) { |
| receivers.add(new Receiver<>(delegate, path)); |
| } |
| } |
| |
| @Override |
| public void visitMethodCallExpression(final MethodCallExpression call) { |
| String name = call.getMethodAsString(); |
| if (name == null) { |
| addStaticTypeError("Cannot resolve dynamic method name at compile time", call.getMethod()); |
| return; |
| } |
| if (extension.beforeMethodCall(call)) { |
| extension.afterMethodCall(call); |
| return; |
| } |
| typeCheckingContext.pushEnclosingMethodCall(call); |
| Expression objectExpression = call.getObjectExpression(); |
| |
| objectExpression.visit(this); |
| call.getMethod().visit(this); |
| |
| ClassNode receiver = getType(objectExpression); |
| if (objectExpression instanceof ConstructorCallExpression) { // GROOVY-10228 |
| inferDiamondType((ConstructorCallExpression) objectExpression, receiver.getPlainNodeReference()); |
| } |
| if (call.isSpreadSafe()) { |
| ClassNode componentType = inferComponentType(receiver, null); |
| if (componentType == null) { |
| addStaticTypeError("Spread-dot operator can only be used on iterable types", objectExpression); |
| } else { |
| MethodCallExpression subcall = callX(varX("item", componentType), name, call.getArguments()); |
| subcall.setLineNumber(call.getLineNumber()); subcall.setColumnNumber(call.getColumnNumber()); |
| subcall.setImplicitThis(call.isImplicitThis()); |
| visitMethodCallExpression(subcall); |
| // inferred type should be a list of what sub-call returns |
| storeType(call, extension.buildListType(getType(subcall))); |
| storeTargetMethod(call, subcall.getNodeMetaData(DIRECT_METHOD_CALL_TARGET)); |
| } |
| typeCheckingContext.popEnclosingMethodCall(); |
| return; |
| } |
| |
| Expression callArguments = call.getArguments(); |
| ArgumentListExpression argumentList = InvocationWriter.makeArgumentList(callArguments); |
| |
| // visit functional arguments *after* target method selection |
| visitMethodCallArguments(receiver, argumentList, false, null); |
| |
| boolean isThisObjectExpression = isThisExpression(objectExpression); |
| boolean isCallOnClosure = false; |
| FieldNode fieldNode = null; |
| switch (name) { |
| case "call": |
| case "doCall": |
| if (!isThisObjectExpression) { |
| isCallOnClosure = receiver.equals(CLOSURE_TYPE); |
| break; |
| } |
| default: |
| if (isThisObjectExpression) { |
| ClassNode enclosingType = typeCheckingContext.getEnclosingClassNode(); |
| fieldNode = enclosingType.getDeclaredField(name); |
| if (fieldNode != null && getType(fieldNode).equals(CLOSURE_TYPE) |
| && !enclosingType.hasPossibleMethod(name, callArguments)) { |
| isCallOnClosure = true; |
| } |
| } |
| } |
| |
| try { |
| ClassNode[] args = getArgumentTypes(argumentList); |
| boolean functorsVisited = false; |
| if (isCallOnClosure) { |
| if (fieldNode != null) { |
| GenericsType[] genericsTypes = getType(fieldNode).getGenericsTypes(); |
| if (genericsTypes != null) { |
| Parameter[] parameters = fieldNode.getNodeMetaData(CLOSURE_ARGUMENTS); |
| if (parameters != null) { |
| typeCheckClosureCall(callArguments, args, parameters); |
| } |
| ClassNode closureReturnType = genericsTypes[0].getType(); |
| storeType(call, closureReturnType); |
| } |
| } else if (objectExpression instanceof VariableExpression) { |
| Variable variable = findTargetVariable((VariableExpression) objectExpression); |
| if (variable instanceof ASTNode) { |
| Parameter[] parameters = ((ASTNode) variable).getNodeMetaData(CLOSURE_ARGUMENTS); |
| if (parameters != null) { |
| checkForbiddenSpreadArgument(argumentList, parameters); |
| typeCheckClosureCall(callArguments, args, parameters); |
| } |
| ClassNode type = getType((ASTNode) variable); |
| if (type.equals(CLOSURE_TYPE)) { // GROOVY-10098, et al. |
| GenericsType[] genericsTypes = type.getGenericsTypes(); |
| if (genericsTypes != null && genericsTypes.length == 1 |
| && genericsTypes[0].getLowerBound() == null) { |
| type = genericsTypes[0].getType(); |
| } else { |
| type = OBJECT_TYPE; |
| } |
| } |
| if (type != null) { |
| storeType(call, type); |
| } |
| } |
| } else if (objectExpression instanceof ClosureExpression) { |
| // we can get actual parameters directly |
| Parameter[] parameters = ((ClosureExpression) objectExpression).getParameters(); |
| if (parameters != null) { |
| typeCheckClosureCall(callArguments, args, parameters); |
| } |
| ClassNode type = getInferredReturnType(objectExpression); |
| if (type != null) { |
| storeType(call, type); |
| } |
| } |
| |
| List<Expression> list = argumentList.getExpressions(); |
| int nArgs = list.stream().noneMatch(e -> e instanceof SpreadExpression) ? list.size() : Integer.MAX_VALUE; |
| storeTargetMethod(call, nArgs == 0 ? CLOSURE_CALL_NO_ARG : nArgs == 1 ? CLOSURE_CALL_ONE_ARG : CLOSURE_CALL_VARGS); |
| } else { |
| // method call receivers are : |
| // - possible "with" receivers |
| // - the actual receiver as found in the method call expression |
| // - any of the potential receivers found in the instanceof temporary table |
| // in that order |
| List<Receiver<String>> receivers = new ArrayList<>(); |
| addReceivers(receivers, makeOwnerList(objectExpression), call.isImplicitThis()); |
| |
| MethodNode first = null; |
| List<MethodNode> mn = null; |
| Receiver<String> chosenReceiver = null; |
| for (Receiver<String> currentReceiver : receivers) { |
| mn = findMethod(currentReceiver.getType().getPlainNodeReference(), name, args); |
| if (!mn.isEmpty()) { |
| first = mn.get(0); // capture for error string |
| // for "this" in a static context, only static methods are compatible |
| if (currentReceiver.getData() == null && !isClassType(currentReceiver.getType())) { |
| boolean staticThis = (isThisObjectExpression || call.isImplicitThis()) && typeCheckingContext.isInStaticContext; |
| boolean staticThat = isClassClassNodeWrappingConcreteType(receiver); // GROOVY-10819, GROOVY-10820 |
| mn = allowStaticAccessToMember(mn, staticThis || staticThat); |
| } |
| } |
| if (!mn.isEmpty()) { |
| chosenReceiver = currentReceiver; |
| break; |
| } |
| } |
| if (mn.isEmpty() && isThisObjectExpression && call.isImplicitThis() && typeCheckingContext.getEnclosingClosure() != null) { |
| mn = CLOSURE_TYPE.getDeclaredMethods(name); |
| if (!mn.isEmpty()) { |
| objectExpression.removeNodeMetaData(INFERRED_TYPE); |
| } |
| } |
| if (mn.isEmpty()) { |
| mn = extension.handleMissingMethod(receiver, name, argumentList, args, call); |
| if (mn.isEmpty() && first != null) mn.add(first); // non-static method error? |
| } |
| if (mn.isEmpty()) { |
| addNoMatchingMethodError(receiver, name, args, call); |
| } else { |
| if (areCategoryMethodCalls(mn, name, args)) addCategoryMethodCallError(call); |
| |
| { |
| ClassNode obj = chosenReceiver != null ? chosenReceiver.getType() : null; |
| if (mn.size() > 1 && obj instanceof UnionTypeClassNode) { // GROOVY-8965: support duck-typing using dynamic resolution |
| ClassNode returnType = mn.stream().map(MethodNode::getReturnType).reduce(WideningCategories::lowestUpperBound).get(); |
| call.putNodeMetaData(DYNAMIC_RESOLUTION, returnType); |
| |
| MethodNode tmp = new MethodNode(name, 0, returnType, Parameter.EMPTY_ARRAY, ClassNode.EMPTY_ARRAY, null); |
| tmp.setDeclaringClass(obj); // for storeTargetMethod |
| mn = Collections.singletonList(tmp); |
| } else { |
| mn = disambiguateMethods(mn, obj, args, call); |
| } |
| } |
| |
| out: if (mn.size() != 1) { |
| addAmbiguousErrorMessage(mn, name, args, call); |
| } else { |
| MethodNode targetMethod = mn.get(0); |
| // note second pass to differentiate from extension that sets type |
| boolean mergeType = (call.getNodeMetaData(INFERRED_TYPE) != null); |
| storeTargetMethod(call, targetMethod); // trigger onMethodSelection |
| if (call.getNodeMetaData(DIRECT_METHOD_CALL_TARGET) != targetMethod) { |
| break out; // type-checking script intervention |
| } |
| ClassNode declaringClass = targetMethod.getDeclaringClass(); |
| if (chosenReceiver == null) { |
| chosenReceiver = Receiver.make(declaringClass.getPlainNodeReference()); |
| } |
| if (!targetMethod.isStatic() && !(isClassType(declaringClass) || isObjectType(declaringClass)) // GROOVY-10939: Class or Object |
| && isClassType(receiver) && chosenReceiver.getData() == null && !Boolean.TRUE.equals(call.getNodeMetaData(DYNAMIC_RESOLUTION))) { |
| addStaticTypeError("Non-static method " + prettyPrintTypeName(declaringClass) + "#" + targetMethod.getName() + " cannot be called from static context", call); |
| } else if ((chosenReceiver.getType().isInterface() || targetMethod.isAbstract()) && isSuperExpression(objectExpression)) { // GROOVY-8299, GROOVY-10341 |
| String target = toMethodParametersString(targetMethod.getName(), extractTypesFromParameters(targetMethod.getParameters())); |
| if (targetMethod.isDefault() || Traits.hasDefaultImplementation(targetMethod)) { // GROOVY-10494 |
| if (objectExpression instanceof VariableExpression) |
| addStaticTypeError("Default method " + target + " requires qualified super", call); |
| } else { |
| addStaticTypeError("Abstract method " + target + " cannot be called directly", call); |
| } |
| } |
| |
| visitMethodCallArguments(chosenReceiver.getType(), argumentList, true, targetMethod); functorsVisited = true; |
| |
| ClassNode returnType = getType(targetMethod); |
| if (isUsingGenericsOrIsArrayUsingGenerics(returnType)) { |
| ClassNode irtg = inferReturnTypeGenerics(chosenReceiver.getType(), targetMethod, callArguments, call.getGenericsTypes()); |
| if (irtg != null && implementsInterfaceOrIsSubclassOf(irtg, returnType)) |
| returnType = irtg; |
| } |
| // GROOVY-6091: use of "delegate" or "getDelegate()" does not make use of @DelegatesTo metadata |
| if (targetMethod == GET_DELEGATE && typeCheckingContext.getEnclosingClosure() != null) { |
| DelegationMetadata md = getDelegationMetadata(typeCheckingContext.getEnclosingClosure().getClosureExpression()); |
| if (md != null) { |
| returnType = md.getType(); |
| } else { |
| returnType = typeCheckingContext.getEnclosingClassNode(); |
| } |
| } |
| // GROOVY-7106, GROOVY-7274, GROOVY-8909, GROOVY-8961, GROOVY-9734, GROOVY-9844, GROOVY-9915, et al. |
| Parameter[] parameters = targetMethod.getParameters(); |
| if (chosenReceiver.getType().getGenericsTypes() != null && !targetMethod.isStatic() && !(targetMethod instanceof ExtensionMethodNode)) { |
| Map<GenericsTypeName, GenericsType> context = extractPlaceHoldersVisibleToDeclaration(chosenReceiver.getType(), targetMethod, argumentList); |
| parameters = Arrays.stream(parameters).map(p -> new Parameter(applyGenericsContext(context, p.getType()), p.getName())).toArray(Parameter[]::new); |
| } |
| resolvePlaceholdersFromImplicitTypeHints(args, argumentList, parameters); |
| |
| if (typeCheckMethodsWithGenericsOrFail(chosenReceiver.getType(), args, targetMethod, call)) { |
| String data = chosenReceiver.getData(); |
| if (data != null) { |
| // the method which has been chosen is supposed to be a call on delegate or owner |
| // so we store the information so that the static compiler may reuse it |
| call.putNodeMetaData(IMPLICIT_RECEIVER, data); |
| } |
| receiver = chosenReceiver.getType(); |
| if (mergeType || call.getNodeMetaData(INFERRED_TYPE) == null) |
| storeType(call, adjustWithTraits(targetMethod, receiver, args, returnType)); |
| |
| if (objectExpression instanceof VariableExpression && ((VariableExpression) objectExpression).isClosureSharedVariable()) { |
| // if the object expression is a closure shared variable, we will have to perform a second pass |
| typeCheckingContext.secondPassExpressions.add(new SecondPassExpression<>(call, args)); |
| } |
| } else { |
| call.removeNodeMetaData(DIRECT_METHOD_CALL_TARGET); |
| } |
| } |
| } |
| } |
| // adjust typing for explicit math methods which have special handling; operator variants handled elsewhere |
| if (args.length == 1 && isNumberType(args[0]) && isNumberType(receiver) && NUMBER_OPS.containsKey(name)) { |
| ClassNode resultType = getMathResultType(NUMBER_OPS.get(name), receiver, args[0], name); |
| if (resultType != null) { |
| storeType(call, resultType); |
| } |
| } |
| |
| // type-checking extension may have changed or cleared target method |
| MethodNode target = call.getNodeMetaData(DIRECT_METHOD_CALL_TARGET); |
| if (!functorsVisited) { |
| visitMethodCallArguments(receiver, argumentList, true, target); |
| } |
| if (target != null) { Parameter[] params = target.getParameters(); |
| checkClosureMetadata(argumentList.getExpressions(), params); |
| checkForbiddenSpreadArgument(argumentList, params); |
| } else { |
| checkForbiddenSpreadArgument(argumentList); |
| } |
| } finally { |
| typeCheckingContext.popEnclosingMethodCall(); |
| extension.afterMethodCall(call); |
| } |
| } |
| |
| private void checkClosureMetadata(final List<Expression> arguments, final Parameter[] parameters) { |
| // TODO: Check additional delegation metadata like type (see checkClosureWithDelegatesTo). |
| for (int i = 0, n = Math.min(arguments.size(), parameters.length); i < n; i += 1) { |
| Expression argument = arguments.get(i); |
| ClassNode aType = getType(argument), pType = parameters[i].getType(); |
| // GROOVY-7996: check delegation metadata of closure parameter used as method call argument |
| if (CLOSURE_TYPE.equals(aType) && CLOSURE_TYPE.equals(pType) && argument instanceof VariableExpression && ((VariableExpression) argument).getAccessedVariable() instanceof Parameter) { |
| int incomingStrategy = getResolveStrategy((Parameter) ((VariableExpression) argument).getAccessedVariable()); |
| int outgoingStrategy = getResolveStrategy(parameters[i]); |
| if (incomingStrategy != outgoingStrategy) { |
| addStaticTypeError("Closure parameter with resolve strategy " + getResolveStrategyName(incomingStrategy) + " passed to method with resolve strategy " + getResolveStrategyName(outgoingStrategy), argument); |
| } |
| } |
| } |
| } |
| |
| private int getResolveStrategy(final Parameter parameter) { |
| List<AnnotationNode> annotations = parameter.getAnnotations(DELEGATES_TO); |
| if (annotations != null && !annotations.isEmpty()) { |
| Expression strategy = annotations.get(0).getMember("strategy"); |
| if (strategy != null) { |
| return (Integer) evaluateExpression(castX(Integer_TYPE, strategy), getSourceUnit().getConfiguration(), getSourceUnit().getClassLoader()); |
| } |
| } |
| return Closure.OWNER_FIRST; |
| } |
| |
| /** |
| * A special method handling the "withTraits" call for which the type checker |
| * knows more than what the type signature is able to tell. If "withTraits" |
| * is detected, then a new class node is created representing the receiver |
| * type interfaces and the trait interface(s). |
| * |
| * @param directMethodCallCandidate a method selected by the type checker |
| * @param receiver the receiver of the method call |
| * @param argumentTypes the argument types of the method call |
| * @param returnType the return type as inferred by the type checker |
| * @return proxy return type if the selected method is {@link org.codehaus.groovy.runtime.DefaultGroovyMethods#withTraits(Object, Class[]) withTraits} |
| */ |
| private static ClassNode adjustWithTraits(final MethodNode directMethodCallCandidate, final ClassNode receiver, final ClassNode[] argumentTypes, final ClassNode returnType) { |
| if ("withTraits".equals(directMethodCallCandidate.getName()) && isDefaultExtension(directMethodCallCandidate)) { |
| List<ClassNode> interfaces = new ArrayList<>(Arrays.asList(receiver.getInterfaces())); |
| for (ClassNode argumentType : argumentTypes) { |
| if (isClassClassNodeWrappingConcreteType(argumentType)) { |
| argumentType = argumentType.getGenericsTypes()[0].getType(); |
| } |
| if (argumentType.isInterface()) { |
| interfaces.add(argumentType); |
| } |
| } |
| return new WideningCategories.LowestUpperBoundClassNode("ProxyOf$" + receiver.getNameWithoutPackage(), OBJECT_TYPE, interfaces.toArray(ClassNode.EMPTY_ARRAY)); |
| } |
| return returnType; |
| } |
| |
| /** |
| * In the case of a <em>Object.with { ... }</em> call, this method is supposed to retrieve |
| * the inferred closure return type. |
| * |
| * @param callArguments the argument list from the <em>Object#with(Closure)</em> call, i.e. a single closure expression |
| * @return the inferred closure return type or <em>null</em> |
| */ |
| protected ClassNode getInferredReturnTypeFromWithClosureArgument(final Expression callArguments) { |
| if (!(callArguments instanceof ArgumentListExpression)) return null; |
| |
| ArgumentListExpression argList = (ArgumentListExpression) callArguments; |
| ClosureExpression closure = (ClosureExpression) argList.getExpression(0); |
| |
| visitClosureExpression(closure); |
| |
| return getInferredReturnType(closure); |
| } |
| |
| /** |
| * Given an object expression (a message receiver expression), generate list |
| * of possible types. |
| * |
| * @param objectExpression the receiver expression |
| * @return the list of types the receiver may be |
| */ |
| protected List<Receiver<String>> makeOwnerList(final Expression objectExpression) { |
| ClassNode receiver = getType(objectExpression); |
| List<Receiver<String>> owners = new ArrayList<>(); |
| if (typeCheckingContext.delegationMetadata != null |
| && objectExpression instanceof VariableExpression |
| && ((Variable) objectExpression).getName().equals("owner") |
| && /*isNested:*/typeCheckingContext.delegationMetadata.getParent() != null) { |
| List<Receiver<String>> enclosingClass = Collections.singletonList( |
| Receiver.make(typeCheckingContext.getEnclosingClassNode())); |
| addReceivers(owners, enclosingClass, typeCheckingContext.delegationMetadata.getParent(), "owner."); |
| } else { |
| List<ClassNode> temporaryTypes = getTemporaryTypesForExpression(objectExpression); |
| int temporaryTypesCount = (temporaryTypes != null ? temporaryTypes.size() : 0); |
| if (temporaryTypesCount > 0) { // GROOVY-8965, GROOVY-10180, GROOVY-10668 |
| owners.add(Receiver.make(lowestUpperBound(temporaryTypes))); |
| } |
| if (typeCheckingContext.lastImplicitItType != null |
| && objectExpression instanceof VariableExpression |
| && ((Variable) objectExpression).getName().equals("it")) { |
| owners.add(Receiver.make(typeCheckingContext.lastImplicitItType)); |
| } |
| if (isClassClassNodeWrappingConcreteType(receiver)) { |
| ClassNode staticType = receiver.getGenericsTypes()[0].getType(); |
| owners.add(Receiver.make(staticType)); // Type from Class<Type> |
| addTraitType(staticType, owners); // T in Class<T$Trait$Helper> |
| owners.add(Receiver.make(receiver)); // Class<Type> |
| } else { |
| addBoundType(receiver, owners); |
| addSelfTypes(receiver, owners); |
| addTraitType(receiver, owners); |
| if (receiver.redirect().isInterface()) { |
| owners.add(Receiver.make(OBJECT_TYPE)); |
| } else if (isSuperExpression(objectExpression)) { //GROOVY-9909: super.defaultMethod() |
| for (ClassNode in : typeCheckingContext.getEnclosingClassNode().getInterfaces()) { |
| if (!receiver.implementsInterface(in)) owners.add(Receiver.make(in)); |
| } |
| } |
| } |
| if (temporaryTypesCount > 1 && !(objectExpression instanceof VariableExpression)) { |
| owners.add(Receiver.make(new UnionTypeClassNode(temporaryTypes.toArray(ClassNode.EMPTY_ARRAY)))); |
| } |
| } |
| return owners; |
| } |
| |
| private static void addBoundType(final ClassNode receiver, final List<Receiver<String>> owners) { |
| if (!receiver.isGenericsPlaceHolder() || receiver.getGenericsTypes() == null) { |
| owners.add(Receiver.make(receiver)); |
| return; |
| } |
| |
| GenericsType gt = receiver.getGenericsTypes()[0]; |
| if (gt.getLowerBound() == null && gt.getUpperBounds() != null) { |
| for (ClassNode cn : gt.getUpperBounds()) { // T extends C & I |
| addBoundType(cn, owners); |
| addSelfTypes(cn, owners); |
| } |
| } else { |
| ClassNode cn = gt.getType().redirect(); // GROOVY-10846 |
| owners.add(Receiver.make(cn)); // T or T super Type |
| } |
| } |
| |
| private static void addSelfTypes(final ClassNode receiver, final List<Receiver<String>> owners) { |
| for (ClassNode selfType : Traits.collectSelfTypes(receiver, new LinkedHashSet<>())) { |
| owners.add(Receiver.make(selfType)); |
| } |
| } |
| |
| private static void addTraitType(final ClassNode receiver, final List<Receiver<String>> owners) { |
| if (Traits.isTrait(receiver.getOuterClass()) && receiver.getName().endsWith("$Helper")) { |
| ClassNode traitType = receiver.getOuterClass(); |
| owners.add(Receiver.make(traitType)); |
| addSelfTypes(traitType, owners); |
| } |
| } |
| |
| private void checkForbiddenSpreadArgument(final ArgumentListExpression arguments, final Parameter[] parameters) { |
| if (!isVargs(parameters)) { |
| checkForbiddenSpreadArgument(arguments); |
| } else { // GROOVY-10597: allow spread for the ... param |
| List<Expression> list = arguments.getExpressions(); |
| list = list.subList(0, parameters.length - 1); |
| checkForbiddenSpreadArgument(args(list)); |
| } |
| } |
| |
| protected void checkForbiddenSpreadArgument(final ArgumentListExpression arguments) { |
| for (Expression argument : arguments) { |
| if (argument instanceof SpreadExpression) { |
| addStaticTypeError("The spread operator cannot be used as argument of method or closure calls with static type checking because the number of arguments cannot be determined at compile time", argument); |
| } |
| } |
| } |
| |
| protected void storeTargetMethod(final Expression call, final MethodNode target) { |
| if (target == null) { |
| call.removeNodeMetaData(DIRECT_METHOD_CALL_TARGET); return; |
| } |
| call.putNodeMetaData(DIRECT_METHOD_CALL_TARGET,target); |
| |
| checkInterfaceStaticCall(call, target); |
| checkOrMarkPrivateAccess(call, target); |
| checkSuperCallFromClosure(call, target); |
| extension.onMethodSelection(call, target); |
| } |
| |
| private void checkInterfaceStaticCall(final Expression call, final MethodNode target) { |
| if (target instanceof ExtensionMethodNode) return; |
| ClassNode declaringClass = target.getDeclaringClass(); |
| if (declaringClass.isInterface() && target.isStatic()) { |
| Expression objectExpression = getObjectExpression(call); |
| if (objectExpression != null) { ClassNode type = getType(objectExpression); |
| if (!isClassClassNodeWrappingConcreteType(type) || !type.getGenericsTypes()[0].getType().equals(declaringClass)) { |
| addStaticTypeError("static method of interface " + prettyPrintTypeName(declaringClass) + " can only be accessed with class qualifier", call); |
| } |
| } |
| } |
| } |
| |
| private void checkSuperCallFromClosure(final Expression call, final MethodNode target) { |
| if (isSuperExpression(getObjectExpression(call)) && typeCheckingContext.getEnclosingClosure() != null) { |
| ClassNode current = typeCheckingContext.getEnclosingClassNode(); |
| current.getNodeMetaData(SUPER_MOP_METHOD_REQUIRED, x -> new LinkedList<>()).add(target); |
| call.putNodeMetaData(SUPER_MOP_METHOD_REQUIRED, current); |
| } |
| } |
| |
| private static Expression getObjectExpression(final Expression expression) { |
| if (expression instanceof MethodCallExpression) { |
| return ((MethodCallExpression) expression).getObjectExpression(); |
| } |
| if (expression instanceof PropertyExpression) { |
| return ((PropertyExpression) expression).getObjectExpression(); |
| } |
| /*if (expression instanceof MethodPointerExpression) { |
| return ((MethodPointerExpression) expression).getExpression(); |
| }*/ |
| return null; |
| } |
| |
| protected void typeCheckClosureCall(final Expression arguments, final ClassNode[] argumentTypes, final Parameter[] parameters) { |
| if (allParametersAndArgumentsMatchWithDefaultParams(parameters, argumentTypes) < 0 && lastArgMatchesVarg(parameters, argumentTypes) < 0) { |
| addStaticTypeError("Cannot call closure that accepts " + formatArgumentList(extractTypesFromParameters(parameters)) + " with " + formatArgumentList(argumentTypes), arguments); |
| } |
| } |
| |
| @Override |
| public void visitIfElse(final IfStatement ifElse) { |
| Map<VariableExpression, List<ClassNode>> oldTracker = pushAssignmentTracking(); |
| try { |
| Statement thenPath = ifElse.getIfBlock(), elsePath = ifElse.getElseBlock(); |
| |
| // create a new scope for instanceof testing |
| typeCheckingContext.pushTemporaryTypeInfo(); |
| visitStatement(ifElse); |
| ifElse.getBooleanExpression().visit(this); |
| |
| thenPath.visit(this); |
| |
| Map<Object, List<ClassNode>> tti = typeCheckingContext.temporaryIfBranchTypeInformation.pop(); |
| // GROOVY-6099: isolate assignment tracking |
| restoreTypeBeforeConditional(); |
| |
| // GROOVY-6429: reverse instanceof tracking |
| typeCheckingContext.pushTemporaryTypeInfo(); |
| tti.forEach(this::putNotInstanceOfTypeInfo); |
| |
| elsePath.visit(this); |
| |
| typeCheckingContext.popTemporaryTypeInfo(); |
| // GROOVY-8523: propagate tracking to outer scope; keep simple for now |
| if (elsePath.isEmpty() && !GeneralUtils.maybeFallsThrough(thenPath)) { |
| tti.forEach(this::putNotInstanceOfTypeInfo); |
| } |
| // GROOVY-9786: if chaining: "if (...) x=?; else if (...) x=?;" |
| Map<VariableExpression, ClassNode> updates = elsePath.getNodeMetaData("assignments"); |
| if (updates != null) { |
| updates.forEach(this::recordAssignment); |
| } |
| } finally { |
| ifElse.putNodeMetaData("assignments", popAssignmentTracking(oldTracker)); |
| } |
| } |
| |
| @Override |
| public void visitSwitch(final SwitchStatement statement) { |
| typeCheckingContext.pushEnclosingSwitchStatement(statement); |
| try { |
| Map<VariableExpression, List<ClassNode>> oldTracker = pushAssignmentTracking(); |
| try { |
| super.visitSwitch(statement); |
| } finally { |
| popAssignmentTracking(oldTracker); |
| } |
| } finally { |
| typeCheckingContext.popTemporaryTypeInfo(); |
| typeCheckingContext.popEnclosingSwitchStatement(); |
| } |
| } |
| |
| @Override |
| protected void afterSwitchConditionExpressionVisited(final SwitchStatement statement) { |
| typeCheckingContext.pushTemporaryTypeInfo(); |
| Expression conditionExpression = statement.getExpression(); |
| conditionExpression.putNodeMetaData(TYPE, getType(conditionExpression)); |
| } |
| |
| @Override |
| protected void afterSwitchCaseStatementsVisited(final SwitchStatement statement) { |
| // GROOVY-8411: if any "case Type:" then "default:" contributes condition type |
| if (!statement.getDefaultStatement().isEmpty() && !typeCheckingContext.temporaryIfBranchTypeInformation.peek().isEmpty()) |
| pushInstanceOfTypeInfo(statement.getExpression(), new ClassExpression(statement.getExpression().getNodeMetaData(TYPE))); |
| } |
| |
| @Override |
| public void visitCaseStatement(final CaseStatement statement) { |
| Expression selectable = typeCheckingContext.getEnclosingSwitchStatement().getExpression(); |
| Expression expression = statement.getExpression(); |
| if (expression instanceof ClassExpression) { // GROOVY-8411: refine the switch type |
| pushInstanceOfTypeInfo(selectable, expression); |
| } else if (expression instanceof ClosureExpression) { // GROOVY-9854: propagate the switch type |
| ClassNode inf = selectable.getNodeMetaData(TYPE); |
| expression.putNodeMetaData(CLOSURE_ARGUMENTS, new ClassNode[]{inf}); |
| Parameter[] params = ((ClosureExpression) expression).getParameters(); |
| if (params != null && params.length == 1) { |
| boolean lambda = (expression instanceof LambdaExpression); |
| checkParamType(params[0], wrapTypeIfNecessary(inf), false, lambda); |
| } else if (params == null || params.length > 1) { |
| int paramCount = (params != null ? params.length : 0); |
| addError("Incorrect number of parameters. Expected 1 but found " + paramCount, expression); |
| } |
| } |
| super.visitCaseStatement(statement); |
| if (!maybeFallsThrough(statement.getCode())) { |
| typeCheckingContext.temporaryIfBranchTypeInformation |
| .peek().remove(extractTemporaryTypeInfoKey(selectable)); |
| restoreTypeBeforeConditional(); // isolate assignment branch |
| } |
| } |
| |
| private static boolean maybeFallsThrough(Statement statement) { |
| if (statement.isEmpty()) return true; |
| if (statement instanceof BlockStatement) |
| statement = DefaultGroovyMethods.last(((BlockStatement) statement).getStatements()); |
| // end break, continue, return or throw |
| if (statement instanceof BreakStatement |
| || statement instanceof ContinueStatement |
| || !GeneralUtils.maybeFallsThrough(statement)) { |
| return false; |
| } |
| return true; |
| } |
| |
| private void recordAssignment(final VariableExpression lhsExpr, final ClassNode rhsType) { |
| typeCheckingContext.ifElseForWhileAssignmentTracker.computeIfAbsent(lhsExpr, lhs -> { |
| ClassNode lhsType = lhs.getNodeMetaData(INFERRED_TYPE); |
| List<ClassNode> types = new ArrayList<>(2); |
| types.add(lhsType); |
| return types; |
| }).add(rhsType); |
| } |
| |
| private void restoreTypeBeforeConditional() { |
| typeCheckingContext.ifElseForWhileAssignmentTracker.forEach((var, types) -> { |
| var.putNodeMetaData(INFERRED_TYPE, types.get(0)); |
| }); |
| } |
| |
| protected Map<VariableExpression, List<ClassNode>> pushAssignmentTracking() { |
| Map<VariableExpression, List<ClassNode>> oldTracker = typeCheckingContext.ifElseForWhileAssignmentTracker; |
| typeCheckingContext.ifElseForWhileAssignmentTracker = new HashMap<>(); |
| return oldTracker; |
| } |
| |
| protected Map<VariableExpression, ClassNode> popAssignmentTracking(final Map<VariableExpression, List<ClassNode>> oldTracker) { |
| Map<VariableExpression, ClassNode> assignments = new HashMap<>(); |
| typeCheckingContext.ifElseForWhileAssignmentTracker.forEach((var, types) -> { |
| types.stream().filter(t -> t != null && t != UNKNOWN_PARAMETER_TYPE) // GROOVY-6099, GROOVY-10294 |
| .reduce(WideningCategories::lowestUpperBound).ifPresent(type -> { |
| assignments.put(var, type); |
| storeType(var, type); |
| }); |
| }); |
| typeCheckingContext.ifElseForWhileAssignmentTracker = oldTracker; |
| return assignments; |
| } |
| |
| @Override |
| public void visitArrayExpression(final ArrayExpression expression) { |
| super.visitArrayExpression(expression); |
| ClassNode elementType; |
| List<Expression> expressions; |
| if (expression.hasInitializer()) { |
| elementType = expression.getElementType(); |
| expressions = expression.getExpressions(); |
| } else { |
| elementType = int_TYPE; |
| expressions = expression.getSizeExpression(); |
| } |
| for (Expression elementExpr : expressions) { |
| if (!checkCompatibleAssignmentTypes(elementType, getType(elementExpr), elementExpr, false)) { |
| addStaticTypeError("Cannot convert from " + prettyPrintType(getType(elementExpr)) + " to " + prettyPrintType(elementType), elementExpr); |
| } |
| } |
| } |
| |
| @Override |
| public void visitCastExpression(final CastExpression expression) { |
| ClassNode target = expression.getType(); |
| Expression source = expression.getExpression(); |
| applyTargetType(target, source); // GROOVY-9997 |
| |
| source.visit(this); |
| |
| if (!expression.isCoerce() && !checkCast(target, source)) { |
| addStaticTypeError("Inconvertible types: cannot cast " + prettyPrintType(getType(source)) + " to " + prettyPrintType(target), expression); |
| } |
| } |
| |
| protected boolean checkCast(final ClassNode targetType, final Expression source) { |
| boolean sourceIsNull = isNullConstant(source); |
| ClassNode expressionType = getType(source); |
| if (targetType.isArray() && expressionType.isArray()) { |
| return checkCast(targetType.getComponentType(), varX("foo", expressionType.getComponentType())); |
| } else if (isPrimitiveChar(targetType) && isStringType(expressionType) |
| && source instanceof ConstantExpression && source.getText().length() == 1) { |
| // ex: (char) 'c' |
| } else if (isWrapperCharacter(targetType) && (isStringType(expressionType) || sourceIsNull) |
| && (sourceIsNull || source instanceof ConstantExpression && source.getText().length() == 1)) { |
| // ex : (Character) 'c' |
| } else if (isNumberCategory(getWrapper(targetType)) && (isNumberCategory(getWrapper(expressionType)) || isPrimitiveChar(expressionType))) { |
| // ex: short s = (short) 0 |
| } else if (sourceIsNull && !isPrimitiveType(targetType)) { |
| // ex: (Date)null |
| } else if (isPrimitiveChar(targetType) && isPrimitiveType(expressionType) && isNumberType(expressionType)) { |
| // char c = (char) ... |
| } else if (sourceIsNull && isPrimitiveType(targetType) && !isPrimitiveBoolean(targetType)) { |
| return false; |
| } else if (!Modifier.isFinal(expressionType.getModifiers()) && targetType.isInterface()) { |
| return true; |
| } else if (!Modifier.isFinal(targetType.getModifiers()) && expressionType.isInterface()) { |
| return true; |
| } else if (!isAssignableTo(targetType, expressionType) && !implementsInterfaceOrIsSubclassOf(expressionType, targetType)) { |
| return false; |
| } |
| return true; |
| } |
| |
| @Override |
| public void visitTernaryExpression(final TernaryExpression expression) { |
| Map<VariableExpression, List<ClassNode>> oldTracker = pushAssignmentTracking(); |
| typeCheckingContext.pushTemporaryTypeInfo(); // capture instanceof restriction |
| if (!(expression instanceof ElvisOperatorExpression)) { |
| expression.getBooleanExpression().visit(this); |
| } |
| Expression trueExpression = expression.getTrueExpression(); |
| ClassNode typeOfTrue = findCurrentInstanceOfClass(trueExpression, null); |
| typeOfTrue = Optional.ofNullable(typeOfTrue).orElse(visitValueExpression(trueExpression)); |
| Map<Object, List<ClassNode>> tti = typeCheckingContext.temporaryIfBranchTypeInformation.pop(); |
| |
| typeCheckingContext.pushTemporaryTypeInfo(); |
| tti.forEach(this::putNotInstanceOfTypeInfo); // GROOVY-8412 |
| Expression falseExpression = expression.getFalseExpression(); |
| ClassNode typeOfFalse = visitValueExpression(falseExpression); |
| typeCheckingContext.popTemporaryTypeInfo(); |
| |
| ClassNode resultType; |
| if (isNullConstant(trueExpression) && isNullConstant(falseExpression)) { // GROOVY-5523 |
| resultType = checkForTargetType(trueExpression, UNKNOWN_PARAMETER_TYPE); |
| } else if (isNullConstant(trueExpression) || (isEmptyCollection(trueExpression) |
| && isOrImplements(typeOfTrue, typeOfFalse))) { // [] : List/Collection/Iterable |
| resultType = wrapTypeIfNecessary(checkForTargetType(falseExpression, typeOfFalse)); |
| } else if (isNullConstant(falseExpression) || (isEmptyCollection(falseExpression) |
| && isOrImplements(typeOfFalse, typeOfTrue))) { // List/Collection/Iterable : [] |
| resultType = wrapTypeIfNecessary(checkForTargetType(trueExpression, typeOfTrue)); |
| } else { |
| typeOfFalse = checkForTargetType(falseExpression, typeOfFalse); |
| typeOfTrue = checkForTargetType(trueExpression, typeOfTrue); |
| resultType = lowestUpperBound(typeOfTrue, typeOfFalse); |
| } |
| storeType(expression, resultType); |
| popAssignmentTracking(oldTracker); |
| } |
| |
| /** |
| * @param expr true or false branch of ternary expression |
| * @return the inferred type of {@code expr} |
| */ |
| private ClassNode visitValueExpression(final Expression expr) { |
| if (expr instanceof ClosureExpression) { |
| applyTargetType(checkForTargetType(expr, null), expr); |
| } |
| expr.visit(this); |
| return getType(expr); |
| } |
| |
| /** |
| * @param expr true or false branch of ternary expression |
| * @param type the inferred type of {@code expr} |
| */ |
| private ClassNode checkForTargetType(final Expression expr, final ClassNode type) { |
| ClassNode targetType = null; |
| MethodNode enclosingMethod = typeCheckingContext.getEnclosingMethod(); |
| MethodCall enclosingMethodCall = (MethodCall)typeCheckingContext.getEnclosingMethodCall(); |
| BinaryExpression enclosingExpression = typeCheckingContext.getEnclosingBinaryExpression(); |
| if (enclosingExpression != null |
| && isAssignment(enclosingExpression.getOperation().getType()) |
| && isTypeSource(expr, enclosingExpression.getRightExpression())) { |
| targetType = getDeclaredOrInferredType(enclosingExpression.getLeftExpression()); |
| } else if (enclosingMethodCall != null |
| && InvocationWriter.makeArgumentList(enclosingMethodCall.getArguments()) |
| .getExpressions().stream().anyMatch(arg -> isTypeSource(expr, arg))) { |
| // TODO: locate method target parameter |
| } else if (enclosingMethod != null |
| && !enclosingMethod.isAbstract() |
| && !enclosingMethod.isVoidMethod() |
| && isTypeSource(expr, enclosingMethod)) { |
| targetType = enclosingMethod.getReturnType(); |
| } |
| |
| if (expr instanceof ClosureExpression) { // GROOVY-10271, GROOVY-10272 |
| return isSAMType(targetType) ? targetType : type; |
| } |
| |
| if (targetType == null) |
| targetType = type.getPlainNodeReference(); |
| if (type == UNKNOWN_PARAMETER_TYPE) return targetType; |
| |
| if (expr instanceof ConstructorCallExpression) { // GROOVY-9972, GROOVY-9983, GROOVY-10114 |
| inferDiamondType((ConstructorCallExpression) expr, targetType); |
| } |
| |
| return adjustForTargetType(type, targetType); |
| } |
| |
| private static ClassNode adjustForTargetType(final ClassNode resultType, final ClassNode targetType) { |
| if (targetType.isUsingGenerics() |
| && missesGenericsTypes(resultType) |
| // GROOVY-10324, GROOVY-10342, et al. |
| && !resultType.isGenericsPlaceHolder()) { |
| // unchecked assignment |
| // List<Type> list = new LinkedList() |
| // Iterable<Type> iter = new LinkedList() |
| // Collection<Type> col1 = Collections.emptyList() |
| // Collection<Type> col2 = Collections.emptyList() ?: [] |
| // Collection<Type> view = ConcurrentHashMap.newKeySet() |
| |
| // the inferred type of the binary expression is the type of the RHS |
| // "completed" with generics type information available from the LHS |
| if (targetType.equals(resultType)) { |
| // GROOVY-6126, GROOVY-6558, GROOVY-6564, et al. |
| if (!targetType.isGenericsPlaceHolder()) return targetType; |
| } else { |
| // GROOVY-5640, GROOVY-9033, GROOVY-10220, GROOVY-10235, GROOVY-10688, GROOVY-11192, et al. |
| Map<GenericsTypeName, GenericsType> gt = new HashMap<>(); |
| ClassNode sc = resultType; |
| for (;;) { |
| sc = getNextSuperClass(sc,targetType); |
| if (!gt.isEmpty()) { |
| // propagate resultType's generics |
| sc = applyGenericsContext(gt, sc); |
| } |
| if (sc == null || sc.equals(targetType)) { |
| gt.clear(); |
| break; |
| } |
| // map of sc's type vars to resultType's type vars |
| extractGenericsConnections(gt, sc, sc.redirect()); |
| } |
| extractGenericsConnections(gt, resultType, resultType.redirect()); |
| extractGenericsConnections(gt, targetType, sc); // maps rt's tv(s) |
| |
| return applyGenericsContext(gt, resultType.redirect()); |
| } |
| } |
| |
| return resultType; |
| } |
| |
| private static boolean isTypeSource(final Expression expr, final Expression right) { |
| if (right instanceof TernaryExpression) { |
| return isTypeSource(expr, ((TernaryExpression) right).getTrueExpression()) |
| || isTypeSource(expr, ((TernaryExpression) right).getFalseExpression()); |
| } |
| return expr == right; |
| } |
| |
| private static boolean isTypeSource(final Expression expr, final MethodNode mNode) { |
| boolean[] returned = new boolean[1]; |
| |
| mNode.getCode().visit(new CodeVisitorSupport() { |
| @Override |
| public void visitReturnStatement(final ReturnStatement returnStatement) { |
| if (isTypeSource(expr, returnStatement.getExpression())) { |
| returned[0] = true; |
| } |
| } |
| @Override |
| public void visitClosureExpression(final ClosureExpression expression) { |
| } |
| }); |
| |
| if (!returned[0]) { |
| new ReturnAdder(returnStatement -> { |
| if (isTypeSource(expr, returnStatement.getExpression())) { |
| returned[0] = true; |
| } |
| }).visitMethod(mNode); |
| } |
| |
| return returned[0]; |
| } |
| |
| private static boolean isEmptyCollection(final Expression expr) { |
| return isEmptyList(expr) || isEmptyMap(expr); |
| } |
| |
| private static boolean isEmptyList(final Expression expr) { |
| return expr instanceof ListExpression && ((ListExpression) expr).getExpressions().isEmpty(); |
| } |
| |
| private static boolean isEmptyMap(final Expression expr) { |
| return expr instanceof MapExpression && ((MapExpression) expr).getMapEntryExpressions().isEmpty(); |
| } |
| |
| @Override |
| public void visitTryCatchFinally(final TryCatchStatement statement) { |
| List<CatchStatement> catchStatements = statement.getCatchStatements(); |
| for (CatchStatement catchStatement : catchStatements) { |
| ClassNode exceptionType = catchStatement.getExceptionType(); |
| typeCheckingContext.controlStructureVariables.put(catchStatement.getVariable(), exceptionType); |
| } |
| try { |
| super.visitTryCatchFinally(statement); |
| } finally { |
| for (CatchStatement catchStatement : catchStatements) { |
| typeCheckingContext.controlStructureVariables.remove(catchStatement.getVariable()); |
| } |
| } |
| } |
| |
| protected void storeType(final Expression exp, ClassNode cn) { |
| if (cn == UNKNOWN_PARAMETER_TYPE) { // null for assignment or parameter |
| // instead of storing an "unknown" type, reset the type information |
| // by determining the declaration type of the expression |
| cn = getOriginalDeclarationType(exp); |
| // GROOVY-10356, GROOVY-10623: "def" |
| if (isDynamicTyped(cn)) return; |
| } |
| if (cn != null && isPrimitiveType(cn)) { |
| if (exp instanceof VariableExpression && ((VariableExpression) exp).isClosureSharedVariable()) { |
| cn = getWrapper(cn); |
| } else if (exp instanceof MethodCallExpression && ((MethodCallExpression) exp).isSafe()) { |
| cn = getWrapper(cn); |
| } else if (exp instanceof PropertyExpression && ((PropertyExpression) exp).isSafe()) { |
| cn = getWrapper(cn); |
| } |
| } |
| |
| ClassNode oldValue = (ClassNode) exp.putNodeMetaData(INFERRED_TYPE, cn); |
| if (oldValue != null) { |
| // this may happen when a variable declaration type is wider than the subsequent assignment values |
| // for example : |
| // def o = 1 // first, an int |
| // o = 'String' // then a string |
| // o = new Object() // and eventually an object ! |
| // in that case, the INFERRED_TYPE corresponds to the current inferred type, while |
| // DECLARATION_INFERRED_TYPE is the type which should be used for the initial type declaration |
| ClassNode oldDIT = exp.getNodeMetaData(DECLARATION_INFERRED_TYPE); |
| if (oldDIT != null) { |
| exp.putNodeMetaData(DECLARATION_INFERRED_TYPE, cn == null ? oldDIT : lowestUpperBound(oldDIT, cn)); |
| } else { |
| exp.putNodeMetaData(DECLARATION_INFERRED_TYPE, cn == null ? null : lowestUpperBound(oldValue, cn)); |
| } |
| } |
| |
| if (exp instanceof VariableExpression) { |
| VariableExpression var = (VariableExpression) exp; |
| Variable accessedVariable = var.getAccessedVariable(); |
| if (accessedVariable instanceof VariableExpression) { |
| if (accessedVariable != var) |
| storeType((VariableExpression) accessedVariable, cn); |
| } else if (accessedVariable instanceof Parameter) { |
| ((Parameter) accessedVariable).putNodeMetaData(INFERRED_TYPE, cn); |
| } |
| if (cn != null && var.isClosureSharedVariable()) { |
| List<ClassNode> assignedTypes = typeCheckingContext.closureSharedVariablesAssignmentTypes.computeIfAbsent(var, k -> new LinkedList<>()); |
| assignedTypes.add(cn); |
| } |
| if (!var.isThisExpression() && !var.isSuperExpression() && !typeCheckingContext.temporaryIfBranchTypeInformation.isEmpty()) { |
| // GROOVY-5226, GROOVY-11290: assignment voids instanceof |
| pushInstanceOfTypeInfo(var, classX(VOID_TYPE)); |
| } |
| } |
| } |
| |
| protected ClassNode getResultType(ClassNode left, final int op, final ClassNode right, final BinaryExpression expr) { |
| ClassNode leftRedirect = left.redirect(); |
| ClassNode rightRedirect = right.redirect(); |
| |
| Expression leftExpression = expr.getLeftExpression(); |
| Expression rightExpression = expr.getRightExpression(); |
| |
| if (op == EQUAL || op == ELVIS_EQUAL) { |
| if (leftExpression instanceof VariableExpression) { |
| ClassNode initialType = getOriginalDeclarationType(leftExpression); |
| |
| if (isPrimitiveType(rightRedirect) && initialType.isDerivedFrom(Number_TYPE)) { |
| return getWrapper(right); |
| } |
| |
| if (isPrimitiveType(initialType) && rightRedirect.isDerivedFrom(Number_TYPE)) { |
| return getUnwrapper(right); |
| } |
| |
| // as anything can be assigned to a String, Class or [Bb]oolean, return the left type instead |
| if (isWildcardLeftHandSide(initialType) && !isObjectType(initialType)) { |
| return initialType; |
| } |
| } |
| |
| if (!isObjectType(leftRedirect)) { |
| if (rightExpression instanceof ListExpression) { |
| if (LIST_TYPE.equals(leftRedirect) |
| || ITERABLE_TYPE.equals(leftRedirect) |
| || Collection_TYPE.equals(leftRedirect) |
| || ArrayList_TYPE.isDerivedFrom(leftRedirect)) { // GROOVY-6912 |
| return getLiteralResultType(left, right, ArrayList_TYPE); // GROOVY-7128 |
| } |
| if (SET_TYPE.equals(leftRedirect) |
| || LinkedHashSet_TYPE.isDerivedFrom(leftRedirect)) { // GROOVY-6912 |
| return getLiteralResultType(left, right, LinkedHashSet_TYPE); // GROOVY-7128 |
| } |
| } else if (rightExpression instanceof MapExpression) { |
| if (MAP_TYPE.equals(leftRedirect) |
| || LinkedHashMap_TYPE.isDerivedFrom(leftRedirect)) { |
| return getLiteralResultType(left, right, LinkedHashMap_TYPE); // GROOVY-7128, GROOVY-9844 |
| } |
| } else if (rightExpression instanceof ClosureExpression |
| || rightExpression instanceof MethodPointerExpression) { |
| if (isSAMType(leftRedirect)) { |
| return left; // coercion |
| } |
| } |
| } |
| |
| return right; |
| } |
| |
| if (isBoolIntrinsicOp(op)) { |
| return boolean_TYPE; |
| } |
| |
| if (op == FIND_REGEX) { |
| return Matcher_TYPE; |
| } |
| |
| if (isArrayOp(op)) { |
| if (isOrImplements(left, MAP_TYPE) && (isStringType(right) || isGStringOrGStringStringLUB(right))) { // GROOVY-5700, GROOVY-6668, GROOVY-8212, GROOVY-8788 |
| PropertyExpression prop = propX(leftExpression, rightExpression); // m['xx'] -> m.xx |
| return existsProperty(prop, !typeCheckingContext.isTargetOfEnclosingAssignment(expr)) |
| ? getType(prop) : getTypeForMapPropertyExpression(left, prop); |
| } |
| Expression copy = binX(leftExpression, expr.getOperation(), rightExpression); |
| copy.setSourcePosition(expr); // do not propagate BINARY_EXP_TARGET, etc. |
| MethodNode method = findMethodOrFail(copy, left, "getAt", rightRedirect); |
| if (method != null && !isNumberCategory(getWrapper(rightRedirect))) { |
| return inferReturnTypeGenerics(left, method, rightExpression); |
| } |
| return inferComponentType(left, right); |
| } |
| |
| String operationName = getOperationName(op); |
| if (operationName == null) throw new GroovyBugError( |
| "Unknown result type for binary operator " + op); |
| // the left operand is determining the result of the operation |
| // for primitives and their wrapper we use a fixed table here: |
| ClassNode mathResultType = getMathResultType(op, leftRedirect, rightRedirect, operationName); |
| if (mathResultType != null) { |
| return mathResultType; |
| } |
| // GROOVY-9006: compare to null for types that overload equals |
| if ("equals".equals(operationName) && (left == UNKNOWN_PARAMETER_TYPE |
| || right == UNKNOWN_PARAMETER_TYPE)) { |
| return boolean_TYPE; |
| } |
| // GROOVY-5890: do not mix Class<Type> with Type |
| if (leftExpression instanceof ClassExpression) { |
| left = CLASS_Type.getPlainNodeReference(); |
| } |
| MethodNode method = findMethodOrFail(expr, left, operationName, right); |
| if (method != null) { |
| if (op == COMPARE_NOT_IN && isDefaultExtension(method)) { |
| // GROOVY-10915: check if left implements its own isCase method |
| MethodNode isCase = findMethodOrFail(expr, left, "isCase", right); |
| if (isCase != null && !isDefaultExtension(isCase)) return null; // require dynamic dispatch |
| } |
| |
| storeTargetMethod(expr, method); |
| typeCheckMethodsWithGenericsOrFail(left, new ClassNode[]{right}, method, expr); |
| |
| if (isAssignment(op)) return left; |
| if (!"compareTo".equals(operationName)) |
| return inferReturnTypeGenerics(left, method, args(rightExpression)); |
| } |
| |
| if (isCompareToBoolean(op)) return boolean_TYPE; |
| if (op == COMPARE_TO) return int_TYPE; |
| return null; |
| } |
| |
| /** |
| * For "{@code List<Type> x = [...]}" or "{@code Set<Type> y = [...]}", etc. |
| * the literal may be composed of subtypes of {@code Type}. In these cases, |
| * {@code ArrayList<Type>} is an appropriate result type for the expression. |
| */ |
| private static ClassNode getLiteralResultType(final ClassNode targetType, final ClassNode sourceType, final ClassNode baseType) { |
| ClassNode resultType = sourceType.equals(baseType) ? sourceType |
| : GenericsUtils.parameterizeType(sourceType, baseType.getPlainNodeReference()); |
| |
| if (targetType.getGenericsTypes() != null |
| && !GenericsUtils.buildWildcardType(targetType).isCompatibleWith(resultType)) { |
| BiPredicate<GenericsType, GenericsType> isEqualOrSuper = (target, source) -> { |
| if (target.isCompatibleWith(source.getType())) { |
| return true; |
| } |
| if (!target.isPlaceholder() && !target.isWildcard()) { |
| return GenericsUtils.buildWildcardType(getCombinedBoundType(target)).isCompatibleWith(source.getType()); |
| } |
| return false; |
| }; |
| |
| GenericsType[] lgt = targetType.getGenericsTypes(), rgt = resultType.getGenericsTypes(); |
| if (IntStream.range(0, lgt.length).allMatch(i -> isEqualOrSuper.test(lgt[i], rgt[i]))) { |
| resultType = GenericsUtils.parameterizeType(targetType, baseType.getPlainNodeReference()); |
| } |
| } |
| |
| return resultType; |
| } |
| |
| private static ClassNode getMathResultType(final int op, final ClassNode leftRedirect, final ClassNode rightRedirect, final String operationName) { |
| if (isNumberType(leftRedirect) && isNumberType(rightRedirect)) { |
| if (isOperationInGroup(op)) { |
| if (isIntCategory(leftRedirect) && isIntCategory(rightRedirect)) return int_TYPE; |
| if (isLongCategory(leftRedirect) && isLongCategory(rightRedirect)) return long_TYPE; |
| if (isFloat(leftRedirect) && isFloat(rightRedirect)) return float_TYPE; |
| if (isDouble(leftRedirect) && isDouble(rightRedirect)) return double_TYPE; |
| } else if (isPowerOperator(op)) { |
| return Number_TYPE; |
| } else if (isBitOperator(op) || op == INTDIV || op == INTDIV_EQUAL) { |
| if (isIntCategory(getUnwrapper(leftRedirect)) && isIntCategory(getUnwrapper(rightRedirect))) |
| return int_TYPE; |
| if (isLongCategory(getUnwrapper(leftRedirect)) && isLongCategory(getUnwrapper(rightRedirect))) |
| return long_TYPE; |
| if (isBigIntCategory(getUnwrapper(leftRedirect)) && isBigIntCategory(getUnwrapper(rightRedirect))) |
| return BigInteger_TYPE; |
| } else if (isCompareToBoolean(op) || op == COMPARE_EQUAL || op == COMPARE_NOT_EQUAL) { |
| return boolean_TYPE; |
| } |
| } else if (isPrimitiveChar(leftRedirect) && isPrimitiveChar(rightRedirect)) { |
| if (isCompareToBoolean(op) || op == COMPARE_EQUAL || op == COMPARE_NOT_EQUAL) { |
| return boolean_TYPE; |
| } |
| } |
| |
| // try to find a method for the operation |
| if (isShiftOperation(operationName) && isNumberCategory(leftRedirect) && (isIntCategory(rightRedirect) || isLongCategory(rightRedirect))) { |
| return leftRedirect; |
| } |
| |
| // Divisions may produce different results depending on operand types |
| if (isNumberCategory(getWrapper(rightRedirect)) && (isNumberCategory(getWrapper(leftRedirect)) && (DIVIDE == op || DIVIDE_EQUAL == op))) { |
| if (isFloatingCategory(leftRedirect) || isFloatingCategory(rightRedirect)) { |
| if (!isPrimitiveType(leftRedirect) || !isPrimitiveType(rightRedirect)) { |
| return Double_TYPE; |
| } |
| return double_TYPE; |
| } |
| if (DIVIDE == op) { |
| return BigDecimal_TYPE; |
| } |
| return leftRedirect; |
| } else if (isOperationInGroup(op)) { |
| if (isNumberCategory(getWrapper(leftRedirect)) && isNumberCategory(getWrapper(rightRedirect))) { |
| return getGroupOperationResultType(leftRedirect, rightRedirect); |
| } |
| } |
| if (isNumberCategory(getWrapper(rightRedirect)) && isNumberCategory(getWrapper(leftRedirect)) && |
| (REMAINDER == op || REMAINDER_EQUAL == op || MOD == op || MOD_EQUAL == op)) { |
| return leftRedirect; |
| } |
| return null; |
| } |
| |
| protected static ClassNode getGroupOperationResultType(final ClassNode a, final ClassNode b) { |
| if (isBigIntCategory(a) && isBigIntCategory(b)) return BigInteger_TYPE; |
| if (isBigDecCategory(a) && isBigDecCategory(b)) return BigDecimal_TYPE; |
| if (isBigDecimalType(a) || isBigDecimalType(b)) return BigDecimal_TYPE; |
| if (isBigIntegerType(a) || isBigIntegerType(b)) { |
| if (isBigIntCategory(a) && isBigIntCategory(b)) return BigInteger_TYPE; |
| return BigDecimal_TYPE; |
| } |
| if (isPrimitiveDouble(a) || isPrimitiveDouble(b)) return double_TYPE; |
| if (isWrapperDouble(a) || isWrapperDouble(b)) return Double_TYPE; |
| if (isPrimitiveFloat(a) || isPrimitiveFloat(b)) return float_TYPE; |
| if (isWrapperFloat(a) || isWrapperFloat(b)) return Float_TYPE; |
| if (isPrimitiveLong(a) || isPrimitiveLong(b)) return long_TYPE; |
| if (isWrapperLong(a) || isWrapperLong(b)) return Long_TYPE; |
| if (isPrimitiveInt(a) || isPrimitiveInt(b)) return int_TYPE; |
| if (isWrapperInteger(a) || isWrapperInteger(b)) return Integer_TYPE; |
| if (isPrimitiveShort(a) || isPrimitiveShort(b)) return short_TYPE; |
| if (isWrapperShort(a) || isWrapperShort(b)) return Short_TYPE; |
| if (isPrimitiveByte(a) || isPrimitiveByte(b)) return byte_TYPE; |
| if (isWrapperByte(a) || isWrapperByte(b)) return Byte_TYPE; |
| if (isPrimitiveChar(a) || isPrimitiveChar(b)) return char_TYPE; |
| if (isWrapperCharacter(a) || isWrapperCharacter(b)) return Character_TYPE; |
| return Number_TYPE; |
| } |
| |
| protected ClassNode inferComponentType(final ClassNode receiverType, final ClassNode subscriptType) { |
| ClassNode componentType = null; |
| if (receiverType.isArray()) { // GROOVY-11335 |
| componentType = receiverType.getComponentType(); |
| } else { |
| MethodCallExpression mce; |
| if (subscriptType != null) { // GROOVY-5521: check for a suitable "getAt(T)" method |
| mce = callX(varX("#", receiverType), "getAt", varX("selector", subscriptType)); |
| } else { // GROOVY-8133: check for an "iterator()" method |
| mce = callX(varX("#", receiverType), "iterator"); |
| } |
| mce.setImplicitThis(false); // GROOVY-8943 |
| |
| typeCheckingContext.pushErrorCollector(); |
| try { |
| visitMethodCallExpression(mce); |
| } finally { |
| typeCheckingContext.popErrorCollector(); |
| } |
| |
| if (subscriptType != null) { |
| componentType = getType(mce); |
| } else { |
| ClassNode iteratorType = getType(mce); |
| if (isOrImplements(iteratorType, Iterator_TYPE) && (iteratorType.getGenericsTypes() != null |
| // ignore the iterator(Object) extension method, since it makes *everything* appear iterable |
| || !mce.<MethodNode>getNodeMetaData(DIRECT_METHOD_CALL_TARGET).getDeclaringClass().equals(OBJECT_TYPE))) { |
| componentType = Optional.ofNullable(iteratorType.getGenericsTypes()).map(gt -> getCombinedBoundType(gt[0])).orElse(OBJECT_TYPE); |
| } |
| } |
| } |
| return componentType; |
| } |
| |
| protected MethodNode findMethodOrFail(final Expression expr, final ClassNode receiver, final String name, final ClassNode... args) { |
| List<MethodNode> methods = findMethod(receiver, name, args); |
| if (methods.isEmpty() && (expr instanceof BinaryExpression)) { |
| BinaryExpression be = (BinaryExpression) expr; |
| MethodCallExpression call = callX(be.getLeftExpression(), name, be.getRightExpression()); |
| methods = extension.handleMissingMethod(receiver, name, args(be.getLeftExpression()), args, call); |
| } |
| if (methods.isEmpty()) { |
| addNoMatchingMethodError(receiver, name, args, expr); |
| } else { |
| if (areCategoryMethodCalls(methods, name, args)) { |
| addCategoryMethodCallError(expr); |
| } |
| methods = disambiguateMethods(methods, receiver, args, expr); |
| if (methods.size() == 1) { |
| return methods.get(0); |
| } else { |
| addAmbiguousErrorMessage(methods, name, args, expr); |
| } |
| } |
| return null; |
| } |
| |
| private List<MethodNode> disambiguateMethods(List<MethodNode> methods, final ClassNode receiver, final ClassNode[] arguments, final Expression call) { |
| if (methods.size() > 1 && receiver != null && arguments != null) { |
| List<MethodNode> filteredWithGenerics = new LinkedList<>(); |
| for (MethodNode method : methods) { |
| if (typeCheckMethodsWithGenerics(receiver, arguments, method) |
| && (method.getModifiers() & Opcodes.ACC_BRIDGE) == 0) { |
| filteredWithGenerics.add(method); |
| } |
| } |
| if (filteredWithGenerics.size() == 1) { |
| return filteredWithGenerics; |
| } |
| |
| methods = extension.handleAmbiguousMethods(methods, call); |
| } |
| |
| if (methods.size() > 1 && call instanceof MethodCall) { |
| String methodName = ((MethodCall) call).getMethodAsString(); |
| methods = methods.stream().filter(m -> m.getName().equals(methodName)).collect(Collectors.toList()); |
| } |
| |
| return methods; |
| } |
| |
| protected static String prettyPrintMethodList(final List<MethodNode> nodes) { |
| StringBuilder sb = new StringBuilder("["); |
| for (int i = 0, n = nodes.size(); i < n; i += 1) { |
| MethodNode node = nodes.get(i); |
| sb.append(prettyPrintType(node.getReturnType())); |
| sb.append(" "); |
| sb.append(prettyPrintTypeName(node.getDeclaringClass())); |
| sb.append("#"); |
| sb.append(toMethodParametersString(node.getName(), extractTypesFromParameters(node.getParameters()))); |
| if (i < n - 1) sb.append(", "); |
| } |
| sb.append("]"); |
| return sb.toString(); |
| } |
| |
| protected boolean areCategoryMethodCalls(final List<MethodNode> foundMethods, final String name, final ClassNode[] args) { |
| boolean category = false; |
| if ("use".equals(name) && args != null && args.length == 2 && args[1].equals(CLOSURE_TYPE)) { |
| category = true; |
| for (MethodNode method : foundMethods) { |
| if (!isDefaultExtension(method)) { |
| category = false; |
| break; |
| } |
| } |
| } |
| return category; |
| } |
| |
| /** |
| * Returns methods defined for the specified receiver and adds "non-existing" |
| * methods that will be generated afterwards by the compiler; for example if |
| * a method is using default values and the class node is not compiled yet. |
| * |
| * @param receiver the type to search for methods |
| * @param name the name of the methods to return |
| * @return the methods that are defined on the receiver completed with stubs for future methods |
| */ |
| protected List<MethodNode> findMethodsWithGenerated(final ClassNode receiver, final String name) { |
| if (receiver.isArray()) { |
| if (name.equals("clone")) { // GROOVY-10319: array clone -- <https://docs.oracle.com/javase/specs/jls/se8/html/jls-10.html#jls-10.7> |
| MethodNode clone = new MethodNode("clone", Opcodes.ACC_PUBLIC, OBJECT_TYPE, Parameter.EMPTY_ARRAY, ClassNode.EMPTY_ARRAY, null); |
| clone.setDeclaringClass(OBJECT_TYPE); // retain Object for declaringClass and returnType |
| clone.setNodeMetaData(INFERRED_RETURN_TYPE, receiver); |
| return Collections.singletonList(clone); |
| } else { |
| return OBJECT_TYPE.getMethods(name); |
| } |
| } |
| |
| List<MethodNode> methods = receiver.getMethods(name); |
| |
| // GROOVY-5166, GROOVY-9890, GROOVY-10700: non-static interface/trait methods |
| Set<ClassNode> done = new HashSet<>(); |
| for (ClassNode next = receiver; next != null; next = next.getSuperClass()) { |
| done.add(next); |
| for (ClassNode face : next.getAllInterfaces()) { |
| if (done.add(face)) { |
| for (MethodNode mn : face.getDeclaredMethods(name)) { |
| if (mn.isPublic() && !mn.isStatic()) methods.add(mn); |
| } |
| } |
| } |
| } |
| |
| if (receiver.isInterface()) { |
| methods.addAll(OBJECT_TYPE.getMethods(name)); |
| } |
| |
| if (!receiver.isResolved() && !methods.isEmpty()) { |
| methods = addGeneratedMethods(receiver, methods); |
| } |
| |
| return methods; |
| } |
| |
| private static List<MethodNode> addGeneratedMethods(final ClassNode receiver, final List<? extends MethodNode> methods) { |
| // using a comparator of parameters |
| List<MethodNode> result = new LinkedList<>(); |
| for (MethodNode method : methods) { |
| result.add(method); |
| Parameter[] parameters = method.getParameters(); |
| int counter = 0; |
| int size = parameters.length; |
| for (int i = size - 1; i >= 0; i--) { |
| Parameter parameter = parameters[i]; |
| if (parameter != null && parameter.hasInitialExpression()) { |
| counter++; |
| } |
| } |
| |
| for (int j = 1; j <= counter; j++) { |
| Parameter[] newParams = new Parameter[parameters.length - j]; |
| int index = 0; |
| int k = 1; |
| for (Parameter parameter : parameters) { |
| if (k > counter - j && parameter != null && parameter.hasInitialExpression()) { |
| k++; |
| } else if (parameter != null && parameter.hasInitialExpression()) { |
| newParams[index++] = parameter; |
| k++; |
| } else { |
| newParams[index++] = parameter; |
| } |
| } |
| MethodNode stubbed; |
| if (method.isConstructor()) { |
| stubbed = new ConstructorNode( |
| method.getModifiers(), |
| newParams, |
| method.getExceptions(), |
| GENERATED_EMPTY_STATEMENT |
| ); |
| } else { |
| stubbed = new MethodNode( |
| method.getName(), |
| method.getModifiers(), |
| method.getReturnType(), |
| newParams, |
| method.getExceptions(), |
| GENERATED_EMPTY_STATEMENT |
| ); |
| stubbed.setGenericsTypes(method.getGenericsTypes()); |
| } |
| stubbed.setDeclaringClass(method.getDeclaringClass()); |
| result.add(stubbed); |
| } |
| } |
| return result; |
| } |
| |
| protected List<MethodNode> findMethod(ClassNode receiver, final String name, final ClassNode... args) { |
| if (isPrimitiveType(receiver)) receiver = getWrapper(receiver); |
| |
| List<MethodNode> methods; |
| if ("<init>".equals(name) && !receiver.isInterface()) { |
| methods = addGeneratedMethods(receiver, receiver.getDeclaredConstructors()); |
| if (methods.isEmpty()) { |
| MethodNode node = new ConstructorNode(Opcodes.ACC_PUBLIC, Parameter.EMPTY_ARRAY, ClassNode.EMPTY_ARRAY, GENERATED_EMPTY_STATEMENT); |
| node.setDeclaringClass(receiver); |
| methods.add(node); |
| if (receiver.isArray()) { |
| // No need to check the arguments against an array constructor: it just needs to exist. The array is |
| // created through coercion or by specifying its dimension(s), anyway, and would not match an |
| // arbitrary number of parameters. |
| return methods; |
| } |
| } |
| } else { |
| methods = findMethodsWithGenerated(receiver, name); |
| if ("call".equals(name) && receiver.isInterface()) { |
| MethodNode sam = findSAM(receiver); |
| if (sam != null) { |
| MethodNode callMethod = new MethodNode("call", sam.getModifiers(), sam.getReturnType(), sam.getParameters(), sam.getExceptions(), sam.getCode()); |
| callMethod.setDeclaringClass(sam.getDeclaringClass()); |
| callMethod.setSourcePosition(sam); |
| methods.add(callMethod); |
| } |
| } |
| if (typeCheckingContext.getEnclosingClassNodes().contains(receiver)) { |
| boolean staticOnly = Modifier.isStatic(receiver.getModifiers()); |
| for (ClassNode outer = receiver; (outer = outer.getOuterClass()) != null; |
| staticOnly = staticOnly || Modifier.isStatic(outer.getModifiers())) { |
| methods.addAll(allowStaticAccessToMember(findMethodsWithGenerated(outer, name), staticOnly)); |
| } |
| } |
| if (args == null || args.length == 0) { |
| // check for property accessor |
| String pname = extractPropertyNameFromMethodName("get", name); |
| if (pname == null) { |
| pname = extractPropertyNameFromMethodName("is", name); |
| } |
| PropertyNode property = null; |
| if (pname != null) { |
| property = findProperty(receiver, pname); |
| } else { |
| out: // look for property via getGetterName() for non-canonical case |
| for (ClassNode cn = receiver; cn != null; cn = cn.getSuperClass()) { |
| for (PropertyNode pn : cn.getProperties()) { |
| if (name.equals(pn.getGetterName())) { |
| property = pn; |
| break out; |
| } |
| } |
| } |
| } |
| if (property != null && property.getDeclaringClass().getGetterMethod(name) == null) { |
| MethodNode node = new MethodNode(name, Opcodes.ACC_PUBLIC | (property.isStatic() ? Opcodes.ACC_STATIC : 0), |
| property.getOriginType(), Parameter.EMPTY_ARRAY, ClassNode.EMPTY_ARRAY, GENERATED_EMPTY_STATEMENT); |
| node.setDeclaringClass(property.getDeclaringClass()); |
| node.setSynthetic(true); |
| methods.add(node); |
| } |
| } else if (args.length == 1 && (methods.isEmpty() |
| || methods.stream().allMatch(MethodNode::isAbstract))) { // GROOVY-10922 |
| // check for property mutator |
| String pname = extractPropertyNameFromMethodName("set", name); |
| if (pname != null) { |
| PropertyNode property = findProperty(receiver, pname); |
| if (property != null && !property.isFinal()) { |
| ClassNode type = property.getOriginType(); |
| if (implementsInterfaceOrIsSubclassOf(wrapTypeIfNecessary(args[0]), wrapTypeIfNecessary(type))) { |
| MethodNode node = new MethodNode(name, Opcodes.ACC_PUBLIC | (property.isStatic() ? Opcodes.ACC_STATIC : 0), |
| VOID_TYPE, new Parameter[]{new Parameter(type, name)}, ClassNode.EMPTY_ARRAY, GENERATED_EMPTY_STATEMENT); |
| node.setDeclaringClass(property.getDeclaringClass()); |
| node.setSynthetic(true); |
| methods.add(node); |
| } |
| } |
| } |
| } |
| } |
| |
| if (!name.endsWith("init>")) { // also search for extension methods |
| methods.addAll(findDGMMethodsForClassNode(getSourceUnit().getClassLoader(), receiver, name)); |
| } |
| methods = filterMethodsByVisibility(methods, typeCheckingContext.getEnclosingClassNode()); |
| List<MethodNode> chosen = chooseBestMethod(receiver, methods, args); |
| if (!chosen.isEmpty()) return chosen; |
| |
| // GROOVY-5566 |
| if (receiver instanceof InnerClassNode && ((InnerClassNode) receiver).isAnonymous() && methods.size() == 1 && args != null && "<init>".equals(name)) { |
| MethodNode constructor = methods.get(0); |
| if (constructor.getParameters().length == args.length) { |
| return methods; |
| } |
| } |
| |
| if (isClassClassNodeWrappingConcreteType(receiver)) { // GROOVY-6802, GROOVY-6803, GROOVY-9415 |
| List<MethodNode> result = findMethod(receiver.getGenericsTypes()[0].getType(), name, args); |
| if (!result.isEmpty()) return result; |
| } |
| |
| if (isGStringType(receiver)) { |
| return findMethod(STRING_TYPE, name, args); |
| } |
| |
| return EMPTY_METHODNODE_LIST; |
| } |
| |
| private PropertyNode findProperty(final ClassNode receiver, final String name) { |
| for (ClassNode cn = receiver; cn != null; cn = cn.getSuperClass()) { |
| PropertyNode property = cn.getProperty(name); |
| if (property != null) return property; |
| |
| if (!cn.isStaticClass() && cn.getOuterClass() != null |
| && typeCheckingContext.getEnclosingClassNodes().contains(cn)) { |
| ClassNode outer = cn.getOuterClass(); |
| do { |
| property = outer.getProperty(name); |
| if (property != null) return property; |
| } while (!outer.isStaticClass() && (outer = outer.getOuterClass()) != null); |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Given a method name and a prefix, returns the name of the property that should be looked up, |
| * following the java beans rules. For example, "getName" would return "name", while |
| * "getFullName" would return "fullName". |
| * If the prefix is not found, returns null. |
| * |
| * @param prefix the method name prefix ("get", "is", "set", ...) |
| * @param methodName the method name |
| * @return a property name if the prefix is found and the method matches the java beans rules, null otherwise |
| */ |
| public static String extractPropertyNameFromMethodName(final String prefix, final String methodName) { |
| if (prefix == null || methodName == null) return null; |
| if (methodName.startsWith(prefix) && prefix.length() < methodName.length()) { |
| String result = methodName.substring(prefix.length()); |
| String propertyName = decapitalize(result); |
| if (result.equals(capitalize(propertyName))) return propertyName; |
| } |
| return null; |
| } |
| |
| protected ClassNode getType(final ASTNode node) { |
| ClassNode type = node.getNodeMetaData(INFERRED_TYPE); |
| if (type != null) { |
| return type; |
| } |
| if (node instanceof ClassExpression) { |
| type = ((ClassExpression) node).getType(); |
| return makeClassSafe0(CLASS_Type, new GenericsType(type)); |
| } |
| if (node instanceof VariableExpression) { |
| VariableExpression vexp = (VariableExpression) node; |
| type = isTraitSelf(vexp); |
| if (type != null) return makeSelf(type); |
| if (vexp.isThisExpression()) return makeThis(); |
| if (vexp.isSuperExpression()) return makeSuper(); |
| |
| Variable variable = vexp.getAccessedVariable(); |
| if (variable instanceof FieldNode) { |
| FieldNode fieldNode = (FieldNode) variable; |
| ClassNode fieldType = fieldNode.getOriginType(); |
| if (!fieldNode.isStatic() && GenericsUtils.hasUnresolvedGenerics(fieldType)) { |
| ClassNode declType = fieldNode.getDeclaringClass(), thisType = typeCheckingContext.getEnclosingClassNode(); |
| fieldType = resolveGenericsWithContext(extractPlaceHolders(thisType, declType), fieldType); |
| } |
| return fieldType; |
| } |
| if (variable != vexp && variable instanceof VariableExpression) { |
| return getType((VariableExpression) variable); |
| } |
| if (variable instanceof Parameter) { |
| Parameter parameter = (Parameter) variable; |
| // check if param part of control structure - but not if inside instanceof |
| List<ClassNode> temporaryTypesForExpression = getTemporaryTypesForExpression(vexp); |
| if (temporaryTypesForExpression == null || temporaryTypesForExpression.isEmpty()) { |
| type = typeCheckingContext.controlStructureVariables.get(parameter); |
| } |
| // now check for closure override |
| if (type == null && temporaryTypesForExpression == null) { |
| type = getTypeFromClosureArguments(parameter); |
| } |
| if (type != null) { |
| storeType(vexp, type); |
| return type; |
| } |
| return getType((Parameter) variable); |
| } |
| return vexp.getOriginType(); |
| } |
| if (node instanceof Parameter || node instanceof FieldNode || node instanceof PropertyNode) { |
| return ((Variable) node).getOriginType(); |
| } |
| |
| if (node instanceof MethodNode) { |
| if ((node == GET_DELEGATE || node == GET_OWNER || node == GET_THISOBJECT) |
| && typeCheckingContext.getEnclosingClosure() != null) { |
| return typeCheckingContext.getEnclosingClassNode(); |
| } |
| type = ((MethodNode) node).getReturnType(); |
| return Optional.ofNullable(getInferredReturnType(node)).orElse(type); |
| } |
| if (node instanceof MethodCall) { |
| if (node instanceof ConstructorCallExpression) { |
| return ((ConstructorCallExpression) node).getType(); |
| } |
| MethodNode target = node.getNodeMetaData(DIRECT_METHOD_CALL_TARGET); |
| if (target != null) { |
| return getType(target); |
| } |
| } |
| if (node instanceof ClosureExpression) { |
| type = CLOSURE_TYPE.getPlainNodeReference(); |
| ClassNode returnType = getInferredReturnType(node); |
| if (returnType != null) { |
| type.setGenericsTypes(new GenericsType[]{ |
| new GenericsType(wrapTypeIfNecessary(returnType)) |
| }); |
| } |
| Parameter[] parameters = ((ClosureExpression) node).getParameters(); |
| int nParameters = parameters == null ? 0 |
| : parameters.length == 0 ? -1 : parameters.length; |
| type.putNodeMetaData(CLOSURE_ARGUMENTS, nParameters); |
| return type; |
| } |
| |
| if (node instanceof ListExpression) { |
| return inferListExpressionType((ListExpression) node); |
| } |
| if (node instanceof MapExpression) { |
| return inferMapExpressionType((MapExpression) node); |
| } |
| if (node instanceof RangeExpression) { |
| RangeExpression re = (RangeExpression) node; |
| ClassNode fromType = getType(re.getFrom()); |
| ClassNode toType = getType(re.getTo()); |
| if (fromType.equals(toType)) { |
| type = wrapTypeIfNecessary(fromType); |
| } else { |
| type = wrapTypeIfNecessary(lowestUpperBound(fromType, toType)); |
| } |
| return makeClassSafe0(RANGE_TYPE, new GenericsType(type)); |
| } |
| if (node instanceof SpreadExpression) { |
| type = getType(((SpreadExpression) node).getExpression()); |
| return inferComponentType(type, null); // for list literal |
| } |
| if (node instanceof UnaryPlusExpression) { |
| return getType(((UnaryPlusExpression) node).getExpression()); |
| } |
| if (node instanceof UnaryMinusExpression) { |
| return getType(((UnaryMinusExpression) node).getExpression()); |
| } |
| if (node instanceof BitwiseNegationExpression) { |
| return getType(((BitwiseNegationExpression) node).getExpression()); |
| } |
| return ((Expression) node).getType(); |
| } |
| |
| private ClassNode getTypeFromClosureArguments(final Parameter parameter) { |
| for (TypeCheckingContext.EnclosingClosure enclosingClosure : typeCheckingContext.getEnclosingClosureStack()) { |
| ClosureExpression closureExpression = enclosingClosure.getClosureExpression(); |
| ClassNode[] closureParamTypes = closureExpression.getNodeMetaData(CLOSURE_ARGUMENTS); |
| if (closureParamTypes != null) { |
| Parameter[] parameters = closureExpression.getParameters(); |
| if (parameters != null) { |
| final int n = parameters.length; |
| String parameterName = parameter.getName(); |
| if (n == 0 && parameterName.equals("it")) { |
| return closureParamTypes.length > 0 ? closureParamTypes[0] : null; |
| } |
| for (int i = 0; i < n; i += 1) { |
| if (parameterName.equals(parameters[i].getName())) { |
| return closureParamTypes.length > i ? closureParamTypes[i] : null; |
| } |
| } |
| } |
| } |
| } |
| return null; |
| } |
| |
| private static ClassNode makeSelf(final ClassNode trait) { |
| Set<ClassNode> selfTypes = Traits.collectSelfTypes(trait, new LinkedHashSet<>()); |
| if (!selfTypes.isEmpty()) { // TODO: reduce to the most-specific type(s) |
| ClassNode superclass = selfTypes.stream().filter(t -> !t.isInterface()).findFirst().orElse(OBJECT_TYPE); |
| selfTypes.remove(superclass); selfTypes.add(trait); |
| return new WideningCategories.LowestUpperBoundClassNode("TypesOf$" + trait.getNameWithoutPackage(), superclass, selfTypes.toArray(ClassNode.EMPTY_ARRAY)); |
| } |
| return trait; |
| } |
| |
| private ClassNode makeSuper() { |
| return makeType(typeCheckingContext.getEnclosingClassNode().getUnresolvedSuperClass(), typeCheckingContext.isInStaticContext); |
| } |
| |
| private ClassNode makeThis() { |
| return makeType(typeCheckingContext.getEnclosingClassNode(), typeCheckingContext.isInStaticContext); |
| } |
| |
| /** |
| * Wrap type in Class<> if usingClass==true. |
| */ |
| private static ClassNode makeType(final ClassNode cn, final boolean usingClass) { |
| if (usingClass) { |
| ClassNode clazzType = CLASS_Type.getPlainNodeReference(); |
| clazzType.setGenericsTypes(new GenericsType[]{new GenericsType(cn)}); |
| return clazzType; |
| } else { |
| return cn; |
| } |
| } |
| |
| /** |
| * Stores the inferred return type of a closure or method. We are using a |
| * separate key to store inferred return type because the inferred type of |
| * a closure is {@link Closure}, which is different from the inferred type |
| * of the code of the closure. |
| * |
| * @param node a {@link ClosureExpression} or {@link MethodNode} |
| * @param type the inferred return type of the code |
| * @return The old value of the inferred type. |
| */ |
| protected ClassNode storeInferredReturnType(final ASTNode node, final ClassNode type) { |
| if (node instanceof ClosureExpression) { |
| if (node.getNodeMetaData(INFERRED_TYPE) != null) { |
| return getInferredReturnType(node); // GROOVY-11079 |
| } else { |
| return (ClassNode) node.putNodeMetaData(INFERRED_RETURN_TYPE, type); |
| } |
| } |
| throw new IllegalArgumentException("Storing inferred return type is only allowed on closures but found " + node.getClass()); |
| } |
| |
| /** |
| * Returns the inferred return type of a closure or method, if stored on the |
| * AST node. This method doesn't perform any type inference by itself. |
| * |
| * @param node a {@link ClosureExpression} or {@link MethodNode} |
| * @return The expected return type. |
| */ |
| protected ClassNode getInferredReturnType(final ASTNode node) { |
| return node.getNodeMetaData(INFERRED_RETURN_TYPE); |
| } |
| |
| protected static boolean isNullConstant(final Expression expression) { |
| return expression instanceof ConstantExpression && ((ConstantExpression) expression).isNullExpression(); |
| } |
| |
| protected static boolean isThisExpression(final Expression expression) { |
| return expression instanceof VariableExpression && ((VariableExpression) expression).isThisExpression(); |
| } |
| |
| protected static boolean isSuperExpression(final Expression expression) { |
| if (expression instanceof PropertyExpression) { // GROOVY-11256 |
| return "super".equals(((PropertyExpression) expression).getPropertyAsString()) |
| && ((PropertyExpression) expression).getObjectExpression() instanceof ClassExpression; |
| } |
| return expression instanceof VariableExpression && ((VariableExpression) expression).isSuperExpression(); |
| } |
| |
| protected ClassNode inferListExpressionType(final ListExpression list) { |
| ClassNode listType = list.getType(); |
| |
| GenericsType[] genericsTypes = listType.getGenericsTypes(); |
| if (!asBoolean(genericsTypes) |
| || (genericsTypes.length == 1 && genericsTypes[0].isPlaceholder())) { |
| // maybe infer the element type |
| List<ClassNode> expressionTypes = list.getExpressions().stream() |
| .filter(e -> !isNullConstant(e)).map(this::getType).collect(Collectors.toList()); |
| if (!expressionTypes.isEmpty()) { |
| ClassNode subType = lowestUpperBound(expressionTypes); |
| genericsTypes = new GenericsType[]{new GenericsType(wrapTypeIfNecessary(subType))}; |
| } else { // GROOVY-11028 |
| GenericsType[] typeVars = ArrayList_TYPE.getGenericsTypes(); |
| Map<GenericsTypeName, GenericsType> spec = extractGenericsConnectionsFromArguments( |
| typeVars, Parameter.EMPTY_ARRAY, ArgumentListExpression.EMPTY_ARGUMENTS, null); |
| genericsTypes = applyGenericsContext(spec, typeVars); |
| } |
| listType = ArrayList_TYPE.getPlainNodeReference(); |
| listType.setGenericsTypes(genericsTypes); |
| } |
| |
| return listType; |
| } |
| |
| protected ClassNode inferMapExpressionType(final MapExpression map) { |
| ClassNode mapType = map.getType(); |
| |
| GenericsType[] genericsTypes = mapType.getGenericsTypes(); |
| if (!asBoolean(genericsTypes) |
| || (genericsTypes.length == 2 && genericsTypes[0].isPlaceholder() && genericsTypes[1].isPlaceholder())) { |
| // maybe infer the entry type |
| List<MapEntryExpression> entryExpressions = map.getMapEntryExpressions(); |
| int nExpressions = entryExpressions.size(); |
| if (nExpressions != 0) { |
| ClassNode keyType; |
| ClassNode valueType; |
| List<ClassNode> keyTypes = new ArrayList<>(nExpressions); |
| List<ClassNode> valueTypes = new ArrayList<>(nExpressions); |
| for (MapEntryExpression entryExpression : entryExpressions) { |
| valueType = getType(entryExpression.getValueExpression()); |
| if (!(entryExpression.getKeyExpression() instanceof SpreadMapExpression)) { |
| keyType = getType(entryExpression.getKeyExpression()); |
| } else { // GROOVY-7247 |
| valueType = GenericsUtils.parameterizeType(valueType, MAP_TYPE); |
| keyType = getCombinedBoundType(valueType.getGenericsTypes()[0]); |
| valueType = getCombinedBoundType(valueType.getGenericsTypes()[1]); |
| } |
| keyTypes.add(keyType); |
| valueTypes.add(valueType); // TODO: skip null value |
| } |
| keyType = lowestUpperBound(keyTypes); |
| valueType = lowestUpperBound(valueTypes); |
| genericsTypes = new GenericsType[]{new GenericsType(wrapTypeIfNecessary(keyType)), new GenericsType(wrapTypeIfNecessary(valueType))}; |
| } else { // GROOVY-11028 |
| GenericsType[] typeVars = LinkedHashMap_TYPE.getGenericsTypes(); |
| Map<GenericsTypeName, GenericsType> spec = extractGenericsConnectionsFromArguments( |
| typeVars, Parameter.EMPTY_ARRAY, ArgumentListExpression.EMPTY_ARGUMENTS, null); |
| genericsTypes = applyGenericsContext(spec, typeVars); |
| } |
| mapType = LinkedHashMap_TYPE.getPlainNodeReference(); |
| mapType.setGenericsTypes(genericsTypes); |
| } |
| |
| return mapType; |
| } |
| |
| /** |
| * If a method call returns a parameterized type, then perform additional |
| * inference on the return type, so that the type gets actual type arguments. |
| * For example, the method {@code Arrays.asList(T...)} is parameterized with |
| * {@code T}, which can be deduced type arguments or call arguments. |
| * |
| * @param method the method node |
| * @param arguments the method call arguments |
| * @param receiver the object expression type |
| */ |
| protected ClassNode inferReturnTypeGenerics(final ClassNode receiver, final MethodNode method, final Expression arguments) { |
| return inferReturnTypeGenerics(receiver, method, arguments, null); |
| } |
| |
| /** |
| * If a method call returns a parameterized type, then perform additional |
| * inference on the return type, so that the type gets actual type arguments. |
| * For example, the method {@code Arrays.asList(T...)} is parameterized with |
| * {@code T}, which can be deduced type arguments or call arguments. |
| * |
| * @param method the method node |
| * @param arguments the method call arguments |
| * @param receiver the object expression type |
| * @param explicitTypeHints type arguments (optional), for example {@code Collections.<String>emptyList()} |
| */ |
| protected ClassNode inferReturnTypeGenerics(final ClassNode receiver, final MethodNode method, final Expression arguments, final GenericsType[] explicitTypeHints) { |
| ClassNode returnType = method.isConstructor() ? method.getDeclaringClass() : method.getReturnType(); |
| if (!GenericsUtils.hasUnresolvedGenerics(returnType)) { |
| // GROOVY-7538: replace "Type<?>" with "Type<? extends/super X>" for any "Type<T extends/super X>" |
| if (getGenericsWithoutArray(returnType) != null) returnType = boundUnboundedWildcards(returnType); |
| |
| return returnType; |
| } |
| |
| if (method instanceof ExtensionMethodNode) { |
| ArgumentListExpression args = getExtensionArguments(receiver, method, arguments); |
| MethodNode extension = ((ExtensionMethodNode) method).getExtensionMethodNode(); |
| return inferReturnTypeGenerics(receiver, extension, args, explicitTypeHints); |
| } |
| |
| Map<GenericsTypeName, GenericsType> context = method.isStatic() || method.isConstructor() |
| ? null : extractPlaceHoldersVisibleToDeclaration(receiver, method, arguments); |
| GenericsType[] methodGenericTypes = method.isConstructor() ? method.getDeclaringClass().getGenericsTypes() : applyGenericsContext(context, method.getGenericsTypes()); |
| |
| // 1) resolve type parameters of method |
| |
| if (methodGenericTypes != null) { |
| Map<GenericsTypeName, GenericsType> resolvedPlaceholders = new HashMap<>(); |
| for (GenericsType gt : methodGenericTypes) resolvedPlaceholders.put(new GenericsTypeName(gt.getName()), gt); |
| applyGenericsConnections(extractGenericsConnectionsFromArguments(methodGenericTypes, Arrays.stream(method.getParameters()).map(param -> |
| new Parameter(applyGenericsContext(context, param.getType()), param.getName()) |
| ).toArray(Parameter[]::new), arguments, explicitTypeHints), resolvedPlaceholders); |
| |
| returnType = applyGenericsContext(resolvedPlaceholders, returnType); |
| } |
| |
| // 2) resolve type parameters of method's enclosing context |
| |
| if (context != null) { |
| returnType = applyGenericsContext(context, returnType); |
| |
| if (receiver.getGenericsTypes() == null && receiver.redirect().getGenericsTypes() != null |
| && !receiver.isGenericsPlaceHolder() && GenericsUtils.hasUnresolvedGenerics(returnType)) { |
| returnType = returnType.getPlainNodeReference(); // GROOVY-10049: not "Stream<E>" for raw type |
| } |
| } |
| |
| // 3) resolve bounds of type parameters from calling context |
| |
| returnType = applyGenericsContext(extractGenericsParameterMapOfThis(typeCheckingContext), returnType); |
| |
| return returnType; |
| } |
| |
| /** |
| * Resolves type parameters declared by method from type or call arguments. |
| */ |
| private Map<GenericsTypeName, GenericsType> extractGenericsConnectionsFromArguments(final GenericsType[] methodGenericTypes, final Parameter[] parameters, final Expression arguments, final GenericsType[] explicitTypeHints) { |
| Map<GenericsTypeName, GenericsType> resolvedPlaceholders = new HashMap<>(); |
| |
| if (asBoolean(explicitTypeHints)) { // resolve type parameters from type arguments |
| int n = methodGenericTypes.length; |
| if (n == explicitTypeHints.length) { |
| for (int i = 0; i < n; i += 1) { |
| resolvedPlaceholders.put(new GenericsTypeName(methodGenericTypes[i].getName()), explicitTypeHints[i]); |
| } |
| } |
| } else if (parameters.length > 0) { // resolve type parameters from call arguments |
| List<Expression> expressions = InvocationWriter.makeArgumentList(arguments).getExpressions(); |
| boolean isVargs = isVargs(parameters); |
| int nArguments = expressions.size(); |
| int nParams = parameters.length; |
| |
| if (isVargs ? nArguments >= nParams - 1 : nArguments == nParams) { |
| for (int i = 0; i < nArguments; i += 1) { |
| Expression argument = expressions.get(i); |
| if (isNullConstant(argument)) continue; // GROOVY-9984: skip |
| ClassNode argumentType = getDeclaredOrInferredType(argument); |
| ClassNode paramType = parameters[Math.min(i, nParams - 1)].getType(); |
| |
| if (GenericsUtils.hasUnresolvedGenerics(paramType)) { |
| // if supplying array param with multiple arguments or single non-array argument, infer using element type |
| if (isVargs && (i >= nParams || (i == nParams - 1 && (nArguments > nParams || !argumentType.isArray())))) { |
| paramType = paramType.getComponentType(); |
| } |
| |
| Map<GenericsTypeName, GenericsType> connections = new HashMap<>(); |
| |
| if ((argument instanceof ClosureExpression || argument instanceof MethodPointerExpression) && isSAMType(paramType)) { |
| // target type information |
| Tuple2<ClassNode[], ClassNode> samParamsAndReturnType = GenericsUtils.parameterizeSAM(paramType); |
| ClassNode[] q = samParamsAndReturnType.getV1(); |
| |
| // source type information |
| ClassNode returnType = isClosureWithType(argumentType) |
| ? getCombinedBoundType(argumentType.getGenericsTypes()[0]) |
| : wrapTypeIfNecessary(getInferredReturnType(argument)); |
| ClassNode[] p; |
| if (argument instanceof ClosureExpression) { |
| ClosureExpression closure = (ClosureExpression) argument; |
| p = extractTypesFromParameters(getParametersSafe(closure)); |
| } else { // argument instanceof MethodPointerExpression |
| List<MethodNode> candidates = argument.getNodeMetaData(MethodNode.class); |
| if (candidates != null && !candidates.isEmpty()) { |
| MethodPointerExpression methodPointer = (MethodPointerExpression) argument; |
| p = collateMethodReferenceParameterTypes(methodPointer, candidates.get(0)); |
| if (p.length > 0 && GenericsUtils.hasUnresolvedGenerics(returnType)) { |
| // GROOVY-11241: implicit receiver for "Optional::get" is resolved |
| if (!candidates.get(0).isStatic() && isClassClassNodeWrappingConcreteType(getType(methodPointer.getExpression()))) { |
| extractGenericsConnections(connections, q[0], p[0].redirect()); |
| } |
| for (int j = 0; j < q.length; j += 1) { |
| // SAM parameters are like arguments in this case |
| extractGenericsConnections(connections, q[j], p[j]); |
| } |
| // convert the method's generics into the SAM's generics |
| returnType = applyGenericsContext(connections, returnType); |
| p = applyGenericsContext(connections, p ); |
| |
| connections.clear(); |
| } |
| } else { |
| p = ClassNode.EMPTY_ARRAY; |
| } |
| } |
| |
| // parameters and return type correspond to the SAM's |
| for (int j = 0; j < p.length && j < q.length; j += 1) { |
| if (!isDynamicTyped(p[j])) |
| // GROOVY-10054, GROOVY-10699, GROOVY-10749, et al. |
| extractGenericsConnections(connections, wrapTypeIfNecessary(p[j]), q[j]); |
| } |
| extractGenericsConnections(connections, returnType, samParamsAndReturnType.getV2()); |
| } else { |
| extractGenericsConnections(connections, wrapTypeIfNecessary(argumentType), paramType); |
| } |
| |
| connections.forEach((gtn, gt) -> resolvedPlaceholders.merge(gtn, gt, (gt1, gt2) -> { |
| // GROOVY-10339: incorporate another witness |
| return getCombinedGenericsType(gt1, gt2); |
| })); |
| } |
| } |
| } |
| |
| // in case of "<T, U extends Type<T>>", we can learn about "T" from a resolved "U" |
| extractGenericsConnectionsForBoundTypes(methodGenericTypes, resolvedPlaceholders); |
| } |
| |
| for (GenericsType gt : methodGenericTypes) { |
| // GROOVY-8409, GROOVY-10067, et al.: provide "no type witness" mapping for param |
| resolvedPlaceholders.computeIfAbsent(new GenericsTypeName(gt.getName()), gtn -> { |
| ClassNode hash = ClassHelper.makeWithoutCaching("#" + gt.getName()); |
| hash.setRedirect(getCombinedBoundType(gt)); |
| hash.setGenericsPlaceHolder(true); |
| |
| GenericsType gtx = new GenericsType(hash, applyGenericsContext(resolvedPlaceholders, gt.getUpperBounds()), null); |
| gtx.putNodeMetaData(GenericsType.class, gt); |
| gtx.setResolved(true); |
| return gtx; |
| }); |
| } |
| |
| return resolvedPlaceholders; |
| } |
| |
| /** |
| * Given method call like "m(Collections.emptyList())", the type of the call |
| * argument is {@code List<T>} without explicit type arguments. Now, knowing |
| * the method target of "m", {@code T} could be resolved. |
| */ |
| private void resolvePlaceholdersFromImplicitTypeHints(final ClassNode[] actuals, final ArgumentListExpression argumentList, final Parameter[] parameterArray) { |
| int np = parameterArray.length; |
| for (int i = 0, n = actuals.length; np > 0 && i < n; i += 1) { |
| Parameter p = parameterArray[Math.min(i, np - 1)]; |
| ClassNode pt = p.getOriginType(), at = actuals[i]; |
| if (!isUsingGenericsOrIsArrayUsingGenerics(pt)) continue; |
| if (i >= (np - 1) && pt.isArray() && !at.isArray()) pt = pt.getComponentType(); |
| |
| var a = argumentList.getExpression(i); |
| |
| if (a instanceof ListExpression) { |
| actuals[i] = getLiteralResultType(pt, at, ArrayList_TYPE); |
| } else if (a instanceof MapExpression) { |
| actuals[i] = getLiteralResultType(pt, at, LinkedHashMap_TYPE); |
| } else if (a instanceof ConstructorCallExpression) { |
| inferDiamondType((ConstructorCallExpression) a, pt); // GROOVY-8974, GROOVY-9983, GROOVY-10086, GROOVY-10890, et al. |
| } else if (a instanceof TernaryExpression && at.getGenericsTypes() != null && at.getGenericsTypes().length == 0) { |
| // GROOVY-9983: double diamond scenario -- "m(flag ? new Type<>(...) : new Type<>(...))" |
| typeCheckingContext.pushEnclosingBinaryExpression(assignX(varX(p), a, a)); |
| a.visit(this); // re-visit with target type witness |
| typeCheckingContext.popEnclosingBinaryExpression(); |
| actuals[i] = getType(a); |
| } |
| |
| // check for method call without type arguments, with a known target |
| if (!(a instanceof MethodCall) || (a instanceof MethodCallExpression |
| && ((MethodCallExpression) a).isUsingGenerics())) continue; |
| MethodNode aNode = a.getNodeMetaData(DIRECT_METHOD_CALL_TARGET); |
| if (aNode == null || aNode.getGenericsTypes() == null) continue; |
| |
| // and unknown generics |
| if (!GenericsUtils.hasUnresolvedGenerics(at)) continue; |
| |
| while (!at.equals(pt) && !isObjectType(at) && !isGenericsPlaceHolderOrArrayOf(at) && !isGenericsPlaceHolderOrArrayOf(pt)) { |
| at = applyGenericsContext(GenericsUtils.extractPlaceholders(at), getNextSuperClass(at, pt)); |
| } |
| |
| // try to resolve placeholder(s) in argument type using parameter type |
| |
| Map<GenericsTypeName, GenericsType> linked = new HashMap<>(); |
| Map<GenericsTypeName, GenericsType> source = GenericsUtils.extractPlaceholders(at); |
| Map<GenericsTypeName, GenericsType> target = GenericsUtils.extractPlaceholders(pt); |
| |
| if (at.isGenericsPlaceHolder()) // GROOVY-10482: call argument via "def <T> T m()" |
| target.put(new GenericsTypeName(at.getUnresolvedName()), pt.asGenericsType()); |
| |
| // connect E:T from source to E:Type from target |
| for (GenericsType placeholder : aNode.getGenericsTypes()) { |
| for (Map.Entry<GenericsTypeName, GenericsType> e : source.entrySet()) { |
| if (e.getValue().getNodeMetaData(GenericsType.class) == placeholder) { |
| Optional.ofNullable(target.get(e.getKey())) |
| // skip "f(g())" for "f(T<String>)" and "<U extends Number> U g()" |
| .filter(gt -> isAssignableTo(gt.getType(), placeholder.getType())) |
| .ifPresent(gt -> linked.put(new GenericsTypeName(e.getValue().getName()), gt)); |
| break; |
| } |
| } |
| } |
| |
| actuals[i] = applyGenericsContext(linked, at); |
| } |
| } |
| |
| private static void extractGenericsConnectionsForBoundTypes(final GenericsType[] spec, final Map<GenericsTypeName, GenericsType> target) { |
| if (spec.length < 2) return; |
| Map<GenericsTypeName, GenericsType> outer = new HashMap<>(); |
| for (GenericsType tp : spec) { |
| ClassNode[] bounds = tp.getUpperBounds(); |
| if (bounds == null || bounds.length == 0) continue; |
| |
| GenericsTypeName key = new GenericsTypeName(tp.getName()); |
| GenericsType value = target.get(key); // look for specific resolved type |
| if (value == null || value.isPlaceholder() || value.isWildcard()) continue; |
| |
| Map<GenericsTypeName, GenericsType> inner = new HashMap<>(); |
| for (ClassNode bound : bounds) { |
| extractGenericsConnections(inner,value.getType(),bound); |
| } |
| inner.forEach((k, v) -> outer.merge(k, v, StaticTypeCheckingSupport::getCombinedGenericsType)); // GROOVY-5893 |
| } |
| outer.forEach(target::putIfAbsent); |
| } |
| |
| private static ClassNode[] collateMethodReferenceParameterTypes(final MethodPointerExpression source, final MethodNode target) { |
| Parameter[] params; |
| |
| if (target instanceof ExtensionMethodNode && !((ExtensionMethodNode) target).isStaticExtension()) { |
| params = ((ExtensionMethodNode) target).getExtensionMethodNode().getParameters(); |
| } else if (!target.isStatic() && source.getExpression() instanceof ClassExpression) { |
| ClassNode thisType = ((ClassExpression) source.getExpression()).getType(); |
| // there is an implicit parameter for "String::length" |
| int n = target.getParameters().length; |
| params = new Parameter[n + 1]; |
| params[0] = new Parameter(thisType, ""); |
| System.arraycopy(target.getParameters(), 0, params, 1, n); |
| } else { |
| params = target.getParameters(); |
| // GROOVY-10974, GROOVY-10975: propagate type args |
| if (!target.isStatic() && target.getDeclaringClass().getGenericsTypes() != null) { |
| ClassNode objectExprType = source.getExpression().getNodeMetaData(INFERRED_TYPE); |
| if (objectExprType == null && source.getExpression() instanceof VariableExpression) { |
| Variable variable = ((VariableExpression) source.getExpression()).getAccessedVariable(); |
| if (variable instanceof ASTNode) objectExprType = ((ASTNode) variable).getNodeMetaData(INFERRED_TYPE); |
| } |
| if (objectExprType == null) objectExprType = source.getExpression().getType(); |
| var spec = extractPlaceHolders(objectExprType, target.getDeclaringClass()); |
| return applyGenericsContext(spec, extractTypesFromParameters(params)); |
| } |
| } |
| |
| return extractTypesFromParameters(params); |
| } |
| |
| private ClassNode getDeclaredOrInferredType(final Expression expression) { |
| ClassNode declaredOrInferred; |
| // in case of "T t = new ExtendsOrImplementsT()", return T for the expression type |
| if (expression instanceof Variable && !((Variable) expression).isDynamicTyped()) { |
| declaredOrInferred = getOriginalDeclarationType(expression); // GROOVY-9996 |
| } else { |
| declaredOrInferred = getType(expression); |
| } |
| // GROOVY-10011: apply instanceof constraints to either option |
| return getInferredTypeFromTempInfo(expression, declaredOrInferred); |
| } |
| |
| private static class ExtensionMethodDeclaringClass { |
| } |
| |
| private static ArgumentListExpression getExtensionArguments(final ClassNode receiver, final MethodNode method, final Expression arguments) { |
| VariableExpression self = varX("$self", receiver); // implicit first argument |
| self.putNodeMetaData(ExtensionMethodDeclaringClass.class, method.getDeclaringClass()); |
| |
| ArgumentListExpression args = new ArgumentListExpression(); |
| args.addExpression(self); |
| if (arguments instanceof TupleExpression) { |
| for (Expression argument : (TupleExpression) arguments) { |
| args.addExpression(argument); |
| } |
| } else { |
| args.addExpression(arguments); |
| } |
| return args; |
| } |
| |
| private static boolean isDefaultExtension(final MethodNode method) { |
| return method instanceof ExtensionMethodNode && ((ExtensionMethodNode) method).getExtensionMethodNode().getDeclaringClass().equals(DGM_CLASSNODE); |
| } |
| |
| private static boolean isGenericsPlaceHolderOrArrayOf(ClassNode cn) { |
| while (cn.isArray()) cn = cn.getComponentType(); |
| return cn.isGenericsPlaceHolder(); |
| } |
| |
| private static Map<GenericsTypeName, GenericsType> extractPlaceHolders(final ClassNode receiver, final ClassNode declaringClass) { |
| Map<GenericsTypeName, GenericsType> result = null; |
| ClassNode[] todo; |
| if (receiver instanceof UnionTypeClassNode) { |
| todo = ((UnionTypeClassNode) receiver).getDelegates(); |
| } else { |
| todo = new ClassNode[] {!isPrimitiveType(declaringClass) ? wrapTypeIfNecessary(receiver) : receiver}; |
| } |
| for (ClassNode type : todo) { |
| for (ClassNode current = type; current != null; ) { |
| Map<GenericsTypeName, GenericsType> placeHolders = new HashMap<>(); |
| if (current.isGenericsPlaceHolder()) |
| // GROOVY-10622: type param bound "T extends Set<Type>" |
| current = current.asGenericsType().getUpperBounds()[0]; |
| // GROOVY-10055: handle diamond or raw type |
| else if (current.getGenericsTypes() != null |
| ? current.getGenericsTypes().length == 0 |
| : current.redirect().getGenericsTypes() != null) { |
| for (GenericsType gt : current.redirect().getGenericsTypes()) { |
| ClassNode cn = gt.getUpperBounds() != null ? gt.getUpperBounds()[0] : gt.getType().redirect(); |
| placeHolders.put(new GenericsTypeName(gt.getName()), cn.getPlainNodeReference().asGenericsType()); |
| } |
| } |
| |
| boolean currentIsDeclaring = current.equals(declaringClass) || isGenericsPlaceHolderOrArrayOf(declaringClass); |
| if (currentIsDeclaring) { |
| extractGenericsConnections(placeHolders, current, declaringClass); |
| } else { |
| GenericsUtils.extractPlaceholders(current, placeHolders); |
| } |
| |
| if (result != null) { // merge maps |
| for (Map.Entry<GenericsTypeName, GenericsType> entry : placeHolders.entrySet()) { |
| GenericsType gt = entry.getValue(); |
| if (!gt.isPlaceholder()) continue; |
| GenericsType referenced = result.get(new GenericsTypeName(gt.getName())); |
| if (referenced == null) continue; |
| entry.setValue(referenced); |
| } |
| } |
| result = placeHolders; |
| |
| // we are done if we are now in the declaring class |
| if (currentIsDeclaring) break; |
| |
| current = getNextSuperClass(current, declaringClass); |
| if (current == null && isClassType(declaringClass)) { |
| // this can happen if the receiver is Class<Foo>, then |
| // the actual receiver is Foo and declaringClass is Class |
| current = declaringClass; |
| } else { |
| current = applyGenericsContext(placeHolders, current); |
| } |
| } |
| } |
| if (result == null) { |
| throw new GroovyBugError("Declaring class " + prettyPrintTypeName(declaringClass) + " was not matched with receiver " + prettyPrintTypeName(receiver) + ". This should not have happened!"); |
| } |
| return result; |
| } |
| |
| private static Map<GenericsTypeName, GenericsType> extractPlaceHoldersVisibleToDeclaration(final ClassNode receiver, final MethodNode method, final Expression argument) { |
| Map<GenericsTypeName, GenericsType> result; |
| if (method.isStatic() || (method.isConstructor() && !asBoolean(receiver.getGenericsTypes()))) { |
| result = new HashMap<>(); |
| } else { |
| ClassNode declaring = method.getDeclaringClass(); |
| if (argument instanceof TupleExpression) { // resolve extension method class |
| List<Expression> arguments = ((TupleExpression) argument).getExpressions(); |
| if (!arguments.isEmpty()) { |
| ClassNode cn = arguments.get(0).getNodeMetaData(ExtensionMethodDeclaringClass.class); |
| if (cn != null) |
| declaring = cn; |
| } |
| } |
| result = extractPlaceHolders(receiver, declaring); |
| if (!result.isEmpty()) Optional.ofNullable(method.getGenericsTypes()).ifPresent(methodGenerics -> |
| Arrays.stream(methodGenerics).map(gt -> new GenericsTypeName(gt.getName())).forEach(result::remove)); // GROOVY-10322 |
| } |
| return result; |
| } |
| |
| protected boolean typeCheckMethodsWithGenericsOrFail(final ClassNode receiver, final ClassNode[] arguments, final MethodNode candidateMethod, final Expression location) { |
| if (!typeCheckMethodsWithGenerics(receiver, arguments, candidateMethod)) { |
| ClassNode r = receiver, at[] = arguments; MethodNode m = candidateMethod; |
| if (candidateMethod instanceof ExtensionMethodNode) { |
| m = ((ExtensionMethodNode) candidateMethod).getExtensionMethodNode(); |
| r = m.getDeclaringClass(); |
| at = new ClassNode[arguments.length + 1]; |
| at[0] = receiver; // object expression is first argument |
| System.arraycopy(arguments, 0, at, 1, arguments.length); |
| } |
| Map<GenericsTypeName, GenericsType> spec = extractPlaceHoldersVisibleToDeclaration(r, m, null); |
| GenericsType[] gt = applyGenericsContext(spec, m.getGenericsTypes()); // class params in bounds |
| GenericsUtils.extractPlaceholders(makeClassSafe0(OBJECT_TYPE, gt), spec); |
| |
| Parameter[] parameters = m.getParameters(); |
| ClassNode[] paramTypes = new ClassNode[parameters.length]; |
| for (int i = 0, n = parameters.length; i < n; i += 1) { |
| paramTypes[i] = fullyResolveType(parameters[i].getType(), spec); |
| // GROOVY-10010: check for List<String> parameter and ["foo","$bar"] argument |
| if (i < at.length && hasGStringStringError(paramTypes[i], at[i], location)) { |
| return false; |
| } |
| } |
| |
| addStaticTypeError("Cannot call " + (gt == null ? "" : GenericsUtils.toGenericTypesString(gt)) + |
| prettyPrintTypeName(r) + "#" + toMethodParametersString(m.getName(), paramTypes) + " with arguments " + formatArgumentList(at), location); |
| return false; |
| } |
| return true; |
| } |
| |
| protected static String formatArgumentList(final ClassNode[] nodes) { |
| if (nodes == null || nodes.length == 0) return "[]"; |
| |
| StringJoiner joiner = new StringJoiner(", ", "[", "]"); |
| for (ClassNode node : nodes) { |
| joiner.add(prettyPrintType(node)); |
| } |
| return joiner.toString(); |
| } |
| |
| private static void putSetterInfo(final Expression exp, final SetterInfo info) { |
| exp.putNodeMetaData(SetterInfo.class, info); |
| } |
| |
| private static SetterInfo removeSetterInfo(final Expression exp) { |
| Object nodeMetaData = exp.getNodeMetaData(SetterInfo.class); |
| if (nodeMetaData != null) { |
| exp.removeNodeMetaData(SetterInfo.class); |
| return (SetterInfo) nodeMetaData; |
| } |
| return null; |
| } |
| |
| @Override |
| public void addError(final String msg, final ASTNode node) { |
| Long err = ((long) node.getLineNumber()) << 16 + node.getColumnNumber(); |
| if ((DEBUG_GENERATED_CODE && node.getLineNumber() < 0) || !typeCheckingContext.reportedErrors.contains(err)) { |
| typeCheckingContext.getErrorCollector().addErrorAndContinue(msg + '\n', node, getSourceUnit()); |
| typeCheckingContext.reportedErrors.add(err); |
| } |
| } |
| |
| protected void addStaticTypeError(final String msg, final ASTNode node) { |
| if (node.getColumnNumber() > 0 && node.getLineNumber() > 0) { |
| addError(StaticTypesTransformation.STATIC_ERROR_PREFIX + msg, node); |
| } else { |
| if (DEBUG_GENERATED_CODE) { |
| addError(StaticTypesTransformation.STATIC_ERROR_PREFIX + "Error in generated code [" + node.getText() + "] - " + msg, node); |
| } |
| // ignore errors which are related to unknown source locations |
| // because they are likely related to generated code |
| } |
| } |
| |
| protected void addNoMatchingMethodError(final ClassNode receiver, final String name, ClassNode[] args, final Expression exp) { |
| addNoMatchingMethodError(receiver, name, args, (ASTNode)exp); |
| } |
| |
| protected void addNoMatchingMethodError(final ClassNode receiver, final String name, ClassNode[] args, final ASTNode origin) { |
| String error; |
| if ("<init>".equals(name)) { |
| // remove implicit agruments [String, int] from enum constant construction |
| if (receiver.isEnum() && args.length >= 2) args = Arrays.copyOfRange(args, 2, args.length); |
| error = "Cannot find matching constructor " + prettyPrintTypeName(receiver) + toMethodParametersString("", args); |
| } else { |
| ClassNode type = isClassClassNodeWrappingConcreteType(receiver) ? receiver.getGenericsTypes()[0].getType() : receiver; |
| error = "Cannot find matching method " + prettyPrintTypeName(type) + "#" + toMethodParametersString(name, args) + ". Please check if the declared type is correct and if the method exists."; |
| } |
| addStaticTypeError(error, origin); |
| } |
| |
| protected void addAmbiguousErrorMessage(final List<MethodNode> foundMethods, final String name, final ClassNode[] args, final Expression expr) { |
| addStaticTypeError("Reference to method is ambiguous. Cannot choose between " + prettyPrintMethodList(foundMethods), expr); |
| } |
| |
| protected void addCategoryMethodCallError(final Expression call) { |
| addStaticTypeError("Due to their dynamic nature, usage of categories is not possible with static type checking active", call); |
| } |
| |
| protected void addAssignmentError(final ClassNode leftType, final ClassNode rightType, final Expression expression) { |
| addStaticTypeError("Cannot assign value of type " + prettyPrintType(rightType) + " to variable of type " + prettyPrintType(leftType), expression); |
| } |
| |
| protected void addUnsupportedPreOrPostfixExpressionError(final Expression expression) { |
| if (expression instanceof PostfixExpression) { |
| addStaticTypeError("Unsupported postfix operation type [" + ((PostfixExpression) expression).getOperation() + "]", expression); |
| } else if (expression instanceof PrefixExpression) { |
| addStaticTypeError("Unsupported prefix operation type [" + ((PrefixExpression) expression).getOperation() + "]", expression); |
| } else { |
| throw new IllegalArgumentException("Method should be called with a PostfixExpression or a PrefixExpression"); |
| } |
| } |
| |
| public void setMethodsToBeVisited(final Set<MethodNode> methodsToBeVisited) { |
| this.typeCheckingContext.methodsToBeVisited = methodsToBeVisited; |
| } |
| |
| public void performSecondPass() { |
| for (SecondPassExpression<?> wrapper : typeCheckingContext.secondPassExpressions) { |
| Expression expression = wrapper.getExpression(); |
| if (expression instanceof BinaryExpression) { |
| Expression left = ((BinaryExpression) expression).getLeftExpression(); |
| if (left instanceof VariableExpression) { |
| Variable target = findTargetVariable((VariableExpression) left); |
| if (target instanceof VariableExpression) { |
| List<ClassNode> classNodes = typeCheckingContext.closureSharedVariablesAssignmentTypes.get(target); |
| if (classNodes != null && classNodes.size() > 1) { |
| ClassNode type = lowestUpperBound(classNodes); |
| String message = getOperationName(((BinaryExpression) expression).getOperation().getType()); |
| if (message != null) { |
| List<MethodNode> methods = findMethod(type, message, getType(((BinaryExpression) expression).getRightExpression())); |
| if (methods.isEmpty()) { |
| String methSpec = toMethodParametersString(message, getType(((BinaryExpression) expression).getRightExpression())); |
| String stcError = String.format("The closure shared variable \"%s\" has been assigned with various types and the method %s does not exist in the lowest upper bound of those types: %s", target.getName(), methSpec, prettyPrintTypeName(type)); |
| addStaticTypeError(stcError + ". In general, this style of variable reuse is a bad practice because the compiler cannot determine safely what is the type of the variable at the moment of the call in a multi-threaded context.", expression ); |
| } |
| } |
| } |
| } |
| } |
| } else if (expression instanceof MethodCallExpression) { |
| MethodCallExpression call = (MethodCallExpression) expression; |
| Expression objectExpression = call.getObjectExpression(); |
| if (objectExpression instanceof VariableExpression) { |
| // this should always be the case, but adding a test is safer |
| Variable target = findTargetVariable((VariableExpression) objectExpression); |
| if (target instanceof VariableExpression) { |
| List<ClassNode> classNodes = typeCheckingContext.closureSharedVariablesAssignmentTypes.get(target); |
| if (classNodes != null && classNodes.size() > 1) { |
| ClassNode type = lowestUpperBound(classNodes); |
| MethodNode mct = call.getNodeMetaData(DIRECT_METHOD_CALL_TARGET); |
| // we must check that such a method exists for the common type(s) |
| List<MethodNode> methods = findMethod(type, mct.getName(), (ClassNode[]) wrapper.getData()); |
| if (methods.size() != 1) { |
| String methSpec = toMethodParametersString(mct.getName(), extractTypesFromParameters(mct.getParameters())); |
| String stcError = String.format("The closure shared variable \"%s\" has been assigned with various types and the method %s does not exist in the lowest upper bound of those types: %s", target.getName(), methSpec, prettyPrintTypeName(type)); |
| addStaticTypeError(stcError + ". In general, this style of variable reuse is a bad practice because the compiler cannot determine safely what is the type of the variable at the moment of the call in a multi-threaded context.", expression ); |
| } |
| } |
| } |
| } |
| } |
| } |
| // give a chance to type checker extensions to throw errors based on information gathered afterwards |
| extension.finish(); |
| } |
| |
| protected static ClassNode[] extractTypesFromParameters(final Parameter[] parameters) { |
| return Arrays.stream(parameters).map(Parameter::getType).toArray(ClassNode[]::new); |
| } |
| |
| /** |
| * Returns a wrapped type if, and only if, the provided class node is a primitive type. |
| * This method differs from {@link ClassHelper#getWrapper(org.codehaus.groovy.ast.ClassNode)} as it will |
| * return the same instance if the provided type is not a generic type. |
| * |
| * @return the wrapped type |
| */ |
| protected static ClassNode wrapTypeIfNecessary(final ClassNode type) { |
| return (type != null && isPrimitiveType(type) ? getWrapper(type) : type); |
| } |
| |
| protected static boolean isClassInnerClassOrEqualTo(final ClassNode toBeChecked, final ClassNode start) { |
| if (start == toBeChecked) return true; |
| ClassNode outer = start.getOuterClass(); |
| if (outer != null) { |
| return isClassInnerClassOrEqualTo(toBeChecked, outer); |
| } |
| return false; |
| } |
| |
| private static boolean isNonStaticHelperMethod(final MethodNode method) { |
| Parameter[] parameters = method.getParameters(); // check first param is "$self" |
| if (parameters.length > 0 && parameters[0].getName().equals(Traits.THIS_OBJECT)) { |
| return !method.getName().contains("$init$") && Traits.isTrait(method.getDeclaringClass().getOuterClass()); |
| } |
| return false; |
| } |
| |
| private static BinaryExpression assignX(final Expression lhs, final Expression rhs, final ASTNode pos) { |
| BinaryExpression exp = (BinaryExpression) GeneralUtils.assignX(lhs, rhs); |
| exp.setSourcePosition(pos); |
| exp.setSynthetic(true); |
| return exp; |
| } |
| |
| //-------------------------------------------------------------------------- |
| // temporaryIfBranchTypeInformation support; migrate to TypeCheckingContext? |
| |
| /** |
| * Stores information about types when [objectOfInstanceof instanceof typeExpression] is visited. |
| * |
| * @param objectOfInstanceOf the expression to be checked against instanceof |
| * @param typeExpression the expression which represents the target type |
| */ |
| protected void pushInstanceOfTypeInfo(final Expression objectOfInstanceOf, final Expression typeExpression) { |
| Object ttiKey = extractTemporaryTypeInfoKey(objectOfInstanceOf); ClassNode type = typeExpression.getType(); |
| typeCheckingContext.temporaryIfBranchTypeInformation.peek().computeIfAbsent(ttiKey, x -> new LinkedList<>()).add(type); |
| } |
| |
| private void putNotInstanceOfTypeInfo(final Object key, final Collection<ClassNode> types) { |
| Object notKey = key instanceof Object[] ? ((Object[]) key)[1] : new Object[]{"!instanceof", key}; // stash negative type(s) |
| typeCheckingContext.temporaryIfBranchTypeInformation.peek().computeIfAbsent(notKey, x -> new LinkedList<>()).addAll(types); |
| } |
| |
| /** |
| * Computes the key to use for {@link TypeCheckingContext#temporaryIfBranchTypeInformation}. |
| */ |
| protected Object extractTemporaryTypeInfoKey(final Expression expression) { |
| return expression instanceof VariableExpression ? findTargetVariable((VariableExpression) expression) : expression.getText(); |
| } |
| |
| /** |
| * A helper method which determines which receiver class should be used in error messages when a field or attribute |
| * is not found. The returned type class depends on whether we have temporary type information available (due to |
| * instanceof checks) and whether there is a single candidate in that case. |
| * |
| * @param expression the expression for which an unknown field has been found |
| * @param type the type of the expression (used as fallback type) |
| * @return if temporary information is available and there's only one type, returns the temporary type class |
| * otherwise falls back to the provided type class. |
| */ |
| protected ClassNode findCurrentInstanceOfClass(final Expression expression, final ClassNode type) { |
| List<ClassNode> tempTypes = getTemporaryTypesForExpression(expression); |
| if (tempTypes.size() == 1) return tempTypes.get(0); |
| return type; |
| } |
| |
| protected List<ClassNode> getTemporaryTypesForExpression(final Expression expression) { |
| Object key = extractTemporaryTypeInfoKey(expression); |
| List<ClassNode> tempTypes = typeCheckingContext.temporaryIfBranchTypeInformation.stream().flatMap(tti -> |
| tti.getOrDefault(key, Collections.emptyList()).stream() |
| ).collect(Collectors.toList()); |
| int i = tempTypes.lastIndexOf(VOID_TYPE); |
| if (i != -1) { // assignment overwrites instanceof |
| tempTypes = tempTypes.subList(i + 1, tempTypes.size()); |
| } |
| return DefaultGroovyMethods.unique(tempTypes); // GROOVY-6429 |
| } |
| |
| private ClassNode getInferredTypeFromTempInfo(final Expression expression, final ClassNode expressionType) { |
| if (expression instanceof VariableExpression) { |
| List<ClassNode> tempTypes = getTemporaryTypesForExpression(expression); |
| if (!tempTypes.isEmpty()) { |
| ClassNode superclass; |
| ClassNode[] interfaces; |
| if (expressionType instanceof WideningCategories.LowestUpperBoundClassNode) { |
| superclass = expressionType.getSuperClass(); |
| interfaces = expressionType.getInterfaces(); |
| } else if (expressionType != null && expressionType.isInterface()) { |
| superclass = OBJECT_TYPE; |
| interfaces = new ClassNode[]{expressionType}; |
| } else { |
| superclass = expressionType; |
| interfaces = ClassNode.EMPTY_ARRAY; |
| } |
| |
| List<ClassNode> types = new ArrayList<>(); |
| if (superclass != null && !superclass.equals(OBJECT_TYPE) // GROOVY-7333 |
| && tempTypes.stream().noneMatch(t -> !t.equals(superclass) && t.isDerivedFrom(superclass))) { // GROOVY-9769 |
| types.add(superclass); |
| } |
| for (ClassNode anInterface : interfaces) { |
| if (tempTypes.stream().noneMatch(t -> t.implementsInterface(anInterface))) { // GROOVY-9769 |
| types.add(anInterface); |
| } |
| } |
| int tempTypesCount = tempTypes.size(); |
| if (tempTypesCount == 1 && types.isEmpty()) { |
| types.add(tempTypes.get(0)); |
| } else for (ClassNode tempType : tempTypes) { |
| if (!tempType.isInterface() // GROOVY-11290: keep most-specific types |
| ? (superclass == null || !superclass.isDerivedFrom(tempType)) |
| && (tempTypesCount == 1 || tempTypes.stream().noneMatch(t -> !t.equals(tempType) && t.isDerivedFrom(tempType))) |
| : (expressionType == null || !isOrImplements(expressionType, tempType)) |
| && (tempTypesCount == 1 || tempTypes.stream().noneMatch(t -> t != tempType && t.implementsInterface(tempType)))) { |
| types.add(tempType); |
| } |
| } |
| |
| int typesCount = types.size(); |
| if (typesCount == 1) { |
| return types.get(0); |
| } else if (typesCount > 1) { |
| return new UnionTypeClassNode(types.toArray(ClassNode.EMPTY_ARRAY)); |
| } |
| } |
| } |
| return expressionType; |
| } |
| |
| //-------------------------------------------------------------------------- |
| |
| public static class SignatureCodecFactory { |
| public static SignatureCodec getCodec(final int version, final ClassLoader classLoader) { |
| switch (version) { |
| case 1: |
| return new SignatureCodecVersion1(classLoader); |
| default: |
| return null; |
| } |
| } |
| } |
| |
| // class only used to store setter information when an expression of type |
| // a.x = foo or x=foo is found and that it corresponds to a setter call |
| private static class SetterInfo { |
| final ClassNode receiverType; |
| final String name; |
| final List<MethodNode> setters; |
| |
| private SetterInfo(final ClassNode receiverType, final String name, final List<MethodNode> setters) { |
| this.receiverType = receiverType; |
| this.setters = setters; |
| this.name = name; |
| } |
| } |
| |
| private class PropertyLookup extends ClassCodeVisitorSupport { |
| private final ClassNode receiverType; |
| private final Consumer<ClassNode> propertyType; |
| |
| PropertyLookup(final ClassNode receiverType, final Consumer<ClassNode> propertyType) { |
| this.receiverType = receiverType; |
| this.propertyType = propertyType; |
| } |
| |
| @Override |
| protected SourceUnit getSourceUnit() { |
| return StaticTypeCheckingVisitor.this.getSourceUnit(); |
| } |
| |
| @Override |
| public void visitField(final FieldNode node) { |
| reportPropertyType(node.getType(), node.isStatic() ? null : node.getDeclaringClass()); |
| } |
| |
| @Override |
| public void visitMethod(final MethodNode node) { |
| reportPropertyType(node.getReturnType(), node.isStatic() ? null : node.getDeclaringClass()); |
| } |
| |
| @Override |
| public void visitProperty(final PropertyNode node) { |
| reportPropertyType(node.getOriginType(), node.isStatic() ? null : node.getDeclaringClass()); |
| } |
| |
| private void reportPropertyType(ClassNode type, final ClassNode declaringClass) { |
| if (declaringClass != null && GenericsUtils.hasUnresolvedGenerics(type)) { // GROOVY-10787 |
| Map<GenericsTypeName, GenericsType> spec = extractPlaceHolders(receiverType, declaringClass); |
| type = applyGenericsContext(spec, type); |
| } |
| propertyType.accept(type); |
| } |
| } |
| |
| /** |
| * Wrapper for a Parameter so it can be treated like a VariableExpression |
| * and tracked in the {@code ifElseForWhileAssignmentTracker}. |
| */ |
| private class ParameterVariableExpression extends VariableExpression { |
| private final Parameter parameter; |
| |
| ParameterVariableExpression(final Parameter parameter) { |
| super(parameter); |
| this.parameter = parameter; |
| |
| ClassNode inferredType = getNodeMetaData(INFERRED_TYPE); |
| if (inferredType == null) { |
| inferredType = typeCheckingContext.controlStructureVariables.get(parameter); // for/catch/closure |
| if (inferredType == null) { |
| inferredType = getTypeFromClosureArguments(parameter); // @ClosureParams or SAM-type coercion |
| } |
| setNodeMetaData(INFERRED_TYPE, inferredType != null ? inferredType : parameter.getType()); // GROOVY-10651 |
| } |
| } |
| |
| @Override |
| public Map<?, ?> getMetaDataMap() { |
| return parameter.getMetaDataMap(); |
| } |
| |
| @Override |
| public void setMetaDataMap(final Map<?, ?> metaDataMap) { |
| parameter.setMetaDataMap(metaDataMap); |
| } |
| } |
| |
| protected class VariableExpressionTypeMemoizer extends ClassCodeVisitorSupport { |
| private final boolean onlySharedVariables; |
| private final Set<VariableExpression> decl = new HashSet<>(); |
| private final Map<VariableExpression, ClassNode> varOrigType; |
| |
| public VariableExpressionTypeMemoizer(final Map<VariableExpression, ClassNode> varOrigType) { |
| this(varOrigType, false); |
| } |
| |
| public VariableExpressionTypeMemoizer(final Map<VariableExpression, ClassNode> varOrigType, final boolean onlySharedVariables) { |
| this.varOrigType = varOrigType; |
| this.onlySharedVariables = onlySharedVariables; |
| } |
| |
| @Override |
| protected SourceUnit getSourceUnit() { |
| return StaticTypeCheckingVisitor.this.getSourceUnit(); |
| } |
| |
| private boolean isOuterScopeShared(final Variable var) { |
| return var.isClosureSharedVariable() && !decl.contains(var); |
| } |
| |
| @Override |
| public void visitVariableExpression(final VariableExpression expression) { |
| Variable var = findTargetVariable(expression); |
| if ((!onlySharedVariables || isOuterScopeShared(var)) && var instanceof VariableExpression) { |
| VariableExpression ve = (VariableExpression) var; |
| ClassNode cn = ve.getNodeMetaData(INFERRED_TYPE); |
| if (cn == null) cn = ve.getOriginType(); |
| varOrigType.put(ve, cn); |
| } |
| super.visitVariableExpression(expression); |
| } |
| |
| @Override |
| @SuppressWarnings("unchecked") |
| public void visitDeclarationExpression(final DeclarationExpression expression) { |
| if (expression.isMultipleAssignmentDeclaration()) { |
| List<?> vars = expression.getTupleExpression().getExpressions(); |
| decl.addAll((List<VariableExpression>) vars); |
| } else { |
| decl.add(expression.getVariableExpression()); |
| } |
| super.visitDeclarationExpression(expression); |
| } |
| } |
| } |