protected boolean existsProperty()

in src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java [1551:1840]


    protected boolean existsProperty(final PropertyExpression pexp, final boolean readMode, final ClassCodeVisitorSupport visitor) {
        super.visitPropertyExpression(pexp);

        final String propertyName = pexp.getPropertyAsString();
        if (propertyName == null) return false;

        Expression objectExpression = pexp.getObjectExpression();
        if (objectExpression instanceof ConstructorCallExpression) {
            ClassNode rawType = getType(objectExpression).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());

        if (objectExpression instanceof ClassExpression) {
            if ("this".equals(propertyName)) {
                // handle "Outer.this" for any level of nesting
                ClassNode outer = getType(objectExpression).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 accessingType = getType(objectExpression).getGenericsTypes()[0].getType();
                if (accessingType.isInterface() && enclosingType.implementsInterface(accessingType)) {
                    storeType(pexp, accessingType);
                    return true;
                }
                return false;
            }
        }

        final String isserName  = getGetterName(propertyName, Boolean.TYPE);
        final String getterName = getGetterName(propertyName);
        final String setterName = getSetterName(propertyName);

        boolean foundGetterOrSetter = false;
        Set<ClassNode> handledTypes = 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;
            }

            // in case of a lookup on java.lang.Class, look for instance methods on Class
            // in case of static property access, Type (static) and Class<Type> are tried
            boolean staticOnly = !receiver.isObject();
            // GROOVY-11367, GROOVY-11384, GROOVY-11387: entry before a non-public member
            boolean publicOnly = !staticOnly && isOrImplements(receiverType, MAP_TYPE);

            List<MethodNode> setters = new ArrayList<>(4);
            for (MethodNode method : findMethodsWithGenerated(wrapTypeIfNecessary(receiverType), setterName)) {
                if (method.getParameters().length == 1 && (!staticOnly || method.isStatic()) && (!publicOnly || method.isPublic())
                        // GROOVY-11319:
                        && hasAccessToMember(typeCheckingContext.getEnclosingClassNode(), method.getDeclaringClass(), method.getModifiers())) {
                    setters.add(method);
                }
            }
            // GROOVY-11372:
            var loader = getSourceUnit().getClassLoader();
            var dgmSet = (TreeSet<MethodNode>) findDGMMethodsForClassNode(loader,            receiverType,  setterName);
            if (isPrimitiveType(receiverType)) findDGMMethodsForClassNode(loader, getWrapper(receiverType), setterName, dgmSet);
            for (MethodNode method : dgmSet) {
                if (method.getParameters().length == 1 && (!staticOnly || method.isStatic())) {
                    setters.add(method);
                }
            }

            var queue = new LinkedList<ClassNode>();
            queue.add(receiverType);
            if (isPrimitiveType(receiverType)) {
                queue.add(getWrapper(receiverType));
            } else if (receiverType.isInterface()) {
                queue.add(OBJECT_TYPE);//GROOVY-5585
            }
            while (!queue.isEmpty()) {
                ClassNode current = queue.remove();
                if (!handledTypes.add(current)) continue;

                FieldNode field = current.getDeclaredField(propertyName);
                if (field == null) {
                    if (current.getSuperClass() != null)
                        queue.addFirst(current.getSuperClass());
                    Collections.addAll(queue, current.getInterfaces());
                }
                else 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;
                }

                if (field != null && publicOnly && !field.isPublic()
                        // GROOVY-11367, GROOVY-11402, GROOVY-11403:
                        && (!receiverType.equals(current) || !isThisExpression(objectExpression) || typeCheckingContext.getEnclosingClosure() != null)) {
                    field = null;
                }
                // skip property/accessor checks for "field", "this.field", "this.with { field }", etc. within the declaring class of the field
                else if (field != null && enclosingTypes.contains(current) && storeField(field, pexp, receiverType, visitor, receiver.getData(), !readMode)) {
                    return true;
                }

                MethodNode getter = current.getGetterMethod(isserName);
                getter = allowStaticAccessToMember(getter, staticOnly);
                if (getter == null) {
                    getter = current.getGetterMethod(getterName);
                    getter = allowStaticAccessToMember(getter, staticOnly);
                }
                if (getter != null && ((publicOnly && (!getter.isPublic() || "class".equals(propertyName) || "empty".equals(propertyName)))
                        // GROOVY-11319:
                        || !hasAccessToMember(typeCheckingContext.getEnclosingClassNode(), getter.getDeclaringClass(), getter.getModifiers()))) {
                    getter = null;
                }

                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)) {
                    if (readMode) {
                        if (getter != null) {
                            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 {
                        if (!setters.isEmpty()) {
                            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, setterName, 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 (getter != null && (field == null || field.isFinal())) {
                            pexp.putNodeMetaData(READONLY_PROPERTY, Boolean.TRUE); // GROOVY-9127
                        }
                    }
                }

                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, GROOVY-9115, GROOVY-9123: the property may be defined by an extension
            for (ClassNode dgmReceiver : isPrimitiveType(receiverType) ? new ClassNode[]{receiverType, getWrapper(receiverType)} : new ClassNode[]{receiverType}) {
                Set<MethodNode> methods = findDGMMethodsForClassNode(loader, dgmReceiver, getterName);
                for (MethodNode method : findDGMMethodsForClassNode(loader, dgmReceiver, isserName)) {
                    if (isPrimitiveBoolean(method.getReturnType())) methods.add(method);
                }
                if (!receiver.isObject()) {
                    // 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)
                    );
                }
                List<MethodNode> bestMethods = chooseBestMethod(dgmReceiver, methods, ClassNode.EMPTY_ARRAY);
                if (bestMethods.size() == 1) {
                    if (readMode) {
                        MethodNode getter = bestMethods.get(0);
                        if (visitor != null) {
                            visitor.visitMethod(getter);
                        }
                        ClassNode returnType = inferReturnTypeGenerics(dgmReceiver, getter, ArgumentListExpression.EMPTY_ARGUMENTS);
                        storeInferredTypeForPropertyExpression(pexp, returnType);
                        storeTargetMethod(pexp, getter);
                        return true;
                    } else if (setters.isEmpty()) { // GROOVY-11372
                        pexp.putNodeMetaData(READONLY_PROPERTY, Boolean.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;
                }
            }

            // GROOVY-11401
            if (!staticOnly && isOrImplements(receiverType, MAP_TYPE)) break;
        }

        for (Receiver<String> receiver : receivers) {
            if (receiver.isObject()) {
                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) {
                    PropertyNode node = new PropertyNode(propertyName, Opcodes.ACC_PUBLIC, propertyType, receiverType, null, null, null);
                    node.setDeclaringClass(receiverType);
                    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()) || typeCheckingContext.isInStaticContext;
            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;
    }