# Buggy code
def warn_or_error(
    removal_version: str,
    deprecated_entity_description: str,
    hint: Optional[str] = None,
    deprecation_start_version: Optional[str] = None,
    stacklevel: int = 3,
    frame_info: Optional[inspect.FrameInfo] = None,
    context: int = 1,
    ensure_stderr: bool = False,
    print_warning: bool = True,
) -> None:
    """Check the removal_version against the current pants version.

    Issues a warning if the removal version is > current pants version, or an error otherwise.

    :param removal_version: The pantsbuild.pants version at which the deprecated entity will
                            be/was removed.
    :param deprecated_entity_description: A short description of the deprecated entity, that
                                          we can embed in warning/error messages.
    :param hint: A message describing how to migrate from the removed entity.
    :param deprecation_start_version: The pantsbuild.pants version at which the entity will
                                      begin to display a deprecation warning. This must be less
                                      than the `removal_version`. If not provided, the
                                      deprecation warning is always displayed.
    :param stacklevel: The stacklevel to pass to warnings.warn.
    :param frame_info: If provided, use this frame info instead of getting one from `stacklevel`.
    :param context: The number of lines of source code surrounding the selected frame to display
                    in a warning message.
    :param ensure_stderr: Whether use warnings.warn, or use warnings.showwarning to print
                          directly to stderr.
    :param print_warning: Whether to print a warning for deprecations *before* their removal.
                          If this flag is off, an exception will still be raised for options
                          past their deprecation date.
    :raises DeprecationApplicationError: if the removal_version parameter is invalid.
    :raises CodeRemovedError: if the current version is later than the version marked for removal.
    """
    removal_semver = validate_deprecation_semver(removal_version, "removal version")
    if deprecation_start_version:
        deprecation_start_semver = validate_deprecation_semver(
            deprecation_start_version, "deprecation start version"
        )
        if deprecation_start_semver >= removal_semver:
            raise InvalidSemanticVersionOrderingError(
                "The deprecation start version {} must be less than the end version {}.".format(
                    deprecation_start_version, removal_version
                )
            )
        elif PANTS_SEMVER < deprecation_start_semver:
            return

    msg = "DEPRECATED: {} {} removed in version {}.".format(
        deprecated_entity_description,
        get_deprecated_tense(removal_version),
        removal_version,
    )
    if hint:
        msg += "\n  {}".format(hint)

    # We need to have filename and line_number for warnings.formatwarning, which appears to be the only
    # way to get a warning message to display to stderr. We get that from frame_info -- it's too bad
    # we have to reconstruct the `stacklevel` logic ourselves, but we do also gain the ability to have
    # multiple lines of context, which is neat.
    if frame_info is None:
        frame_info = _get_frame_info(stacklevel, context=context)
    _, filename, line_number, _, code_context, _ = frame_info
    if code_context:
        context_lines = "".join(code_context)
    else:
        context_lines = "<no code context available>"

    if removal_semver > PANTS_SEMVER:
        if ensure_stderr:
            # No warning filters can stop us from printing this message directly to stderr.
            warning_msg = warnings.formatwarning(
                msg, DeprecationWarning, filename, line_number, line=context_lines
            )
            print(warning_msg, file=sys.stderr)
        elif print_warning:
            # This output is filtered by warning filters.
            with _greater_warnings_context(context_lines):
                warnings.warn_explicit(
                    message=msg,
                    category=DeprecationWarning,
                    filename=filename,
                    lineno=line_number,
                )
        return
    else:
        raise CodeRemovedError(msg)


# Traceback (RecursionError)
Traceback (most recent call last):
File "<example>/pantsbuild.pants-1.25.0.dev3+git951fbda1-cp36-abi3-macosx_10_11_x86_64.whl/pants/engine/mapper.py", line 55, in parse
objects = parser.parse(filepath, filecontent)
File "<example>/pantsbuild.pants-1.25.0.dev3+git951fbda1-cp36-abi3-macosx_10_11_x86_64.whl/pants/engine/legacy/parser.py", line 177, in parse
self.check_for_deprecated_globs_usage(token_str, filepath, lineno)
File "<example>/pantsbuild.pants-1.25.0.dev3+git951fbda1-cp36-abi3-macosx_10_11_x86_64.whl/pants/engine/legacy/parser.py", line 152, in check_for_deprecated_globs_usage
hint=warning,
File "<example>/pantsbuild.pants-1.25.0.dev3+git951fbda1-cp36-abi3-macosx_10_11_x86_64.whl/pants/base/deprecated.py", line 211, in warn_or_error
lineno=line_number)
File "<python_dist>/lib/python3.6/warnings.py", line 99, in _showwarnmsg
msg.file, msg.line)
File "<example>/pantsbuild.pants-1.25.0.dev3+git951fbda1-cp36-abi3-macosx_10_11_x86_64.whl/pants/base/deprecated.py", line 127, in wrapped
line=(line or context_lines_string))
File "<example>/pantsbuild.pants-1.25.0.dev3+git951fbda1-cp36-abi3-macosx_10_11_x86_64.whl/pants/base/deprecated.py", line 127, in wrapped
line=(line or context_lines_string))
File "<example>/pantsbuild.pants-1.25.0.dev3+git951fbda1-cp36-abi3-macosx_10_11_x86_64.whl/pants/base/deprecated.py", line 127, in wrapped
line=(line or context_lines_string))
[Previous line repeated 968 more times]
File "<python_dist>/lib/python3.6/logging/__init__.py", line 2003, in _showwarning
logger.warning("%s", s)
File "<python_dist>/lib/python3.6/logging/__init__.py", line 1318, in warning
self._log(WARNING, msg, args, **kwargs)
File "<python_dist>/lib/python3.6/logging/__init__.py", line 1441, in _log
exc_info, func, extra, sinfo)
File "<python_dist>/lib/python3.6/logging/__init__.py", line 1411, in makeRecord
sinfo)
File "<python_dist>/lib/python3.6/logging/__init__.py", line 277, in __init__
if (args and len(args) == 1 and isinstance(args[0], collections.Mapping)
File "<python_dist>/lib/python3.6/abc.py", line 188, in __instancecheck__
subclass in cls._abc_negative_cache):
File "<python_dist>/lib/python3.6/_weakrefset.py", line 75, in __contains__
return wr in self.data
RecursionError: maximum recursion depth exceeded in comparison

# Fixed code
def warn_or_error(
    removal_version: str,
    deprecated_entity_description: str,
    hint: Optional[str] = None,
    deprecation_start_version: Optional[str] = None,
    stacklevel: int = 3,
    frame_info: Optional[inspect.FrameInfo] = None,
    ensure_stderr: bool = False,
    print_warning: bool = True,
) -> None:
    """Check the removal_version against the current pants version.

    Issues a warning if the removal version is > current pants version, or an error otherwise.

    :param removal_version: The pantsbuild.pants version at which the deprecated entity will
                            be/was removed.
    :param deprecated_entity_description: A short description of the deprecated entity, that
                                          we can embed in warning/error messages.
    :param hint: A message describing how to migrate from the removed entity.
    :param deprecation_start_version: The pantsbuild.pants version at which the entity will
                                      begin to display a deprecation warning. This must be less
                                      than the `removal_version`. If not provided, the
                                      deprecation warning is always displayed.
    :param stacklevel: The stacklevel to pass to warnings.warn, which determines the file name and
                       line number of the error message.
    :param frame_info: If provided, use this frame info instead of getting one from `stacklevel`.
    :param ensure_stderr: Whether use warnings.warn, or use warnings.showwarning to print
                          directly to stderr.
    :param print_warning: Whether to print a warning for deprecations *before* their removal.
                          If this flag is off, an exception will still be raised for options
                          past their deprecation date.
    :raises DeprecationApplicationError: if the removal_version parameter is invalid.
    :raises CodeRemovedError: if the current version is later than the version marked for removal.
    """
    removal_semver = validate_deprecation_semver(removal_version, "removal version")
    if deprecation_start_version:
        deprecation_start_semver = validate_deprecation_semver(
            deprecation_start_version, "deprecation start version"
        )
        if deprecation_start_semver >= removal_semver:
            raise InvalidSemanticVersionOrderingError(
                "The deprecation start version {} must be less than the end version {}.".format(
                    deprecation_start_version, removal_version
                )
            )
        elif PANTS_SEMVER < deprecation_start_semver:
            return

    msg = "DEPRECATED: {} {} removed in version {}.".format(
        deprecated_entity_description,
        get_deprecated_tense(removal_version),
        removal_version,
    )
    if hint:
        msg += "\n  {}".format(hint)

    # We need to have filename and line_number for warnings.formatwarning, which appears to be the only
    # way to get a warning message to display to stderr. We get that from frame_info.
    if frame_info is None:
        frame_info = _get_frame_info(stacklevel)
    _, filename, line_number, _, _, _ = frame_info

    if removal_semver > PANTS_SEMVER:
        if ensure_stderr:
            # No warning filters can stop us from printing this message directly to stderr.
            warning_msg = warnings.formatwarning(
                msg,
                DeprecationWarning,
                filename,
                line_number,
            )
            print(warning_msg, file=sys.stderr)
        elif print_warning:
            # This output is filtered by warning filters.
            warnings.warn_explicit(
                message=msg,
                category=DeprecationWarning,
                filename=filename,
                lineno=line_number,
            )
        return
    else:
        raise CodeRemovedError(msg)