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;
}