| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119 |
- #!/usr/bin/env python3
- """Checks diagnostic use.
- Validates that each diagnostic declared with CARBON_DIAGNOSTIC_KIND is
- referenced by one (and only one) CARBON_DIAGNOSTIC.
- """
- __copyright__ = """
- Part of the Carbon Language project, under the Apache License v2.0 with LLVM
- Exceptions. See /LICENSE for license information.
- SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
- """
- import collections
- from concurrent import futures
- import itertools
- import os
- from pathlib import Path
- import re
- import sys
- from typing import Dict, List, NamedTuple, Set
- # Example or test diagnostics, ignored because they're expected to not pass.
- IGNORED = set(["MyDiagnostic", "TestDiagnostic", "TestDiagnosticNote"])
- class Loc(NamedTuple):
- """A location for a diagnostic."""
- def __str__(self) -> str:
- return f"{str(self.path)}:{self.line}"
- path: Path
- line: int
- def load_diagnostic_kind() -> Set[str]:
- """Returns the set of declared diagnostic kinds.
- This isn't validated for uniqueness because the compiler does that.
- """
- path = Path("toolchain/diagnostics/diagnostic_kind.def")
- content = path.read_text()
- decls = set(re.findall(r"CARBON_DIAGNOSTIC_KIND\((.+)\)", content))
- return decls.difference(IGNORED)
- def load_diagnostic_uses_in(
- path: Path,
- ) -> Dict[str, List[Loc]]:
- """Returns the path's CARBON_DIAGNOSTIC uses."""
- content = path.read_text()
- # Keep a line cursor so that we don't keep re-scanning the file.
- line = 1
- line_offset = 0
- found: Dict[str, List[Loc]] = collections.defaultdict(lambda: [])
- for m in re.finditer(r"CARBON_DIAGNOSTIC\(\s*(\w+),", content):
- diag = m.group(1)
- if diag in IGNORED:
- continue
- line += content.count("\n", line_offset, m.start())
- line_offset = m.start()
- found[diag].append(Loc(path, line))
- return found
- def load_diagnostic_uses() -> Dict[str, List[Loc]]:
- """Returns all CARBON_DIAGNOSTIC uses."""
- globs = itertools.chain(
- *[Path("toolchain").glob(f"**/*.{ext}") for ext in ("h", "cpp")]
- )
- with futures.ThreadPoolExecutor() as exec:
- results = exec.map(load_diagnostic_uses_in, globs)
- found: Dict[str, List[Loc]] = collections.defaultdict(lambda: [])
- for result in results:
- for diag, locations in result.items():
- found[diag].extend(locations)
- return found
- def check_uniqueness(uses: Dict[str, List[Loc]]) -> bool:
- """If any diagnostic is non-unique, prints an error and returns true."""
- has_errors = False
- for diag in sorted(uses.keys()):
- if len(uses[diag]) > 1:
- print(f"Non-unique diagnostic {diag}:", file=sys.stderr)
- for loc in uses[diag]:
- print(f" - {loc}", file=sys.stderr)
- has_errors = True
- return has_errors
- def check_unused(decls: Set[str], uses: Dict[str, List[Loc]]) -> bool:
- """If any diagnostic is unused, prints an error and returns true."""
- unused = decls.difference(uses.keys())
- if not unused:
- return False
- for diag in sorted(unused):
- print(f"Unused diagnostic: {diag}")
- return True
- def main() -> None:
- # Run from the repo root.
- os.chdir(Path(__file__).parents[2])
- decls = load_diagnostic_kind()
- uses = load_diagnostic_uses()
- if any([check_uniqueness(uses), check_unused(decls, uses)]):
- exit(1)
- if __name__ == "__main__":
- main()
|