Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rnd/type inference engine specification #1763

Draft
wants to merge 19 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
5602c8b
add new type model classes
maksim-grebeniuk-sonarsource Apr 9, 2024
0a66a02
add new type getter to Expression interface
maksim-grebeniuk-sonarsource Apr 9, 2024
69e68b7
add new python type support to name and qualified expression
maksim-grebeniuk-sonarsource Apr 9, 2024
5737238
Introduce SymbolTableBuilderV2
maksim-grebeniuk-sonarsource Apr 9, 2024
33538e8
TypeInferenceV2 basic implementation
guillaume-dequenne-sonarsource Apr 11, 2024
b77c7a3
SONARPY-1764 temp
guillaume-dequenne-sonarsource Apr 15, 2024
2009d3c
SONARPY-1764 Enrich FunctionType (#1764)
guillaume-dequenne-sonarsource Apr 18, 2024
d85e753
SONARPY-1760 Infer types of imported serialized module (#1765)
maksim-grebeniuk-sonarsource Apr 22, 2024
d5ce5fe
SONARPY-1785 Run the new symbol table builder and type inference engi…
guillaume-dequenne-sonarsource Apr 22, 2024
bf06536
SONARPY-1786 Add basic information to locally defined ClassType (pare…
guillaume-dequenne-sonarsource Apr 24, 2024
33cb264
SONARPY-1782 Migrate NonCallableCalledCheck to the new type model
guillaume-dequenne-sonarsource Apr 24, 2024
ed60500
SONARPY-1789 Resolve inherited class members (#1770)
guillaume-dequenne-sonarsource Apr 25, 2024
6fa1260
SONARPY-1794 Avoid inferring types for global variables (#1771)
guillaume-dequenne-sonarsource Apr 25, 2024
bfea989
SONARPY-1796 Infer types for set, dict and tuple literals (#1773)
guillaume-dequenne-sonarsource Apr 26, 2024
cb39e36
SONARPY-1798 Try to resolve built-in types for names which have no sy…
maksim-grebeniuk-sonarsource Apr 26, 2024
de9a80f
SONARPY-1800 NonCallableCallCheck migration: Use the display name of …
guillaume-dequenne-sonarsource Apr 29, 2024
ee6d13f
SONARPY-1803 Store type definition location in the new type model (#1…
guillaume-dequenne-sonarsource Apr 29, 2024
de87f17
Remove unused model classes
guillaume-dequenne-sonarsource May 1, 2024
e33ca0a
Removed unused Python file
guillaume-dequenne-sonarsource May 1, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -21,21 +21,47 @@

import javax.annotation.Nullable;
import org.sonar.check.Rule;
import org.sonar.plugins.python.api.types.InferredType;
import org.sonar.plugins.python.api.PythonSubscriptionCheck;
import org.sonar.plugins.python.api.tree.CallExpression;
import org.sonar.plugins.python.api.tree.Expression;
import org.sonar.plugins.python.api.tree.Tree;
import org.sonar.python.types.v2.PythonType;
import org.sonar.python.types.v2.TriBool;

import static org.sonar.python.tree.TreeUtils.nameFromExpression;

@Rule(key = "S5756")
public class NonCallableCalledCheck extends NonCallableCalled {
public class NonCallableCalledCheck extends PythonSubscriptionCheck {

@Override
public boolean isNonCallableType(InferredType type) {
return !type.canHaveMember("__call__");
public void initialize(Context context) {
context.registerSyntaxNodeConsumer(Tree.Kind.CALL_EXPR, ctx -> {
CallExpression callExpression = (CallExpression) ctx.syntaxNode();
Expression callee = callExpression.callee();
PythonType type = callee.typeV2();
if (isNonCallableType(type)) {
String name = nameFromExpression(callee);
PreciseIssue preciseIssue = ctx.addIssue(callee, message(type, name));
type.definitionLocation()
.ifPresent(location -> preciseIssue.secondary(location, "Definition."));
}
});
}

@Override
public String message(InferredType calleeType, @Nullable String name) {
protected static String addTypeName(PythonType type) {
return type.displayName()
.map(d -> " has type " + d + " and it")
.orElse("");
}

public boolean isNonCallableType(PythonType type) {
return type.hasMember("__call__") == TriBool.FALSE;
}

public String message(PythonType typeV2, @Nullable String name) {
if (name != null) {
return String.format("Fix this call; \"%s\"%s is not callable.", name, addTypeName(calleeType));
return "Fix this call; \"%s\"%s is not callable.".formatted(name, addTypeName(typeV2));
}
return String.format("Fix this call; this expression%s is not callable.", addTypeName(calleeType));
return "Fix this call; this expression%s is not callable.".formatted(addTypeName(typeV2));
}
}
51 changes: 44 additions & 7 deletions python-checks/src/test/resources/checks/nonCallableCalled.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ def call_noncallable(p):
dict_var = {}
dict_var() # Noncompliant

set_literal = {1, 2}
set_literal() # Noncompliant

set_var = set()
set_var() # Noncompliant

Expand All @@ -37,17 +40,21 @@ def call_noncallable(p):
x = 42
else:
x = 'str'
x() # Noncompliant {{Fix this call; "x" is not callable.}}
x() # FN: multiple assignment not handled


def call_no_name():
42() # Noncompliant {{Fix this call; this expression has type int and it is not callable.}}

def flow_sensitivity():
my_var = "hello"
my_var = 42
my_var() # Noncompliant
my_var() # FN: multiple assignment not handled

my_other_var = func
my_other_var() # OK
my_other_var = 42
my_other_var() # Noncompliant
my_other_var() # FN: multiple assignment not handled

def flow_sensitivity_nested_try_except():
def func_with_try_except():
Expand All @@ -59,7 +66,7 @@ def func_with_try_except():
def other_func():
my_var = "hello"
my_var = 42
my_var() # Noncompliant
my_var() # FN: multiple assignments

def member_access():
my_callable = MyCallable()
Expand All @@ -69,16 +76,16 @@ def member_access():
def types_from_typeshed(foo):
from math import acos
from functools import wraps
acos(42)() # Noncompliant {{Fix this call; this expression has type float and it is not callable.}}
# ^^^^^^^^
acos(42)() # FN: declared return type of Typeshed
wraps(func)(foo) # OK, wraps returns a Callable

def with_metaclass():
class Factory: ...
class Base(metaclass=Factory): ...
class A(Base): ...
a = A()
a() # OK
# TODO: resolve type hierarchy and metaclasses
a() # Noncompliant


def decorators():
Expand Down Expand Up @@ -181,3 +188,33 @@ def typing_named_tuple_no_fp():
from typing import NamedTuple
Employee = NamedTuple('Employee', [('name', str), ('id', int)])
employee = Employee("Sam", 42)


class Parent:
def __call__(self):
...

class Child(Parent):
...


def inherited_call_method():
child = Child()
child() # OK


some_global_func = None

def assigning_global(my_func):
global some_global_func
some_global_func = my_func

def calling_global_func():
some_global_func() # OK


some_nonlocal_var = 42

def using_nonlocal_var():
nonlocal some_nonlocal_var
some_nonlocal_var() # Noncompliant
Original file line number Diff line number Diff line change
Expand Up @@ -19,38 +19,5 @@
*/
package org.sonar.plugins.python.api;

public class LocationInFile {
private final String fileId;
private final int startLine;
private final int startLineOffset;
private final int endLine;
private final int endLineOffset;

public LocationInFile(String fileId, int startLine, int startLineOffset, int endLine, int endLineOffset) {
this.fileId = fileId;
this.startLine = startLine;
this.startLineOffset = startLineOffset;
this.endLine = endLine;
this.endLineOffset = endLineOffset;
}

public String fileId() {
return fileId;
}

public int startLine() {
return startLine;
}

public int startLineOffset() {
return startLineOffset;
}

public int endLine() {
return endLine;
}

public int endLineOffset() {
return endLineOffset;
}
public record LocationInFile(String fileId, int startLine, int startLineOffset, int endLine, int endLineOffset) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@
import org.sonar.python.caching.CacheContextImpl;
import org.sonar.python.semantic.ProjectLevelSymbolTable;
import org.sonar.python.semantic.SymbolTableBuilder;
import org.sonar.python.semantic.v2.ProjectLevelTypeTable;
import org.sonar.python.semantic.v2.SymbolTableBuilderV2;
import org.sonar.python.semantic.v2.TypeInferenceV2;

public class PythonVisitorContext extends PythonInputFileContext {

Expand All @@ -44,6 +47,9 @@ public PythonVisitorContext(FileInput rootTree, PythonFile pythonFile, @Nullable
this.parsingException = null;
SymbolTableBuilder symbolTableBuilder = packageName != null ? new SymbolTableBuilder(packageName, pythonFile) : new SymbolTableBuilder(pythonFile);
symbolTableBuilder.visitFileInput(rootTree);
SymbolTableBuilderV2 symbolTableBuilderV2 = new SymbolTableBuilderV2();
symbolTableBuilderV2.visitFileInput(rootTree);
rootTree.accept(new TypeInferenceV2(new ProjectLevelTypeTable(ProjectLevelSymbolTable.empty()), pythonFile));
}

public PythonVisitorContext(FileInput rootTree, PythonFile pythonFile, @Nullable File workingDirectory, String packageName,
Expand All @@ -52,6 +58,9 @@ public PythonVisitorContext(FileInput rootTree, PythonFile pythonFile, @Nullable
this.rootTree = rootTree;
this.parsingException = null;
new SymbolTableBuilder(packageName, pythonFile, projectLevelSymbolTable).visitFileInput(rootTree);
SymbolTableBuilderV2 symbolTableBuilderV2 = new SymbolTableBuilderV2();
symbolTableBuilderV2.visitFileInput(rootTree);
rootTree.accept(new TypeInferenceV2(new ProjectLevelTypeTable(projectLevelSymbolTable), pythonFile));
}

public PythonVisitorContext(FileInput rootTree, PythonFile pythonFile, @Nullable File workingDirectory, String packageName,
Expand All @@ -60,6 +69,9 @@ public PythonVisitorContext(FileInput rootTree, PythonFile pythonFile, @Nullable
this.rootTree = rootTree;
this.parsingException = null;
new SymbolTableBuilder(packageName, pythonFile, projectLevelSymbolTable).visitFileInput(rootTree);
SymbolTableBuilderV2 symbolTableBuilderV2 = new SymbolTableBuilderV2();
symbolTableBuilderV2.visitFileInput(rootTree);
rootTree.accept(new TypeInferenceV2(new ProjectLevelTypeTable(projectLevelSymbolTable), pythonFile));
}

public PythonVisitorContext(PythonFile pythonFile, RecognitionException parsingException) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,18 @@
import com.google.common.annotations.Beta;
import org.sonar.plugins.python.api.types.InferredType;
import org.sonar.python.types.InferredTypes;
import org.sonar.python.types.v2.PythonType;

public interface Expression extends Tree {

@Beta
default InferredType type() {
return InferredTypes.anyType();
}

@Beta
default PythonType typeV2() {
return PythonType.UNKNOWN;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
*/
package org.sonar.plugins.python.api.tree;

import org.sonar.python.semantic.v2.SymbolV2;

/**
* See https://docs.python.org/3/reference/expressions.html#atom-identifiers
*/
Expand All @@ -28,4 +30,6 @@ public interface Name extends Expression, HasSymbol {

// FIXME: we should create a separate tree for Variables
boolean isVariable();

SymbolV2 symbolV2();
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import javax.annotation.CheckForNull;
import org.sonar.plugins.python.api.symbols.Symbol;
import org.sonar.plugins.python.api.symbols.Usage;
import org.sonar.python.types.v2.PythonType;

/**
* Qualified expression like "foo.bar"
Expand Down Expand Up @@ -56,4 +57,9 @@ default Symbol symbol() {
default Usage usage() {
return name().usage();
}

@Override
default PythonType typeV2() {
return name().typeV2();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ public static List<Expression> assignmentsLhs(AssignmentStatement assignmentStat
.toList();
}

static List<Name> boundNamesFromExpression(@CheckForNull Tree tree) {
public static List<Name> boundNamesFromExpression(@CheckForNull Tree tree) {
List<Name> names = new ArrayList<>();
if (tree == null) {
return names;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* SonarQube Python Plugin
* Copyright (C) 2011-2024 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.v2;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.annotation.Nullable;
import org.sonar.plugins.python.api.LocationInFile;
import org.sonar.python.types.v2.ClassType;
import org.sonar.python.types.v2.Member;
import org.sonar.python.types.v2.PythonType;

public class ClassTypeBuilder implements TypeBuilder<ClassType> {


String name;
Set<Member> members = new HashSet<>();
List<PythonType> attributes = new ArrayList<>();
List<PythonType> superClasses = new ArrayList<>();
List<PythonType> metaClasses = new ArrayList<>();
LocationInFile definitionLocation;

@Override
public ClassType build() {
return new ClassType(name, members, attributes, superClasses, metaClasses, definitionLocation);
}

public ClassTypeBuilder withName(String name) {
this.name = name;
return this;
}

@Override
public ClassTypeBuilder withDefinitionLocation(@Nullable LocationInFile definitionLocation) {
this.definitionLocation = definitionLocation;
return this;
}

public List<PythonType> superClasses() {
return superClasses;
}

public List<PythonType> metaClasses() {
return metaClasses;
}
}