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

Force typing instead of inference #8230

Open
alonme opened this issue Feb 7, 2023 · 8 comments
Open

Force typing instead of inference #8230

alonme opened this issue Feb 7, 2023 · 8 comments
Labels
Enhancement ✨ Improvement to a component Needs design proposal 🔒 This is a huge feature, some discussion should happen before a PR is proposed

Comments

@alonme
Copy link

alonme commented Feb 7, 2023

Question

I have a use case in which i want to force pylint to use the type hint of a variable, instead of the inferred value.

For example i have the following code

number: int = "123"

pylint will treat number as a str, but i want it to treat it as an int.

Is this possible in any way?

Documentation for future user

If there is a feature like that, i couldn't find it easily in the docs

Additional context

No response

@alonme alonme added Documentation 📗 Needs triage 📥 Just created, needs acknowledgment, triage, and proper labelling Question labels Feb 7, 2023
@Pierre-Sassoulas
Copy link
Member

We have #4813 to use typing when the inference fail, but there's no plan to use typing before inference. (Imo, if we did that we might ad well create a new program from scratch as inference is at the very core of what pylint do). Also I don't understand the use case, in your example number IS a string, treating it like an int will definitely cause issue, right ?

@Pierre-Sassoulas Pierre-Sassoulas removed the Needs triage 📥 Just created, needs acknowledgment, triage, and proper labelling label Feb 7, 2023
@Pierre-Sassoulas Pierre-Sassoulas added this to the 2.17.0 milestone Feb 7, 2023
@alonme
Copy link
Author

alonme commented Feb 7, 2023

Its a complex case, so i simplified it.

What i would like to do is to tell pylint to specifically use a type instead of infering.

maybe register_transform with inference_tip can do the trick?
in the actual case, i am always assigning ... and then injecting the actual value later into the variable


Something like this i guess?

def infer_my_custom_call(call_node, context=None):
    # Do some transformation here
    return iter((call_node.annotation,))


astroid.MANAGER.register_transform(
    astroid.nodes.AnnAssign,
    inference_tip(infer_my_custom_call),
    predicate=lambda x: x.value == ...,
)

@DanielNoord
Copy link
Collaborator

I would suggest maing this a pylint extension for now.

@alonme
Copy link
Author

alonme commented Feb 8, 2023

@DanielNoord not sure i understand.
Are you saying this is achievable using an extension?

Any tips regarding how?
I had no luck with the direction that i posted in the last comment

The following seems to work for the examples i currently have, but its kind of ugly

def pylint_cast(t):
    if False:  # pylint: disable=using-constant-test
        return t()
    return ...

number: int = pylint_cast(int)

@DanielNoord
Copy link
Collaborator

I don't really have an idea about whether this is feasible, I only know that I don't think we should spend time of maintainers (which is already sparse) to explore this. We simply have more pressing issues for us to focus on this future goal.

@Pierre-Sassoulas
Copy link
Member

Pierre-Sassoulas commented Feb 8, 2023

@alonme there's a bunch of examples about inference tweaking in astroid's "brains" (inference tip) see https://github.com/PyCQA/astroid/tree/main/astroid/brain. You can pretty much do whatever you want for a specific lib this way. Your suggestion to permit to do it on a case by case basis on the user side maybe with pylint: inference:int or something similar is interesting imo, but also a LOT of design work and then of work. And as Daniel said we have a lot on our plate already.

@Pierre-Sassoulas Pierre-Sassoulas added Enhancement ✨ Improvement to a component Needs design proposal 🔒 This is a huge feature, some discussion should happen before a PR is proposed and removed Question Documentation 📗 labels Feb 8, 2023
@Pierre-Sassoulas Pierre-Sassoulas modified the milestones: 2.17.0, 3.0.0 Mar 7, 2023
@alonme
Copy link
Author

alonme commented Mar 16, 2023

I created a plugin - which isn't perfect - but works for me for now.

couldn't get it too work with inference_tip so i used a regular transform

Of course i still believe this should become a feature

from typing import TYPE_CHECKING, Optional

import astroid
from astroid.builder import extract_node
from astroid.nodes.node_classes import AnnAssign, Const, Name, NodeNG, Subscript

if TYPE_CHECKING:
    from pylint.lint import PyLinter


def register(linter: "PyLinter") -> None:
    """This required method auto registers the checker during initialization.
    :param linter: The linter to register the checker to.
    """
    pass

def _is_assigning_ellipsis(node: AnnAssign):
    if isinstance(node.value, Const) and node.value.value is ...:
        return True

def _handle_subscript(node: Subscript):
    if node.value.name == "Optional":
        new_node_code = f"{_create_new_code(node.slice)} or None"
    elif node.value.name == "Union":
        types = [_create_new_code(s) for s in node.slice.elts]
        new_node_code = " or ".join(types)
    elif node.value.name == "List":
        inner_type = _create_new_code(node.slice)
        new_node_code = f"list({inner_type})"
    elif node.value.name == "Tuple":
        types = [_create_new_code(s) for s in node.slice.elts]
        new_node_code = f"tuple({', '.join(types)})"
    elif node.value.name == "Set":
        inner_type = _create_new_code(node.slice)
        new_node_code = f"Set({inner_type})"
    else:
        raise ValueError(f"Unhandled Subscript: {node.value.name}")

    return new_node_code


def _create_new_code(call_node: Optional[NodeNG]) -> str:
    if isinstance(call_node, Name):
        new_node_code = f"{call_node.name}()"

    elif isinstance(call_node, Subscript):
        new_node_code = _handle_subscript(call_node)

    else:
        raise ValueError(f"Unhandled node type: {call_node}")

    return new_node_code


def _transform_to_annotation_object(call_node: AnnAssign, context=None):
    """
    Transform an AnnAssign node to have a value based on the type annotation only
    """
    new_node_code = _create_new_code(call_node.annotation)
    new_node = extract_node(new_node_code)
    # Change the assignment to look as if its value is of the annotation class
    call_node.value = new_node


astroid.MANAGER.register_transform(
    AnnAssign,
    _transform_to_annotation_object,
    predicate=_is_assigning_ellipsis,
)

@pylint-dev pylint-dev deleted a comment from alonme Mar 16, 2023
@pylint-dev pylint-dev deleted a comment from alonme Mar 16, 2023
@Pierre-Sassoulas Pierre-Sassoulas removed this from the 3.0.0 milestone Sep 25, 2023
@Pierre-Sassoulas
Copy link
Member

Pierre-Sassoulas commented Apr 1, 2024

Related (use typing when the inference fail): #4813. It's a consensual first step imo.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Enhancement ✨ Improvement to a component Needs design proposal 🔒 This is a huge feature, some discussion should happen before a PR is proposed
Projects
None yet
Development

No branches or pull requests

3 participants