All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.sonar.python.semantic.ClassSymbolImpl Maven / Gradle / Ivy

There is a newer version: 4.25.0.19056
Show newest version
/*
 * SonarQube Python Plugin
 * Copyright (C) 2011-2023 SonarSource SA
 * mailto:info AT sonarsource DOT com
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
package org.sonar.python.semantic;


import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import org.sonar.plugins.python.api.LocationInFile;
import org.sonar.plugins.python.api.PythonFile;
import org.sonar.plugins.python.api.symbols.AmbiguousSymbol;
import org.sonar.plugins.python.api.symbols.ClassSymbol;
import org.sonar.plugins.python.api.symbols.Symbol;
import org.sonar.plugins.python.api.tree.ClassDef;
import org.sonar.python.index.ClassDescriptor;
import org.sonar.python.types.TypeShed;
import org.sonar.python.types.protobuf.SymbolsProtos;

import static org.sonar.python.semantic.SymbolUtils.isPrivateName;
import static org.sonar.python.semantic.SymbolUtils.pathOf;
import static org.sonar.python.tree.TreeUtils.locationInFile;
import static org.sonar.python.types.TypeShed.isValidForProjectPythonVersion;
import static org.sonar.python.types.TypeShed.symbolsFromProtobufDescriptors;

public class ClassSymbolImpl extends SymbolImpl implements ClassSymbol {

  private final List superClasses = new ArrayList<>();
  private List superClassesFqns = new ArrayList<>();
  private List inlinedSuperClassFqn = new ArrayList<>();
  private Set allSuperClasses = null;
  private Set allSuperClassesIncludingAmbiguousSymbols = null;
  private boolean hasSuperClassWithoutSymbol = false;
  private final Set members = new HashSet<>();
  private Map membersByName = null;
  private boolean hasAlreadyReadSuperClasses = false;
  private boolean hasAlreadyReadMembers = false;
  private boolean hasDecorators = false;
  private boolean hasMetaClass = false;
  private final LocationInFile classDefinitionLocation;
  @Nullable
  private String metaclassFQN = null;
  private boolean supportsGenerics = false;

  public ClassSymbolImpl(ClassDef classDef, @Nullable String fullyQualifiedName, PythonFile pythonFile) {
    super(classDef.name().name(), fullyQualifiedName);
    this.setKind(Kind.CLASS);
    Path path = pathOf(pythonFile);
    String fileId = path != null ? path.toString() : pythonFile.toString();
    hasDecorators = !classDef.decorators().isEmpty();
    classDefinitionLocation = locationInFile(classDef.name(), fileId);
  }

  public ClassSymbolImpl(String name, @Nullable String fullyQualifiedName) {
    super(name, fullyQualifiedName);
    classDefinitionLocation = null;
    hasDecorators = false;
    hasMetaClass = false;
    metaclassFQN = null;
    supportsGenerics = false;
    setKind(Kind.CLASS);
  }

  public ClassSymbolImpl(String name, @Nullable String fullyQualifiedName, LocationInFile location) {
    super(name, fullyQualifiedName);
    classDefinitionLocation = location;
    hasDecorators = false;
    hasMetaClass = false;
    metaclassFQN = null;
    supportsGenerics = false;
    setKind(Kind.CLASS);
  }

  public ClassSymbolImpl(ClassDescriptor classDescriptor, String symbolName) {
    super(symbolName, classDescriptor.fullyQualifiedName());
    setKind(Kind.CLASS);
    classDefinitionLocation = classDescriptor.definitionLocation();
    hasDecorators = classDescriptor.hasDecorators();
    hasMetaClass = classDescriptor.hasMetaClass();
    metaclassFQN = classDescriptor.metaclassFQN();
    supportsGenerics = classDescriptor.supportsGenerics();
    hasSuperClassWithoutSymbol = classDescriptor.hasSuperClassWithoutDescriptor();
  }

  public static ClassSymbol copyFrom(String name, ClassSymbol classSymbol) {
    return new ClassSymbolImpl(name, classSymbol);
  }

  private ClassSymbolImpl(String name, ClassSymbol classSymbol) {
    super(name, classSymbol.fullyQualifiedName());
    classDefinitionLocation = classSymbol.definitionLocation();
    hasDecorators = classSymbol.hasDecorators();
    hasMetaClass = ((ClassSymbolImpl) classSymbol).hasMetaClass();
    metaclassFQN = ((ClassSymbolImpl) classSymbol).metaclassFQN;
    supportsGenerics = ((ClassSymbolImpl) classSymbol).supportsGenerics;
    validForPythonVersions = ((ClassSymbolImpl) classSymbol).validForPythonVersions;
    superClassesFqns = ((ClassSymbolImpl) classSymbol).superClassesFqns;
    setKind(Kind.CLASS);
  }

  public ClassSymbolImpl(SymbolsProtos.ClassSymbol classSymbolProto, String moduleName) {
    super(classSymbolProto.getName(), TypeShed.normalizedFqn(classSymbolProto.getFullyQualifiedName(), moduleName, classSymbolProto.getName()));
    setKind(Kind.CLASS);
    classDefinitionLocation = null;
    hasDecorators = classSymbolProto.getHasDecorators();
    hasMetaClass = classSymbolProto.getHasMetaclass();
    metaclassFQN = classSymbolProto.getMetaclassName();
    supportsGenerics = classSymbolProto.getIsGeneric();
    Set classMembers = new HashSet<>();
    Map> descriptorsByFqn = new HashMap<>();
    classSymbolProto.getMethodsList().stream()
      .filter(d -> isValidForProjectPythonVersion(d.getValidForList()))
      .forEach(proto -> descriptorsByFqn.computeIfAbsent(proto.getFullyQualifiedName(), d -> new HashSet<>()).add(proto));
    classSymbolProto.getOverloadedMethodsList().stream()
      .filter(d -> isValidForProjectPythonVersion(d.getValidForList()))
      .forEach(proto -> descriptorsByFqn.computeIfAbsent(proto.getFullname(), d -> new HashSet<>()).add(proto));
    classSymbolProto.getAttributesList().stream()
      .filter(d -> isValidForProjectPythonVersion(d.getValidForList()))
      .forEach(proto -> descriptorsByFqn.computeIfAbsent(proto.getFullyQualifiedName(), d -> new HashSet<>()).add(proto));

    inlineInheritedMethodsFromPrivateClass(classSymbolProto.getSuperClassesList(), descriptorsByFqn);

    for (Map.Entry> entry : descriptorsByFqn.entrySet()) {
      Set symbols = symbolsFromProtobufDescriptors(entry.getValue(), fullyQualifiedName, moduleName, true);
      classMembers.add(symbols.size() > 1 ? AmbiguousSymbolImpl.create(symbols) : symbols.iterator().next());
    }
    addMembers(classMembers);
    superClassesFqns.addAll(classSymbolProto.getSuperClassesList().stream().map(TypeShed::normalizedFqn).collect(Collectors.toList()));
    superClassesFqns.removeAll(inlinedSuperClassFqn);
    validForPythonVersions = new HashSet<>(classSymbolProto.getValidForList());
  }

  private void inlineInheritedMethodsFromPrivateClass(List superClassesFqns, Map> descriptorsByFqn) {
    for (String superClassFqn : superClassesFqns) {
      if (isPrivateName(superClassFqn)) {
        SymbolsProtos.ClassSymbol superClass = TypeShed.classDescriptorWithFQN(superClassFqn);
        if (superClass == null) return;
        inlinedSuperClassFqn.add(superClassFqn);
        for (SymbolsProtos.FunctionSymbol functionSymbol : superClass.getMethodsList()) {
          String methodFqn = this.fullyQualifiedName + "." + functionSymbol.getName();
          descriptorsByFqn.computeIfAbsent(methodFqn, d -> new HashSet<>()).add(functionSymbol);
        }
        for (SymbolsProtos.OverloadedFunctionSymbol functionSymbol : superClass.getOverloadedMethodsList()) {
          String methodFqn = this.fullyQualifiedName + "." + functionSymbol.getName();
          descriptorsByFqn.computeIfAbsent(methodFqn, d -> new HashSet<>()).add(functionSymbol);
        }
        this.superClassesFqns.addAll(superClass.getSuperClassesList());
      }
    }
  }

  @Override
  public ClassSymbolImpl copyWithoutUsages() {
    ClassSymbolImpl copiedClassSymbol = new ClassSymbolImpl(name(), this);
    if (hasEvaluatedSuperClasses()) {
      for (Symbol superClass : superClasses()) {
        if (superClass == this) {
          copiedClassSymbol.superClasses.add(copiedClassSymbol);
        } else if (superClass.is(Kind.CLASS, Kind.AMBIGUOUS)) {
          copiedClassSymbol.superClasses.add(((SymbolImpl) superClass).copyWithoutUsages());
        } else {
          copiedClassSymbol.superClasses.add(new SymbolImpl(superClass.name(), superClass.fullyQualifiedName()));
        }
      }
    }
    copiedClassSymbol.addMembers(members.stream().map(m -> ((SymbolImpl) m).copyWithoutUsages()).collect(Collectors.toList()));
    if (hasSuperClassWithoutSymbol) {
      copiedClassSymbol.setHasSuperClassWithoutSymbol();
    }
    return copiedClassSymbol;
  }

  @Override
  public List superClasses() {
    // In case of symbols coming from TypeShed protobuf, we resolve superclasses lazily
    if (!hasAlreadyReadSuperClasses && superClasses.isEmpty() && !superClassesFqns.isEmpty()) {
      superClassesFqns.stream().map(SymbolUtils::typeshedSymbolWithFQN).forEach(this::addSuperClass);
    }
    hasAlreadyReadSuperClasses = true;
    return Collections.unmodifiableList(superClasses);
  }

  public void addSuperClass(Symbol symbol) {
    if (hasAlreadyReadSuperClasses) {
      throw new IllegalStateException("Cannot call addSuperClass, super classes were already read");
    }
    this.superClasses.add(symbol);
  }

  @Override
  public boolean hasUnresolvedTypeHierarchy() {
    return hasUnresolvedTypeHierarchy(true);
  }

  public boolean hasUnresolvedTypeHierarchy(boolean includeAmbiguousSymbols) {
    for (Symbol superClassSymbol : allSuperClasses(includeAmbiguousSymbols)) {
      if (superClassSymbol.kind() != Kind.CLASS) {
        return true;
      }

      ClassSymbolImpl superClass = (ClassSymbolImpl) superClassSymbol;
      if (superClass.hasSuperClassWithoutSymbol) {
        return true;
      }
    }
    return false;
  }

  @Override
  public Set declaredMembers() {
    hasAlreadyReadMembers = true;
    return members;
  }

  @Override
  public Optional resolveMember(String memberName) {
    for (Symbol symbol : allSuperClasses(false)) {
      if (symbol.kind() == Kind.CLASS) {
        ClassSymbolImpl classSymbol = (ClassSymbolImpl) symbol;
        Symbol matchingMember = classSymbol.membersByName().get(memberName);
        if (matchingMember != null) {
          return Optional.of(matchingMember);
        }
      }
    }
    return Optional.empty();
  }

  public boolean hasMetaClass() {
    return hasMetaClass || membersByName().get("__metaclass__") != null;
  }

  @Override
  public boolean canHaveMember(String memberName) {
    if (hasUnresolvedTypeHierarchy() || hasSuperClassWithUnknownMetaClass()) {
      return true;
    }
    for (Symbol symbol : allSuperClasses(true)) {
      if (symbol.kind() == Kind.CLASS) {
        ClassSymbolImpl classSymbol = (ClassSymbolImpl) symbol;
        Symbol matchingMember = classSymbol.membersByName().get(memberName);
        if (matchingMember != null) {
          return true;
        }
      }
    }
    return false;
  }

  public boolean hasSuperClassWithUnknownMetaClass() {
    for (Symbol symbol : allSuperClasses(true)) {
      if (symbol.is(Kind.CLASS)) {
        ClassSymbolImpl superClass = (ClassSymbolImpl) symbol;
        // excluding ABCMeta because it doesn't add extra methods and to avoid FN for typeshed symbols
        if (superClass.hasMetaClass() && !"abc.ABCMeta".equals(superClass.metaclassFQN())) {
          return true;
        }
      }
    }
    return false;
  }

  @Override
  public LocationInFile definitionLocation() {
    return classDefinitionLocation;
  }

  @Override
  public boolean isOrExtends(String fullyQualifiedClassName) {
    return allSuperClasses(false).stream().anyMatch(c -> c.fullyQualifiedName() != null && Objects.equals(fullyQualifiedClassName, c.fullyQualifiedName()));
  }

  @Override
  public boolean isOrExtends(ClassSymbol other) {
    if ("object".equals(other.fullyQualifiedName())) {
      return true;
    }
    // TODO there should be only 1 class with a given fullyQualifiedName when analyzing a python file
    return allSuperClasses(false).stream().anyMatch(c -> Objects.equals(c.fullyQualifiedName(), other.fullyQualifiedName()));
  }

  @Override
  public boolean canBeOrExtend(String fullyQualifiedClassName) {
    if ("object".equals(fullyQualifiedClassName)) {
      return true;
    }
    return allSuperClasses(true).stream().anyMatch(c -> c.fullyQualifiedName() != null && Objects.equals(fullyQualifiedClassName, c.fullyQualifiedName()))
      || hasUnresolvedTypeHierarchy();
  }

  @Override
  public boolean hasDecorators() {
    return hasDecorators;
  }

  private Map membersByName() {
    if (membersByName == null) {
      membersByName = declaredMembers().stream().collect(Collectors.toMap(Symbol::name, m -> m, (s1, s2) -> s1));
    }
    return membersByName;
  }

  public void addMembers(Collection members) {
    if (hasAlreadyReadMembers) {
      throw new IllegalStateException("Cannot call addMembers, members were already read");
    }
    this.members.addAll(members);
    members.stream()
      .filter(m -> m.kind() == Kind.FUNCTION)
      .forEach(m -> ((FunctionSymbolImpl) m).setOwner(this));
  }

  public void setHasSuperClassWithoutSymbol() {
    this.hasSuperClassWithoutSymbol = true;
  }

  public void setHasMetaClass() {
    this.hasMetaClass = true;
  }

  public void setMetaclassFQN(String metaclassFQN) {
    this.metaclassFQN = metaclassFQN;
  }

  @CheckForNull
  public String metaclassFQN() {
    return metaclassFQN;
  }

  private Set allSuperClasses(boolean includeAmbiguousSymbols) {
    if (!includeAmbiguousSymbols) {
      if (allSuperClasses == null) {
        allSuperClasses = new LinkedHashSet<>();
        exploreSuperClasses(this, allSuperClasses, false);
      }
      return allSuperClasses;
    }
    if (allSuperClassesIncludingAmbiguousSymbols == null) {
      allSuperClassesIncludingAmbiguousSymbols = new LinkedHashSet<>();
      exploreSuperClasses(this, allSuperClassesIncludingAmbiguousSymbols, true);
    }
    return allSuperClassesIncludingAmbiguousSymbols;
  }

  private static void exploreSuperClasses(Symbol symbol, Set set, boolean includeAmbiguousSymbols) {
    if (symbol.is(Kind.AMBIGUOUS) && includeAmbiguousSymbols) {
      AmbiguousSymbol ambiguousSymbol = (AmbiguousSymbol) symbol;
      for (Symbol alternative : ambiguousSymbol.alternatives()) {
        exploreSuperClasses(alternative, set, true);
      }
    } else if (set.add(symbol) && symbol.is(Kind.CLASS)) {
      ClassSymbol classSymbol = (ClassSymbol) symbol;
      for (Symbol superClass : classSymbol.superClasses()) {
        exploreSuperClasses(superClass, set, includeAmbiguousSymbols);
      }
    }
  }

  @Override
  public void removeUsages() {
    super.removeUsages();
    superClasses.forEach(symbol -> ((SymbolImpl) symbol).removeUsages());
    members.forEach(symbol -> ((SymbolImpl) symbol).removeUsages());
  }

  public boolean hasSuperClassWithoutSymbol() {
    return hasSuperClassWithoutSymbol;
  }

  public boolean supportsGenerics() {
    return supportsGenerics;
  }

  public void setSupportsGenerics(boolean supportsGenerics) {
    this.supportsGenerics = supportsGenerics;
  }

  /**
   * Precomputed typeshed class symbols might be "lazily evaluated", i.e. only information about super classes fqn is stored, without having created the actual
   * type hierarchy.
   * This method is used to know if super classes have been already created and added to {@link #superClasses}.
   * This might happen in the following cases:
   * - Super classes have been already read, hence class symbol is not lazy anymore
   * - {@link #superClassesFqns} is empty, meaning either this isn't a precomputed typeshed symbol or the class have no superclass.
   */
  public boolean hasEvaluatedSuperClasses() {
    return hasAlreadyReadSuperClasses || superClassesFqns.isEmpty();
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy