pylint-dev / pylint

It's not just a linter that annoys you!
https://pylint.readthedocs.io/en/latest/
GNU General Public License v2.0
5.28k stars 1.13k forks source link

Crash ``Building error when trying to create ast representation of module 'gradient'`` #8602

Closed maxludden closed 1 year ago

maxludden commented 1 year ago

Bug description

When parsing the following file:

"""This module contains the gradient class to automate the creation of gradient colored text."""
# pylint: disable=W0621,C0103,E0013,E1121,E0013
from random import randint
from typing import Any, Iterable, List, Optional, Tuple

from lorem_text import lorem
from rich import inspect
from rich._pick import pick_bool
from rich.cells import cell_len
from rich.color_triplet import ColorTriplet
from rich.console import Console, ConsoleOptions, JustifyMethod, OverflowMethod
from rich.control import strip_control_codes
from rich.layout import Layout
from rich.measure import Measurement
from rich.panel import Panel
from rich.segment import Segment
from rich.style import Style
from rich.text import Span, Text
from snoop import snoop, spy

from color import Color, ColorParsingError, InvalidHexColor, InvalidRGBColor
from theme import GradientTheme

DEFAULT_JUSTIFY: "JustifyMethod" = "default"
DEFAULT_OVERFLOW: "OverflowMethod" = "fold"
StyleType = Optional[str | Style]
console = Console(theme=GradientTheme())

class Gradient(Text):
    """Text with gradient color / style.

        Args:
            text(`text): The text to print. Defaults to empty string.
            colors(`List[Optional[Color|Tuple|str|int]]`): A list of colors to use \
                for the gradient. If `colors` has at least two parsable colors, `colors_start`\
                    and `end_color`'s arguments are ignored and those values are parsed from \
                        `colors`.Defaults to []
            start_color(`Optional[Color|str|int]`): The color to start the gradient.
            end_color(`Optional[Color|str|int]`): The color to end the gradient.
            rainbow(`bool`): Whether to print the gradient text in rainbow colors across \
                the spectrum. Defaults to False.
            invert(`bool): Reverse the color gradient. Defaults to False.
            hues(`int`): The number of colors in the gradient. Defaults to `3`.
            color_box(`bool`): Whether to print the gradient on identically colored background. \
                This makes the gradient's text invisible, but it useful for printing gradient \
                samples. Defaults to False.
            style(`StyleType`) The style of the gradient text. Defaults to None.
            justify(`Optional[JustifyMethod]`): Justify method: "left", "center", "full", \
                "right". Defaults to None.
            overflow(`Optional[OverflowMethod]`):  Overflow method: "crop", "fold", \
                "ellipsis". Defaults to None.
            end (str, optional): Character to end text with. Defaults to "\\\\n".
            no_wrap (bool, optional): Disable text wrapping, or None for default. Defaults to None.
            end (str, optional): Character to end text with. Defaults to "\\\\n".
            tab_size (int): Number of spaces per tab, or `None` to use `console.tab_size`.\
                Defaults to 8.
            spans (List[Span], optional). A list of predefined style spans. Defaults to None.
            verbose(`bool`): Whether to print verbose output. Defaults to False.
    """

    __slots__ = [
        "colors",
        "_indexes",
        "start_color",
        "end_color",
        "invert",
        "hues",
        "color_box",
        "_iter_index",
        "verbose",
        "_console",
    ]

    def __init__(
        self,
        text: Optional[str | Text] = "",
        colors: Optional[List[Color | Tuple | str | int]] = None,
        start_color: Optional[Color | Tuple | str | int] = None,
        end_color: Optional[Color | Tuple | str | int] = None,
        rainbow: bool = False,
        invert: bool = False,
        hues: Optional[int] = None,
        color_box: bool = False,
        style: StyleType = None,
        *,
        justify: Optional[JustifyMethod] = None,
        overflow: Optional[OverflowMethod] = None,
        no_wrap: Optional[bool] = None,
        end: str = "\n",
        tab_size: Optional[int] = 8,
        spans: Optional[List[Span]] = None,
        verbose: bool = False,
    ) -> None:
        self.verbose: bool = verbose
        if self.verbose:
            self._console: Optional[Console] = Console(theme=GradientTheme())
        # if text is a Text object, convert it to a string
        if isinstance(text, Text):
            self._text = text.plain
        super().__init__(
            text=text,
            justify=justify,
            overflow=overflow,
            no_wrap=no_wrap,
            end=end,
            tab_size=tab_size,
            spans=spans,
        )
        # text
        sanitized_text = strip_control_codes(text)
        self._length = len(sanitized_text)
        if self.verbose:
            self._console.log(
                Panel(text, title="[b u]Text[/]", subtitle=f"Length: {self._length}")
            )

        # color_box
        self.color_box = color_box
        if self.color_box:
            self._text = "█" * self._length

        # start color
        self.start_color = (
            Color(start_color) if self.validate_color(start_color) else None
        )

        # end color
        self.end_color = Color(end_color) if self.validate_color(end_color) else None

        # colors
        self.colors: List[Color] = colors or []
        self._indexes: List[int] = []
        self._iter_index: int
        self.hues: int = hues or 3
        self.invert: bool = invert
        if self.validate_colors():  # if colors is a list of Colors
            if self.verbose:
                self._console.log("Validated colors. Setting start and end colors.")
            self.start_color = self.colors[0]
            self.end_color = self.colors[-1]
            self.hues = len(self.colors)
            for color in self.colors:
                self._indexes.append(color.as_index())

        elif rainbow:
            self.colors = self.generate_rainbow_colors()

        # if self.start_color in Color.COLORS:
        #     self.start_color = Color.COLORS.index(self.start_color)
        # elif self.start_color in Color.HEX:
        #     self.start_color = Color.HEX.index(self.start_color)
        # elif isinstance(self.start_color, Color):
        #     self.start_color = Color(self.start_color).as_index()
        # elif isinstance(self.start_color, int):
        #     if self.start_color not in list(range(0, 9)):
        #         raise ValueError(
        #             f"Invalid start index: {self.start_color}. Must be between 0 and 9."
        #         )
        # elif self.start_color is None:
        #     pass
        # else:
        #     raise TypeError(f"Invalid start type: {type(self.start_color)}")

        # if not rainbow:
        #     if self.end_color in Color.COLORS:
        #         self.end_color = Color.COLORS.index(self.end_color)
        #     elif self.end_color in Color.HEX:
        #         self.end_color = Color.HEX.index(self.end_color)
        #     elif isinstance(self.end_color, Color):
        #         self.end_color = Color(self.end_color).as_index()
        #     elif isinstance(self.end_color, int):
        #         if self.end_color not in list(range(0, 9)):
        #             raise ValueError(
        #                 f"Invalid end index: {self.end_color}. Must be between 0 and 9."
        #             )
        #     elif self.end_color is None:
        #         pass
        #     else:
        #         raise TypeError(f"Invalid end type: {type(self.end_color)}")
        # else:
        #     if self.start_color is None:
        #         self.start_color = 0
        #     if not invert:
        #         self.end_color = self.start_color - 1
        #         if self.end_color < 0:
        #             self.end_color += 10
        #     else:
        #         self.end_color = self.start_color + 1
        #         if self.end_color > 9:
        #             self.end_color -= 10

        if not self.colors:
            # Generate indexes from start and end colors
            if self.start_color is None:
                start_index = None
            else:
                start_index = Color(self.start_color).as_index()

            if self.end_color is None:
                end_index = None
            else:
                end_index = Color(self.end_color).as_index()

            # If a gradient's colors haven't already been generated and validated, generate them
            if start_index is None and end_index is None:
                start_index, end_index = self.start_end_index()
            elif start_index is None and end_index:
                start_index = self.start_index()
            elif start_index and self.end_color is None:
                end_index = self.end_index()
            else:
                self._indexes = self.generate_index()

            for index in self._indexes:
                self.colors.append(Color(Color.COLORS[index]))

        if self._spans == []:
            assert self.colors != []
            self._spans = self.generate_spans()

    @staticmethod
    def validate_color(color: Any) -> bool:
        """Validate a color."""
        if color in Color.COLORS:
            return True
        if color in Color.HEX:
            return True
        if isinstance(color, Color):
            return True
        if isinstance(color, int):
            if color in list(range(0, 9)):
                return True
        return False

    @staticmethod
    def parse_color(color: Any) -> Color:
        """Parse a color."""
        try:
            color: Color = Color(color)
        except* InvalidHexColor as ihc:
            raise ihc(f"Invalid hex color: {color}") from ihc()
        except* InvalidRGBColor as irc:
            raise irc(f"Invalid RGB color: {color}") from irc
        except* ColorParsingError as cpe:
            raise cpe(f"Invalid color: {color}") from cpe
        except* ValueError as ve:
            raise ve(f"Invalid color: {color}") from ve

    @staticmethod
    def wrap_index(index: int) -> int:
        """Wrap an index around the color wheel."""
        if index < 0:
            index += 10
        if index > 9:
            index -= 10
        return index

    def start_end_index(self) -> Tuple[int, int]:
        """Generate the start and end when only `hues` is provided."""
        start: int = randint(0, 9)
        _end = start - self.hues if self.invert else start + self.hues
        end = self.wrap_index(_end)
        return start, end

    def start_index(self) -> int:
        """Generate the start when only `end` is provided."""
        end: int = Color(self.end).as_index()
        _start = end + self.hues if self.invert else end - self.hues
        start = self.wrap_index(_start)
        return start

    def end_index(self) -> int:
        """Generate the end when only `start` is provided."""
        start: int = Color(self.start).as_index()
        _end = start - self.hues if self.invert else start + self.hues
        end = self.wrap_index(_end)
        return end

    @snoop
    def generate_index(self, start: int, end: int) -> List[int]:
        """Generate the index when both `start` and `end` are provided.

        Args:
            start (int): The start index.
            end (int): The end index.

        Returns:
            List[int]: The index.
        """
        indexes: List[int] = [] # Clear the index
        # Calculate the hues between the start and end
        if not self.invert:
            hues = (end + 1) - start if start<end else (10-start) + end + 1
        else:
            hues = (start + 1) - end if start>end else (10-end) + start + 1

        self.hues = hues

        for index in range(self.hues):
            _next_index = start-index if self.invert else start+index
            next_index = self.wrap_index(_next_index)
            indexes.append(next_index)
        assert next_index == end, f"{next_index} != {end}"
        return indexes

    # Iterable dunder methods
    def __getitem__(self, index):
        if index >= len(self._indexes):
            raise IndexError("ColorIndex index out of range")
        return self._indexes[index]

    def __iter__(self):
        self._iter_index = 0
        return self

    def __next__(self):
        """Get the next color index."""
        if self._iter_index >= len(self._indexes):
            raise StopIteration
        value = self._indexes[self._iter_index]
        self._iter_index += 1
        return value

    # Representation dunder methods
    def __str__(self):
        """Get the string representation of the gradient's text."""
        return self._text

    def __repr__(self) -> str:
        # return f"Gradient<{', '.join([str(color) for color in self.colors])}>, Text<{self._text}>"
        colors = ", ".join(str(color).capitalize() for color in self.colors)
        return f"Gradient<< Text<{self._text}> Colors<{colors}>>"

    def __rich_repr__(self) -> None:
        return inspect(self, all=True)

    def __len__(self) -> int:
        return len(self._text)

    def validate_colors(self) -> bool:
        """Validate the colors. \n\n\n Sets `self.colors` to the validated colors \
            if there are at least two valid colors. \n\n\n
        Returns:
            bool: `True` if there are at least two valid colors, otherwise `False`.
        """
        # if there are not enough colors, return False
        if self.colors is None or len(self.colors) <= 1:
            self.colors = []
            return False

        # Parse colors and remove any that are not colors
        validated_colors: List[Color] = []
        invalidated_colors: List[Any] = []
        for color in self.colors:
            try:
                color = Color(color)
                validated_colors.append(color)
            except TypeError:
                invalidated_colors.append(color)

        self.colors = []  # clear the colors

        # If there are at least 2 colors
        if len(validated_colors) >= 1:
            for color in validated_colors:
                self.colors.append(color)  # add the validated colors
                return True
        return False

    def generate_rainbow_colors(self) -> List[Color]:
        """Generate the full rainbow of colors. \n\n\n The list of colors \
            will begin at the `start_color` property and scale towards \
            the last color respecting the `invert` property.

        Returns:
            List[Color]: A list of colors spanning the spectrum.
        """
        indexes: List[int] = []
        colors: List[Color] = []
        start_index: int = self.start_color.as_index() or randint(0, 9)

        step = -1 if self.invert else 1
        for index in range(10):
            next_index = start_index + (index * step)
            if next_index < 0:
                next_index += 10
            if next_index > 9:
                next_index -= 10
            indexes.append(next_index)
            colors.append(Color(Color.COLORS.index(next_index)))

        self._indexes = indexes
        self.hues = 10
        return colors

    # ============================================================================ #
    # Generate Gradient Spans
    # ============================================================================ #
    @spy
    def generate_spans(self) -> List[Span]:
        """Generate the spans for the gradient."""
        text = "".join(self._text)
        size = len(text)
        gradients = int(self.hues - 1)
        gradient_size = int(size // gradients)

        for index in range(gradients):
            begin = index * gradient_size
            end = begin + gradient_size
            if index == gradients - 1:
                substring = Text(text[begin:], style=self.style)
            else:
                substring = Text(text[begin:end], style=self.style)

            if index < (self.hues):
                red1, green1, blue1 = self.colors[index].as_rgb()
                red2, green2, blue2 = self.colors[index + 1].as_rgb()
                delta_red = self.color_delta(red1, red2)
                delta_green = self.color_delta(green1, green2)
                delta_blue = self.color_delta(blue1, blue2)

            for char_index in range(len(substring)):
                blend = char_index / gradient_size
                color = ColorTriplet(
                    int(red1 + (delta_red * blend)),
                    int(green1 + (delta_green * blend)),
                    int(blue1 + (delta_blue * blend)),
                )

                # Create the new style
                new_style = Style(color=color)

                if self.color_box:  # Set the fore and background to the same color
                    new_style = Style(color=color, bgcolor=color)

                if self.style:  # Add the style to the new style
                    new_style = new_style + self.style

                start = char_index + begin
                end = start + 1
                new_span = Span(start=start, end=end, style=new_style)
                self._spans.append(new_span)
        return self._spans

    def color_delta(self, first: int, second: int, cross_fade: float = 0.5) -> float:
        """Generate a new color's by blending the component from two colors.

        Args:
            first (int): The first color's component.
            second(int): The second color's component.
            crass_fade (float, optional): The cross fade value. Defaults to 0.5.

        Returns:
            int: The new color's component.
        """
        return (second - first) * cross_fade

    # @snoop
    def as_text(self) -> Text:
        """Return the gradient as a Text object."""
        size = len(self._text)
        number_of_gradients = int(self._length - 1)
        gradient_size = int(size // number_of_gradients)
        gradient_text = Text()

        for index in range(number_of_gradients):
            next_index = index + 1
            begin = index * gradient_size
            end = begin + gradient_size
            if index == number_of_gradients - 1:
                substring = Text(self._text[begin:], style=self.style)
            else:
                substring = Text(self._text[begin:end], style=self.style)

            if index < (self._length - 1):
                color1 = self.colors[index]
                red1, green1, blue1 = tuple(Color(color1).as_rgb())
                color2 = self.colors[next_index]
                red2, green2, blue2 = tuple(Color(color2).as_rgb())
                delta_red = red2 - red1
                delta_green = green2 - green1
                delta_blue = blue2 - blue1

            for char_index in range(len(substring)):
                blend = char_index / gradient_size
                color1 = f"{int(red1 + delta_red * blend):02X}"
                color2 = f"{int(green1 + delta_green * blend):02X}"
                color3 = f"{int(blue1 + delta_blue * blend):02X}"
                color = "#"
                for x in [color1, color2, color3]:
                    if len(x) != 2:
                        x = x[0:1]
                    if "-" in x:
                        x = x.replace("-", "")
                    color = f"{color}{x}"

                new_style = Style(color=color)
                if self.color_box:
                    new_style = Style(color=color, bgcolor=color)
                if self.style:
                    new_style = new_style + self.style

                substring.stylize(new_style, char_index, char_index + 1)

            if self.verbose:
                console.log(f"Gradient {index}:", substring)

            gradient_text = Text.assemble(
                gradient_text,
                substring,
                justify=self.justify,
                overflow=self.overflow,
                end=self.end_color,
            )
        return gradient_text

    def __rich_console__(
        self, console: "Console", options: "ConsoleOptions"
    ) -> Iterable[Segment]:
        # yield from self.as_text().__rich_console__(console, options)
        tab_size: int = console.tab_size or self.tab_size or 8
        justify = self.justify or options.justify or DEFAULT_JUSTIFY

        overflow = self.overflow or options.overflow or DEFAULT_OVERFLOW

        lines = self.wrap(
            console,
            options.max_width,
            justify=justify,
            overflow=overflow,
            tab_size=tab_size or 8,
            no_wrap=pick_bool(self.no_wrap, options.no_wrap, False),
        )
        all_lines = Text("\n").join(lines)
        yield from all_lines.render(console, end=self.end)

    def __rich_measure__(
        self, console: "Console", options: "ConsoleOptions"
    ) -> Measurement:
        text = self.plain
        lines = text.splitlines()
        max_text_width = max(cell_len(line) for line in lines) if lines else 0
        words = text.split()
        min_text_width = (
            max(cell_len(word) for word in words) if words else max_text_width
        )
        return Measurement(min_text_width, max_text_width)

# ============================================================================ #
# ExamplesAAA
# ============================================================================ #

def original_panel(text: str) -> Panel:
    """Generate a panel with a gradient."""
    return Panel(text, title="Original Text", border_style="bold.grey")

@snoop
def random_panel(text: str) -> Panel:
    """Generate a panel containing a random gradient."""
    gradient_text = Gradient(text)
    gradient_title = Gradient("[b u]Random Gradient[/]")
    return Panel(
        gradient_text,
        title=gradient_title,
        border_style="bold.grey",
    )

@snoop
def warm_panel(text: str) -> Panel:
    """Generate a panel containing a warm gradient."""
    warm_text = Gradient(text, start_color="yellow", end_color="magenta")
    warm_title = Gradient(
        "Warm Gradient", style="b u", start_color="yellow", end_color="magenta"
    )
    return Panel(
        warm_text,
        title=warm_title,
        border_style="bold.grey",
    )

@snoop
def cool_panel(text: str) -> Panel:
    """Generate a panel containing a cool gradient."""
    cool_text = Gradient(text, start_color="lightpurple", end_color="green")
    cool_title = Gradient(
        "Cool Gradient", style="b u", start_color="lightpurple", end_color="green"
    )
    return Panel(
        cool_text,
        title=cool_title,
        border_style="bold.grey",
    )

@snoop
def rainbow_panel(text: str) -> Panel:
    """Generate a panel containing a rainbow gradient."""
    rainbow_text = Gradient(text, rainbow=True)
    rainbow_title = Gradient("Rainbow Gradient", style="b u", rainbow=True)
    return Panel(
        rainbow_text,
        title=rainbow_title,
        border_style="bold.grey",
    )

@snoop
def make_layout() -> Layout:
    """Generate a layout of panels containing gradients."""
    layout = Layout(name="root")
    layout.split(
        Layout(name="panel1"),
        Layout(name="panel2"),
        Layout(name="panel3"),
        Layout(name="panel4"),
        Layout(name="panel5"),
    )
    return layout

@snoop
def gradient_example_panels() -> Layout:
    """Generate multiple examples of gradients to be displayed side-by-side."""
    layout = make_layout()
    TEXT = str(lorem.paragraph())
    layout["panel1"].update(original_panel(TEXT))
    layout["panel2"].update(random_panel(TEXT))
    layout["panel3"].update(warm_panel(TEXT))
    layout["panel4"].update(cool_panel(TEXT))
    layout["panel5"].update(rainbow_panel(TEXT))
    return layout

if __name__ == "__main__":  # pragma: no cover
    TEXT = "The quick brown fox jumps over the lazy dog."
    GRADIENT1 = Gradient(TEXT)
    SPANS = GRADIENT1.spans
    GRADIENT2 = Gradient(TEXT, style="u b")
    console.print(GRADIENT1)
    console.print(GRADIENT2)
    # console.print(gradient_example_panels())

pylint crashed with a AstroidBuildingError and with the following stacktrace:

Traceback (most recent call last):
  File "/Users/maxludden/.vscode/extensions/ms-python.pylint-2023.4.0/bundled/libs/pylint/lint/pylinter.py", line 1027, in get_ast
    return astroid.builder.AstroidBuilder(MANAGER).string_build(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/maxludden/.vscode/extensions/ms-python.pylint-2023.4.0/bundled/libs/astroid/builder.py", line 151, in string_build
    module, builder = self._data_build(data, modname, path)
                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/maxludden/.vscode/extensions/ms-python.pylint-2023.4.0/bundled/libs/astroid/builder.py", line 204, in _data_build
    module = builder.visit_module(node, modname, node_file, package)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/maxludden/.vscode/extensions/ms-python.pylint-2023.4.0/bundled/libs/astroid/rebuilder.py", line 254, in visit_module
    [self.visit(child, newnode) for child in node.body],
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/maxludden/.vscode/extensions/ms-python.pylint-2023.4.0/bundled/libs/astroid/rebuilder.py", line 254, in <listcomp>
    [self.visit(child, newnode) for child in node.body],
     ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/maxludden/.vscode/extensions/ms-python.pylint-2023.4.0/bundled/libs/astroid/rebuilder.py", line 605, in visit
    return visit_method(node, parent)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/maxludden/.vscode/extensions/ms-python.pylint-2023.4.0/bundled/libs/astroid/rebuilder.py", line 960, in visit_classdef
    [self.visit(child, newnode) for child in node.body],
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/maxludden/.vscode/extensions/ms-python.pylint-2023.4.0/bundled/libs/astroid/rebuilder.py", line 960, in <listcomp>
    [self.visit(child, newnode) for child in node.body],
     ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/maxludden/.vscode/extensions/ms-python.pylint-2023.4.0/bundled/libs/astroid/rebuilder.py", line 605, in visit
    return visit_method(node, parent)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/maxludden/.vscode/extensions/ms-python.pylint-2023.4.0/bundled/libs/astroid/rebuilder.py", line 1286, in visit_functiondef
    return self._visit_functiondef(nodes.FunctionDef, node, parent)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/maxludden/.vscode/extensions/ms-python.pylint-2023.4.0/bundled/libs/astroid/rebuilder.py", line 1271, in _visit_functiondef
    body=[self.visit(child, newnode) for child in node.body],
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/maxludden/.vscode/extensions/ms-python.pylint-2023.4.0/bundled/libs/astroid/rebuilder.py", line 1271, in <listcomp>
    body=[self.visit(child, newnode) for child in node.body],
          ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/maxludden/.vscode/extensions/ms-python.pylint-2023.4.0/bundled/libs/astroid/rebuilder.py", line 603, in visit
    visit_method = getattr(self, visit_name)
                   ^^^^^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'TreeRebuilder' object has no attribute 'visit_trystar'

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/Users/maxludden/.vscode/extensions/ms-python.pylint-2023.4.0/bundled/libs/pylint/lint/pylinter.py", line 718, in _get_asts
    ast_per_fileitem[fileitem] = self.get_ast(
                                 ^^^^^^^^^^^^^
  File "/Users/maxludden/.vscode/extensions/ms-python.pylint-2023.4.0/bundled/libs/pylint/lint/pylinter.py", line 1048, in get_ast
    raise astroid.AstroidBuildingError(
astroid.exceptions.AstroidBuildingError: Building error when trying to create ast representation of module 'gradient'

Configuration

No response

Command used

Pylint extension for Visual Studio Code

Pylint output

pylint crashed with a ``AstroidBuildingError`` and with the following stacktrace:

Traceback (most recent call last):
  File "/Users/maxludden/.vscode/extensions/ms-python.pylint-2023.4.0/bundled/libs/pylint/lint/pylinter.py", line 1027, in get_ast
    return astroid.builder.AstroidBuilder(MANAGER).string_build(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/maxludden/.vscode/extensions/ms-python.pylint-2023.4.0/bundled/libs/astroid/builder.py", line 151, in string_build
    module, builder = self._data_build(data, modname, path)
                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/maxludden/.vscode/extensions/ms-python.pylint-2023.4.0/bundled/libs/astroid/builder.py", line 204, in _data_build
    module = builder.visit_module(node, modname, node_file, package)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/maxludden/.vscode/extensions/ms-python.pylint-2023.4.0/bundled/libs/astroid/rebuilder.py", line 254, in visit_module
    [self.visit(child, newnode) for child in node.body],
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/maxludden/.vscode/extensions/ms-python.pylint-2023.4.0/bundled/libs/astroid/rebuilder.py", line 254, in <listcomp>
    [self.visit(child, newnode) for child in node.body],
     ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/maxludden/.vscode/extensions/ms-python.pylint-2023.4.0/bundled/libs/astroid/rebuilder.py", line 605, in visit
    return visit_method(node, parent)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/maxludden/.vscode/extensions/ms-python.pylint-2023.4.0/bundled/libs/astroid/rebuilder.py", line 960, in visit_classdef
    [self.visit(child, newnode) for child in node.body],
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/maxludden/.vscode/extensions/ms-python.pylint-2023.4.0/bundled/libs/astroid/rebuilder.py", line 960, in <listcomp>
    [self.visit(child, newnode) for child in node.body],
     ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/maxludden/.vscode/extensions/ms-python.pylint-2023.4.0/bundled/libs/astroid/rebuilder.py", line 605, in visit
    return visit_method(node, parent)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/maxludden/.vscode/extensions/ms-python.pylint-2023.4.0/bundled/libs/astroid/rebuilder.py", line 1286, in visit_functiondef
    return self._visit_functiondef(nodes.FunctionDef, node, parent)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/maxludden/.vscode/extensions/ms-python.pylint-2023.4.0/bundled/libs/astroid/rebuilder.py", line 1271, in _visit_functiondef
    body=[self.visit(child, newnode) for child in node.body],
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/maxludden/.vscode/extensions/ms-python.pylint-2023.4.0/bundled/libs/astroid/rebuilder.py", line 1271, in <listcomp>
    body=[self.visit(child, newnode) for child in node.body],
          ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/maxludden/.vscode/extensions/ms-python.pylint-2023.4.0/bundled/libs/astroid/rebuilder.py", line 603, in visit
    visit_method = getattr(self, visit_name)
                   ^^^^^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'TreeRebuilder' object has no attribute 'visit_trystar'

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/Users/maxludden/.vscode/extensions/ms-python.pylint-2023.4.0/bundled/libs/pylint/lint/pylinter.py", line 718, in _get_asts
    ast_per_fileitem[fileitem] = self.get_ast(
                                 ^^^^^^^^^^^^^
  File "/Users/maxludden/.vscode/extensions/ms-python.pylint-2023.4.0/bundled/libs/pylint/lint/pylinter.py", line 1048, in get_ast
    raise astroid.AstroidBuildingError(
astroid.exceptions.AstroidBuildingError: Building error when trying to create ast representation of module 'gradient'

### Expected behavior

I'm honestly not sure, Pylint asked me to post this, let me know if I can help.

### Pylint version

```shell
pylint 2.17.2
astroid 2.15.3
Python 3.11.2 (main, Apr  5 2023, 19:47:51) [Clang 14.0.3 (clang-1403.0.22.14.1)]

OS / Environment

Hardware

2020 Macbook Air M1

OS

MacOS Ventura 13.3.1

Terminal

VS Code, Integrated Terminal, Default Osx Profile: zsh

Additional dependencies

Package Manager

PDM

pyproject.toml

[tool.pdm]
[tool.pdm.dev-dependencies]
dev = [
    "snoop>=0.4.3",
    "birdseye>=0.9.4",
    "ipython>=8.12.0",
    "ipywidgets>=8.0.6",
]

[project]
name = "gradient"
version = "0.1.0"
description = "A project to automate the process of writing text to the console in a color gradient. This project stands of the sholders of the great rich library."
authors = [
    {name = "maxludden", email = "dev@maxludden.com"},
]
dependencies = [
    "rich>=13.3.3",
    "lorem-text>=2.1",
    "pydantic>=1.10.7",
]
requires-python = ">=3.10"
readme = "README.md"
license = {text = "MIT"}

[build-system]
requires = ["pdm-backend"]
build-backend = "pdm.backend"

PDM list

❯ pdm list
╭────────────────────┬────────────────┬─────────────────────────────────────╮
│ name               │ version        │ location                            │
├────────────────────┼────────────────┼─────────────────────────────────────┤
│ typing_extensions  │ 4.5.0          │                                     │
│ appnope            │ 0.1.3          │                                     │
│ birdseye           │ 0.9.4          │                                     │
│ certifi            │ 2022.12.7      │                                     │
│ wcwidth            │ 0.2.6          │                                     │
│ decorator          │ 5.1.1          │                                     │
│ Werkzeug           │ 2.2.3          │                                     │
│ Jinja2             │ 3.1.2          │                                     │
│ pyzmq              │ 25.0.2         │                                     │
│ jupyter_client     │ 8.2.0          │                                     │
│ ptyprocess         │ 0.7.0          │                                     │
│ littleutils        │ 0.2.2          │                                     │
│ urllib3            │ 1.26.15        │                                     │
│ setuptools         │ 67.7.1         │                                     │
│ python-dateutil    │ 2.8.2          │                                     │
│ Pygments           │ 2.15.1         │                                     │
│ asttokens          │ 2.2.1          │                                     │
│ rich               │ 13.3.4         │                                     │
│ pydantic           │ 1.10.7         │                                     │
│ requests           │ 2.28.2         │                                     │
│ pickleshare        │ 0.7.5          │                                     │
│ stack-data         │ 0.6.2          │                                     │
│ platformdirs       │ 3.2.0          │                                     │
│ nest-asyncio       │ 1.5.6          │                                     │
│ widgetsnbextension │ 4.0.7          │                                     │
│ parso              │ 0.8.3          │                                     │
│ executing          │ 1.2.0          │                                     │
│ six                │ 1.16.0         │                                     │
│ click              │ 8.1.3          │                                     │
│ markdown-it-py     │ 2.2.0          │                                     │
│ debugpy            │ 1.6.7          │                                     │
│ idna               │ 3.4            │                                     │
│ cached-property    │ 1.5.2          │                                     │
│ jupyter_core       │ 5.3.0          │                                     │
│ comm               │ 0.1.3          │                                     │
│ pure-eval          │ 0.2.2          │                                     │
│ backcall           │ 0.2.0          │                                     │
│ pip                │ 23.1           │                                     │
│ matplotlib-inline  │ 0.1.6          │                                     │
│ Flask              │ 2.2.3          │                                     │
│ mdurl              │ 0.1.2          │                                     │
│ itsdangerous       │ 2.1.2          │                                     │
│ packaging          │ 23.1           │                                     │
│ MarkupSafe         │ 2.1.2          │                                     │
│ outdated           │ 0.2.2          │                                     │
│ pexpect            │ 4.8.0          │                                     │
│ ipython            │ 8.12.0         │                                     │
│ psutil             │ 5.9.5          │                                     │
│ Flask-Humanize     │ 0.3.0          │                                     │
│ humanize           │ 4.6.0          │                                     │
│ ipywidgets         │ 8.0.6          │                                     │
│ jupyterlab-widgets │ 3.0.7          │                                     │
│ prompt-toolkit     │ 3.0.38         │                                     │
│ wheel              │ 0.38.4         │                                     │
│ ipykernel          │ 6.22.0         │                                     │
│ SQLAlchemy         │ 2.0.10         │                                     │
│ snoop              │ 0.4.3          │                                     │
│ lorem-text         │ 2.1            │                                     │
│ charset-normalizer │ 3.1.0          │                                     │
│ gradient           │ 0.1.0+editable │ -e /Users/maxludden/dev/py/gradient │
│ jedi               │ 0.18.2         │                                     │
│ cheap-repr         │ 0.5.1          │                                     │
│ tornado            │ 6.3.1          │                                     │
│ traitlets          │ 5.9.0          │                                     │
╰────────────────────┴────────────────┴─────────────────────────────────────╯
mbyrnepr2 commented 1 year ago

Thanks for creating the issue.

Simple reproducer:

cat example.py 
try:
    [][0]
except* IndexError:
    print("key error here")

pylint example.py
Traceback ```python Traceback (most recent call last): File "/Users/markbyrne/venv311/lib/python3.11/site-packages/pylint/lint/pylinter.py", line 1026, in get_ast return MANAGER.ast_from_file(filepath, modname, source=True) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/Users/markbyrne/venv311/lib/python3.11/site-packages/astroid/manager.py", line 124, in ast_from_file return AstroidBuilder(self).file_build(filepath, modname) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/Users/markbyrne/venv311/lib/python3.11/site-packages/astroid/builder.py", line 144, in file_build module, builder = self._data_build(data, modname, path) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/Users/markbyrne/venv311/lib/python3.11/site-packages/astroid/builder.py", line 204, in _data_build module = builder.visit_module(node, modname, node_file, package) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/Users/markbyrne/venv311/lib/python3.11/site-packages/astroid/rebuilder.py", line 254, in visit_module [self.visit(child, newnode) for child in node.body], ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/Users/markbyrne/venv311/lib/python3.11/site-packages/astroid/rebuilder.py", line 254, in [self.visit(child, newnode) for child in node.body], ^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/Users/markbyrne/venv311/lib/python3.11/site-packages/astroid/rebuilder.py", line 603, in visit visit_method = getattr(self, visit_name) ^^^^^^^^^^^^^^^^^^^^^^^^^ AttributeError: 'TreeRebuilder' object has no attribute 'visit_trystar' ```

I can't reproduce it on the latest pylint and astroid branches. @cdce8p was there a fix recently?

cdce8p commented 1 year ago

Support for ExceptionGroups (except* ...) was added in astroid 2.15.0.

This looks like an issue with the VSCode pylint extension which still uses an outdated version. You can try to enable debug logging which might print the used version or even set pylint.importStrategy: fromEnvironment in your settings.json.