Skip to content

Long if-elif chain causes Ty to take 9+ minutes on 61 lines of code #4514

Description

@Nikratio

Summary

The following file is unfeasible to check with Ty 0.0.80 (I aborted after 9+ minutes). With Ty 0.0.17, it still finished sub-second.

I assume there is something pathologic about this code, and I wouldn't necessarily expect Ty to typecheck it more quickly, but I would much rather have it skip the file with a warning (or abort with an error) than hanging.

The reproducer code does not do anything useful, but the pattern (and problem) was discovered in a production codebase. I think it is triggered by migration code of the form:

try:
    from new_location.colors import *
except ImportError:
    class Color(enum.Enum):
        ...  # identical fallback definition

Reproducer

import enum
import random


class _ColorA(enum.Enum):
    RED = 1
    ORANGE = 2
    YELLOW = 3
    GREEN = 4
    BLUE = 5
    INDIGO = 6
    VIOLET = 7
    BLACK = 8
    WHITE = 9
    GRAY = 10


class _ColorB(enum.Enum):
    RED = 1
    ORANGE = 2
    YELLOW = 3
    GREEN = 4
    BLUE = 5
    INDIGO = 6
    VIOLET = 7
    BLACK = 8
    WHITE = 9
    GRAY = 10


Color = _ColorA if random.random() < 0.5 else _ColorB


def describe(item):
    if isinstance(item, str):
        return item

    if item == Color.RED:
        name = "red"
    elif item == Color.ORANGE:
        name = "orange"
    elif item == Color.YELLOW:
        name = "yellow"
    elif item == Color.GREEN:
        name = "green"
    elif item == Color.BLUE:
        name = "blue"
    elif item == Color.INDIGO:
        name = "indigo"
    elif item == Color.VIOLET:
        name = "violet"
    elif item == Color.BLACK:
        name = "black"
    elif item == Color.WHITE:
        name = "white"
    elif item == Color.GRAY:
        name = "gray"
    else:
        raise ValueError(item)

    return name

Scaling

Changing the number of elif branches in the code, I found:

branches check time
6 0.19 s
7 1.12 s
8 9.25 s
9 86.5 s
10 > 300 s

Workarounds

Each of the following changesbrings check time back below a second:

  • Delete the isinstance(item, str) guard and its return
  • Replace the union with a single class (Color = _ColorA)
  • Replace the elif chain with a dict lookup (name = names[item])

Version

I've checked it with:

 uvx ty -V
ty 0.0.80

> time uvx ty check reproducer.py 
Checking --------- 0/1 files^C
________________________________________________________
Executed in    6.53 secs    fish           external
   usr time    6.25 secs    0.00 millis    6.25 secs
   sys time    0.27 secs    1.53 millis    0.27 secs

With an older version, it's much faster:

> ty --version
ty 0.0.17

> ty check reproducer.py
All checks passed!

________________________________________________________
Executed in   31.25 millis    fish           external
   usr time   21.37 millis    0.17 millis   21.20 millis
   sys time   14.08 millis    1.03 millis   13.06 millis

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions