mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-10-29 03:18:14 +08:00
1835 lines
63 KiB
Python
1835 lines
63 KiB
Python
"""
|
||
静态收集可证明的宿主 EventManager 的发布与消费事实。
|
||
"""
|
||
|
||
import ast
|
||
import hashlib
|
||
import json
|
||
from collections.abc import Mapping
|
||
from dataclasses import dataclass
|
||
from pathlib import Path
|
||
from typing import Any, Literal, TypeAlias
|
||
|
||
# 省略优先级参数时使用固定标识,避免把默认值解析成动态表达式。
|
||
_DEFAULT_IDENTITY = "<default>"
|
||
# 无法静态解析的处理函数或优先级使用固定标识,不包含易变行号。
|
||
_DYNAMIC_IDENTITY = "<dynamic>"
|
||
_EVENT_MANAGER_METHODS = {"add_event_listener", "register"}
|
||
_PRODUCER_METHODS = {
|
||
"async_send_event",
|
||
"async_send_event_strict",
|
||
"send_event",
|
||
"send_event_strict",
|
||
}
|
||
_COMPREHENSION_SCOPES = (
|
||
ast.ListComp,
|
||
ast.SetComp,
|
||
ast.DictComp,
|
||
ast.GeneratorExp,
|
||
)
|
||
# 这些节点建立独立名称绑定边界,外层局部名称收集不得直接穿透。
|
||
_FUNCTION_SCOPES = (
|
||
ast.FunctionDef,
|
||
ast.AsyncFunctionDef,
|
||
ast.Lambda,
|
||
ast.ClassDef,
|
||
*_COMPREHENSION_SCOPES,
|
||
)
|
||
|
||
|
||
@dataclass(frozen=True, slots=True)
|
||
class _Symbol:
|
||
"""
|
||
记录规范来源模块、EventManager 类或实例的静态来源。
|
||
|
||
:param kind: 已确认的来源类别:模块、事件管理器类或工厂、管理器或发布器实例、注入字段所属对象、类型检查标记
|
||
:param value: 来源附加信息,保存规范模块或类路径、实例来源标记;无需附加信息时默认空字符串
|
||
"""
|
||
|
||
kind: Literal[
|
||
"module",
|
||
"manager_class",
|
||
"manager_factory",
|
||
"manager_instance",
|
||
"publisher_instance",
|
||
"injected_owner",
|
||
"type_checking",
|
||
]
|
||
value: str = ""
|
||
|
||
|
||
@dataclass(frozen=True, slots=True)
|
||
class _EventSelection:
|
||
"""
|
||
记录一次注册可静态确定的事件集合及未知余项。
|
||
|
||
:param events: 可静态确认的事件成员完整名称,按确定顺序保存
|
||
:param kind: 选择形式:member 为单个成员,enum 为枚举类,list 为事件列表
|
||
:param dynamic: 是否包含无法静态确认的事件部分,默认 False
|
||
:param invalid: 是否包含不符合注册契约的内容,默认 False
|
||
"""
|
||
|
||
events: tuple[str, ...]
|
||
kind: Literal["member", "enum", "list"]
|
||
dynamic: bool = False
|
||
invalid: bool = False
|
||
|
||
|
||
@dataclass(frozen=True, slots=True)
|
||
class _BoundEventMethod:
|
||
"""
|
||
记录来源已证明的调用对象的 EventManager 或 EventPublisher 绑定方法。
|
||
|
||
:param method: 已绑定的监听注册或事件发布方法名,仅接受声明中的六种方法
|
||
:param receiver_kind: 接收调用的对象来源类别,用于区分管理器实例、自身调用及注入端口
|
||
"""
|
||
|
||
method: Literal[
|
||
"add_event_listener",
|
||
"register",
|
||
"send_event",
|
||
"send_event_strict",
|
||
"async_send_event",
|
||
"async_send_event_strict",
|
||
]
|
||
receiver_kind: str
|
||
|
||
|
||
@dataclass(frozen=True, slots=True)
|
||
class _DecoratorFactory:
|
||
"""
|
||
记录尚未应用到处理函数的 EventManager.register 返回值。
|
||
|
||
:param selection: 装饰器工厂已绑定的事件选择结果
|
||
:param priority: 优先级表达式的稳定标识;省略时为 <default>,无法解析时为 <dynamic>
|
||
:param receiver_kind: 调用 register 的对象来源类别
|
||
"""
|
||
|
||
selection: _EventSelection
|
||
priority: str
|
||
receiver_kind: str
|
||
|
||
|
||
@dataclass(frozen=True, slots=True)
|
||
class _Registration:
|
||
"""
|
||
记录调用对象来源已确认的事件注册。
|
||
|
||
:param method: 注册入口名称,区分 add_event_listener 与 register
|
||
:param selection: 该次注册包含的静态事件及动态、无效标记
|
||
:param handler: 处理函数的稳定标识;匿名函数记为 <lambda>,无法解析时为 <dynamic>
|
||
:param priority: 优先级表达式的稳定标识;省略时为 <default>,无法解析时为 <dynamic>
|
||
:param receiver_kind: 发起注册的对象来源类别
|
||
"""
|
||
|
||
method: Literal["add_event_listener", "register"]
|
||
selection: _EventSelection
|
||
handler: str
|
||
priority: str
|
||
receiver_kind: str
|
||
|
||
|
||
_ScopeValue: TypeAlias = (
|
||
_Symbol | _EventSelection | _DecoratorFactory | _BoundEventMethod | None
|
||
)
|
||
|
||
|
||
def _expression_name(node: ast.AST | None) -> str:
|
||
"""
|
||
返回 Name/Attribute 表达式的稳定点分名称。
|
||
"""
|
||
if isinstance(node, ast.Name):
|
||
return node.id
|
||
if isinstance(node, ast.Attribute):
|
||
prefix = _expression_name(node.value)
|
||
return ".".join(part for part in (prefix, node.attr) if part)
|
||
return ""
|
||
|
||
|
||
def _handler_identity(node: ast.AST | None) -> str:
|
||
"""
|
||
返回不含源码位置的处理函数标识。
|
||
"""
|
||
if name := _expression_name(node):
|
||
return name
|
||
if isinstance(node, ast.Lambda):
|
||
return "<lambda>"
|
||
return _DYNAMIC_IDENTITY
|
||
|
||
|
||
def _priority_identity(node: ast.AST | None) -> str:
|
||
"""
|
||
返回不含源码位置的优先级标识。
|
||
"""
|
||
if node is None:
|
||
return _DEFAULT_IDENTITY
|
||
if isinstance(node, ast.Constant):
|
||
return repr(node.value)
|
||
if name := _expression_name(node):
|
||
return name
|
||
return _DYNAMIC_IDENTITY
|
||
|
||
|
||
def fingerprint_event_fact(fact: Mapping[str, object]) -> str:
|
||
"""
|
||
计算排除诊断行号与已有摘要后的字段敏感 SHA256。
|
||
|
||
:param fact: 一条事件事实,line 与已有 fingerprint 不参与摘要
|
||
:return: 稳定 JSON 编码的 SHA256 十六进制摘要
|
||
"""
|
||
payload = {
|
||
key: value
|
||
for key, value in fact.items()
|
||
if key not in {"fingerprint", "line"}
|
||
}
|
||
encoded = json.dumps(
|
||
payload,
|
||
ensure_ascii=True,
|
||
separators=(",", ":"),
|
||
sort_keys=True,
|
||
).encode("utf-8")
|
||
return hashlib.sha256(encoded).hexdigest()
|
||
|
||
|
||
def _fact_sort_key(item: dict[str, Any]) -> tuple[str, str, int, str]:
|
||
"""
|
||
返回逐调用事件事实的确定排序键。
|
||
"""
|
||
return (
|
||
str(item["caller"]),
|
||
str(item["qualname"]),
|
||
int(item["line"]),
|
||
str(item["fingerprint"]),
|
||
)
|
||
|
||
|
||
def _annotation_names(node: ast.AST | None) -> set[str]:
|
||
"""
|
||
提取类型注解中的有限点分名称。
|
||
"""
|
||
if node is None:
|
||
return set()
|
||
if isinstance(node, ast.Name):
|
||
return {node.id}
|
||
if isinstance(node, ast.Attribute):
|
||
return {_expression_name(node)}
|
||
return {
|
||
name
|
||
for child in ast.iter_child_nodes(node)
|
||
for name in _annotation_names(child)
|
||
}
|
||
|
||
|
||
def _bound_names(target: ast.AST) -> set[str]:
|
||
"""
|
||
返回赋值目标在当前词法作用域绑定的名称。
|
||
"""
|
||
if isinstance(target, ast.Name):
|
||
return {target.id}
|
||
if isinstance(target, (ast.List, ast.Tuple)):
|
||
return {
|
||
name
|
||
for element in target.elts
|
||
for name in _bound_names(element)
|
||
}
|
||
if isinstance(target, ast.Starred):
|
||
return _bound_names(target.value)
|
||
return set()
|
||
|
||
|
||
def _pattern_bound_names(pattern: ast.pattern) -> set[str]:
|
||
"""
|
||
返回结构化匹配模式捕获到当前作用域的名称。
|
||
"""
|
||
if isinstance(pattern, ast.MatchAs):
|
||
names = _pattern_bound_names(pattern.pattern) if pattern.pattern else set()
|
||
if pattern.name:
|
||
names.add(pattern.name)
|
||
return names
|
||
if isinstance(pattern, ast.MatchStar):
|
||
return {pattern.name} if pattern.name else set()
|
||
if isinstance(pattern, ast.MatchSequence):
|
||
return {
|
||
name
|
||
for child in pattern.patterns
|
||
for name in _pattern_bound_names(child)
|
||
}
|
||
if isinstance(pattern, ast.MatchMapping):
|
||
names = {
|
||
name
|
||
for child in pattern.patterns
|
||
for name in _pattern_bound_names(child)
|
||
}
|
||
if pattern.rest:
|
||
names.add(pattern.rest)
|
||
return names
|
||
if isinstance(pattern, ast.MatchClass):
|
||
return {
|
||
name
|
||
for child in (*pattern.patterns, *pattern.kwd_patterns)
|
||
for name in _pattern_bound_names(child)
|
||
}
|
||
if isinstance(pattern, ast.MatchOr):
|
||
return {
|
||
name
|
||
for child in pattern.patterns
|
||
for name in _pattern_bound_names(child)
|
||
}
|
||
return set()
|
||
|
||
|
||
def _function_local_names(
|
||
node: ast.FunctionDef | ast.AsyncFunctionDef | ast.Lambda,
|
||
) -> set[str]:
|
||
"""
|
||
按 Python 词法作用域预收集函数局部绑定,避免回退到同名全局。
|
||
"""
|
||
parents: dict[ast.AST, ast.AST] = {}
|
||
for parent in ast.walk(node):
|
||
for child in ast.iter_child_nodes(parent):
|
||
parents[child] = parent
|
||
|
||
def belongs_to_function(
|
||
candidate: ast.AST,
|
||
*,
|
||
cross_comprehensions: bool = False,
|
||
) -> bool:
|
||
"""
|
||
判断节点是否属于当前函数而非嵌套作用域。
|
||
"""
|
||
parent = parents.get(candidate)
|
||
while parent is not None and parent is not node:
|
||
if isinstance(parent, _FUNCTION_SCOPES):
|
||
if not (
|
||
cross_comprehensions
|
||
and isinstance(parent, _COMPREHENSION_SCOPES)
|
||
):
|
||
return False
|
||
parent = parents.get(parent)
|
||
return parent is node
|
||
|
||
local_names = {
|
||
argument.arg
|
||
for argument in (
|
||
*node.args.posonlyargs,
|
||
*node.args.args,
|
||
*node.args.kwonlyargs,
|
||
)
|
||
}
|
||
if node.args.vararg:
|
||
local_names.add(node.args.vararg.arg)
|
||
if node.args.kwarg:
|
||
local_names.add(node.args.kwarg.arg)
|
||
global_names: set[str] = set()
|
||
nonlocal_names: set[str] = set()
|
||
for candidate in ast.walk(node):
|
||
if candidate is node or not belongs_to_function(candidate):
|
||
continue
|
||
if isinstance(candidate, ast.Name) and isinstance(
|
||
candidate.ctx,
|
||
(ast.Store, ast.Del),
|
||
):
|
||
local_names.add(candidate.id)
|
||
elif isinstance(candidate, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)):
|
||
local_names.add(candidate.name)
|
||
elif isinstance(candidate, (ast.Import, ast.ImportFrom)):
|
||
for alias in candidate.names:
|
||
if alias.name != "*":
|
||
local_names.add(alias.asname or alias.name.split(".", 1)[0])
|
||
elif isinstance(candidate, ast.ExceptHandler) and candidate.name:
|
||
local_names.add(candidate.name)
|
||
elif isinstance(candidate, ast.Match):
|
||
local_names.update(
|
||
name
|
||
for case in candidate.cases
|
||
for name in _pattern_bound_names(case.pattern)
|
||
)
|
||
elif isinstance(candidate, ast.Global):
|
||
global_names.update(candidate.names)
|
||
elif isinstance(candidate, ast.Nonlocal):
|
||
nonlocal_names.update(candidate.names)
|
||
for candidate in ast.walk(node):
|
||
if isinstance(candidate, ast.NamedExpr) and belongs_to_function(
|
||
candidate,
|
||
cross_comprehensions=True,
|
||
):
|
||
local_names.update(_bound_names(candidate.target))
|
||
return local_names - global_names - nonlocal_names
|
||
|
||
|
||
def _event_port_symbol(
|
||
annotation: ast.AST | None,
|
||
canonical_aliases: Mapping[str, str] | None = None,
|
||
) -> _Symbol | None:
|
||
"""
|
||
把明确的 EventPublisher/EventManager 注解转换为调用对象来源。
|
||
"""
|
||
annotation_names = _annotation_names(annotation)
|
||
leaf_names = {name.rsplit(".", 1)[-1] for name in annotation_names}
|
||
if any(name.endswith("EventPublisher") for name in leaf_names):
|
||
return _Symbol("publisher_instance", "injected_event_publisher")
|
||
if any(name.endswith("EventManagerPort") for name in leaf_names):
|
||
return _Symbol("manager_instance", "injected_event_manager")
|
||
for name in annotation_names:
|
||
head, *tail = name.split(".")
|
||
canonical = ".".join(
|
||
((canonical_aliases or {}).get(head, head), *tail)
|
||
)
|
||
if canonical == "app.runtime.events.EventManager":
|
||
return _Symbol("manager_instance", "injected_event_manager")
|
||
return None
|
||
|
||
|
||
def _function_parameter_symbols(
|
||
node: ast.FunctionDef | ast.AsyncFunctionDef | ast.Lambda,
|
||
canonical_aliases: Mapping[str, str] | None = None,
|
||
) -> dict[str, _Symbol]:
|
||
"""
|
||
收集函数参数上明确声明的 Event 发布端口。
|
||
"""
|
||
arguments = (
|
||
*node.args.posonlyargs,
|
||
*node.args.args,
|
||
*node.args.kwonlyargs,
|
||
)
|
||
return {
|
||
argument.arg: symbol
|
||
for argument in arguments
|
||
if (
|
||
symbol := _event_port_symbol(
|
||
argument.annotation,
|
||
canonical_aliases,
|
||
)
|
||
) is not None
|
||
}
|
||
|
||
|
||
_InjectedFieldKey: TypeAlias = tuple[str, str]
|
||
|
||
|
||
def _resolve_import_from_module(
|
||
module_name: str,
|
||
node: ast.ImportFrom,
|
||
package_modules: set[str],
|
||
) -> str:
|
||
"""
|
||
把绝对或相对 from-import 解析为规范来源模块。
|
||
"""
|
||
if not node.level:
|
||
return node.module or ""
|
||
current_parts = module_name.split(".")
|
||
package_parts = (
|
||
current_parts
|
||
if module_name in package_modules
|
||
else current_parts[:-1]
|
||
)
|
||
ascend = node.level - 1
|
||
if ascend > len(package_parts):
|
||
return ""
|
||
base_parts = package_parts[:len(package_parts) - ascend]
|
||
if node.module:
|
||
base_parts.extend(node.module.split("."))
|
||
return ".".join(base_parts)
|
||
|
||
|
||
def _statement_bound_names(statement: ast.stmt) -> set[str]:
|
||
"""
|
||
返回模块语句直接或条件分支可能绑定的名称。
|
||
"""
|
||
if isinstance(statement, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)):
|
||
return {statement.name}
|
||
if isinstance(statement, (ast.Assign, ast.AnnAssign, ast.AugAssign)):
|
||
targets = (
|
||
statement.targets
|
||
if isinstance(statement, ast.Assign)
|
||
else (statement.target,)
|
||
)
|
||
return {name for target in targets for name in _bound_names(target)}
|
||
if isinstance(statement, ast.Import):
|
||
return {item.asname or item.name.split(".", 1)[0] for item in statement.names}
|
||
if isinstance(statement, ast.ImportFrom):
|
||
return {item.asname or item.name for item in statement.names}
|
||
if isinstance(statement, ast.If):
|
||
return {
|
||
name
|
||
for branch in (statement.body, statement.orelse)
|
||
for child in branch
|
||
for name in _statement_bound_names(child)
|
||
}
|
||
return set()
|
||
|
||
|
||
def _runtime_module_statements(tree: ast.Module) -> list[ast.stmt]:
|
||
"""
|
||
展开可证明的 TYPE_CHECKING 运行期分支,保留其余顶层语句。
|
||
"""
|
||
type_checking_names: set[str] = set()
|
||
typing_modules: set[str] = set()
|
||
runtime_statements: list[ast.stmt] = []
|
||
|
||
def runtime_value(test: ast.expr) -> bool | None:
|
||
"""
|
||
判断表达式是否是当前未遮蔽的 typing.TYPE_CHECKING。
|
||
"""
|
||
negate = isinstance(test, ast.UnaryOp) and isinstance(test.op, ast.Not)
|
||
target = test.operand if negate else test
|
||
name = _expression_name(target)
|
||
proven = name in type_checking_names or any(
|
||
name == f"{module}.TYPE_CHECKING" for module in typing_modules
|
||
)
|
||
return negate if proven else None
|
||
|
||
def visit(statements: list[ast.stmt]) -> None:
|
||
"""
|
||
按模块执行顺序收集可证明会运行的语句。
|
||
"""
|
||
for statement in statements:
|
||
if isinstance(statement, ast.If):
|
||
value = runtime_value(statement.test)
|
||
if value is not None:
|
||
visit(statement.body if value else statement.orelse)
|
||
continue
|
||
rebound = _statement_bound_names(statement)
|
||
type_checking_names.difference_update(rebound)
|
||
typing_modules.difference_update(rebound)
|
||
runtime_statements.append(statement)
|
||
continue
|
||
|
||
rebound = _statement_bound_names(statement)
|
||
type_checking_names.difference_update(rebound)
|
||
typing_modules.difference_update(rebound)
|
||
runtime_statements.append(statement)
|
||
if isinstance(statement, ast.Import):
|
||
for item in statement.names:
|
||
if item.name == "typing":
|
||
typing_modules.add(item.asname or "typing")
|
||
elif isinstance(statement, ast.ImportFrom) and statement.module == "typing":
|
||
for item in statement.names:
|
||
if item.name == "TYPE_CHECKING":
|
||
type_checking_names.add(item.asname or item.name)
|
||
|
||
visit(tree.body)
|
||
return runtime_statements
|
||
|
||
|
||
def _module_import_aliases(
|
||
statements: list[ast.stmt],
|
||
*,
|
||
module_name: str,
|
||
package_modules: set[str],
|
||
) -> dict[str, str]:
|
||
"""
|
||
收集解析类继承关系所需的模块级导入别名。
|
||
"""
|
||
aliases: dict[str, str] = {}
|
||
for node in statements:
|
||
if isinstance(node, ast.Import):
|
||
for item in node.names:
|
||
bound = item.asname or item.name.split(".", 1)[0]
|
||
aliases[bound] = item.name if item.asname else bound
|
||
elif isinstance(node, ast.ImportFrom):
|
||
source_module = _resolve_import_from_module(
|
||
module_name,
|
||
node,
|
||
package_modules,
|
||
)
|
||
for item in node.names:
|
||
if item.name != "*":
|
||
canonical = ".".join(
|
||
part for part in (source_module, item.name) if part
|
||
)
|
||
aliases[item.asname or item.name] = canonical
|
||
return aliases
|
||
|
||
|
||
def _module_class_aliases(
|
||
statements: list[ast.stmt],
|
||
*,
|
||
module_name: str,
|
||
package_modules: set[str],
|
||
) -> dict[str, str]:
|
||
"""
|
||
收集运行期可证明的模块级简单类别名及其规范目标。
|
||
"""
|
||
aliases: dict[str, str] = {}
|
||
bindings: dict[str, str] = {}
|
||
|
||
def resolve(node: ast.expr) -> str:
|
||
"""
|
||
仅从当前未遮蔽绑定解析类别名右值。
|
||
"""
|
||
name = _expression_name(node)
|
||
if not name:
|
||
return ""
|
||
head, *tail = name.split(".")
|
||
if head not in bindings:
|
||
return ""
|
||
return ".".join((bindings[head], *tail))
|
||
|
||
for statement in statements:
|
||
bound_names = _statement_bound_names(statement)
|
||
target = (
|
||
resolve(statement.value)
|
||
if isinstance(statement, (ast.Assign, ast.AnnAssign))
|
||
and statement.value is not None
|
||
else ""
|
||
)
|
||
for name in bound_names:
|
||
bindings.pop(name, None)
|
||
aliases.pop(f"{module_name}.{name}", None)
|
||
|
||
if isinstance(statement, ast.Import):
|
||
for item in statement.names:
|
||
bound = item.asname or item.name.split(".", 1)[0]
|
||
bindings[bound] = item.name if item.asname else bound
|
||
continue
|
||
if isinstance(statement, ast.ImportFrom):
|
||
source_module = _resolve_import_from_module(
|
||
module_name,
|
||
statement,
|
||
package_modules,
|
||
)
|
||
for item in statement.names:
|
||
if item.name != "*":
|
||
canonical = ".".join(
|
||
part for part in (source_module, item.name) if part
|
||
)
|
||
bindings[item.asname or item.name] = canonical
|
||
continue
|
||
if isinstance(statement, ast.ClassDef):
|
||
bindings[statement.name] = f"{module_name}.{statement.name}"
|
||
continue
|
||
if isinstance(statement, ast.Assign):
|
||
targets = statement.targets
|
||
elif isinstance(statement, ast.AnnAssign) and statement.value is not None:
|
||
targets = (statement.target,)
|
||
else:
|
||
continue
|
||
target_names = [
|
||
target.id for target in targets if isinstance(target, ast.Name)
|
||
]
|
||
if len(target_names) != len(targets) or not target:
|
||
continue
|
||
for name in target_names:
|
||
bindings[name] = target
|
||
aliases[f"{module_name}.{name}"] = target
|
||
return aliases
|
||
|
||
|
||
def _iter_classes(
|
||
statements: list[ast.stmt],
|
||
prefix: tuple[str, ...] = (),
|
||
) -> list[tuple[str, ast.ClassDef]]:
|
||
"""
|
||
按词法限定名返回语句中的类定义,不进入函数作用域。
|
||
"""
|
||
classes: list[tuple[str, ast.ClassDef]] = []
|
||
for statement in statements:
|
||
if not isinstance(statement, ast.ClassDef):
|
||
continue
|
||
qualname = ".".join((*prefix, statement.name))
|
||
classes.append((qualname, statement))
|
||
classes.extend(_iter_classes(statement.body, (*prefix, statement.name)))
|
||
return classes
|
||
|
||
|
||
def _canonical_class_name(
|
||
node: ast.expr,
|
||
*,
|
||
module_name: str,
|
||
aliases: dict[str, str],
|
||
) -> str:
|
||
"""
|
||
把有限 Name/Attribute 基类表达式还原成规范类名。
|
||
"""
|
||
name = _expression_name(node)
|
||
if not name:
|
||
return ""
|
||
head, *tail = name.split(".")
|
||
if head in aliases:
|
||
return ".".join((aliases[head], *tail))
|
||
return f"{module_name}.{name}"
|
||
|
||
|
||
def _discover_injected_event_fields(
|
||
trees: dict[str, ast.Module],
|
||
package_modules: set[str],
|
||
) -> dict[_InjectedFieldKey, dict[str, _Symbol]]:
|
||
"""
|
||
按所属类发现构造注入字段,并沿已知继承关系传播。
|
||
"""
|
||
runtime_statements = {
|
||
module_name: _runtime_module_statements(tree)
|
||
for module_name, tree in trees.items()
|
||
}
|
||
classes = {
|
||
f"{module_name}.{qualname}": (module_name, qualname, node)
|
||
for module_name, statements in runtime_statements.items()
|
||
for qualname, node in _iter_classes(statements)
|
||
}
|
||
aliases = {
|
||
module_name: _module_import_aliases(
|
||
runtime_statements[module_name],
|
||
module_name=module_name,
|
||
package_modules=package_modules,
|
||
)
|
||
for module_name in trees
|
||
}
|
||
class_aliases = {
|
||
alias: target
|
||
for module_name, statements in runtime_statements.items()
|
||
for alias, target in _module_class_aliases(
|
||
statements,
|
||
module_name=module_name,
|
||
package_modules=package_modules,
|
||
).items()
|
||
}
|
||
|
||
def resolve_class_alias(canonical: str) -> str:
|
||
"""
|
||
传递解析简单类别名,并拒绝循环别名。
|
||
"""
|
||
visited: set[str] = set()
|
||
while canonical in class_aliases and canonical not in visited:
|
||
visited.add(canonical)
|
||
canonical = class_aliases[canonical]
|
||
return "" if canonical in visited else canonical
|
||
|
||
fields: dict[str, dict[str, _Symbol]] = {
|
||
canonical: {} for canonical in classes
|
||
}
|
||
bases_by_class: dict[str, set[str]] = {
|
||
canonical: set() for canonical in classes
|
||
}
|
||
descendants_by_class: dict[str, set[str]] = {
|
||
canonical: set() for canonical in classes
|
||
}
|
||
for canonical, (module_name, _qualname, class_node) in classes.items():
|
||
for base in class_node.bases:
|
||
base_name = _canonical_class_name(
|
||
base,
|
||
module_name=module_name,
|
||
aliases=aliases[module_name],
|
||
)
|
||
base_name = resolve_class_alias(base_name)
|
||
if base_name in classes:
|
||
bases_by_class[canonical].add(base_name)
|
||
descendants_by_class[base_name].add(canonical)
|
||
|
||
for canonical, (module_name, qualname, class_node) in classes.items():
|
||
class_fields = fields[canonical]
|
||
for constructor in (
|
||
node
|
||
for node in class_node.body
|
||
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef))
|
||
and node.name == "__init__"
|
||
):
|
||
parameters = _function_parameter_symbols(
|
||
constructor,
|
||
aliases[module_name],
|
||
)
|
||
for candidate in constructor.body:
|
||
candidates = ast.walk(candidate) if not isinstance(
|
||
candidate,
|
||
(ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef),
|
||
) else ()
|
||
for assignment in candidates:
|
||
if isinstance(assignment, ast.Assign):
|
||
pairs = (
|
||
(target, assignment.value)
|
||
for target in assignment.targets
|
||
)
|
||
elif isinstance(assignment, ast.AnnAssign) and assignment.value:
|
||
pairs = ((assignment.target, assignment.value),)
|
||
else:
|
||
continue
|
||
for target, value in pairs:
|
||
if not (
|
||
isinstance(target, ast.Attribute)
|
||
and isinstance(target.value, ast.Name)
|
||
and target.value.id == "self"
|
||
):
|
||
continue
|
||
symbol = (
|
||
parameters.get(value.id)
|
||
if isinstance(value, ast.Name)
|
||
else None
|
||
)
|
||
if (
|
||
symbol is None
|
||
and module_name == "app.chain.base"
|
||
and qualname == "ChainBase"
|
||
and target.attr == "eventmanager"
|
||
and isinstance(value, ast.Attribute)
|
||
and value.attr == "event_manager"
|
||
):
|
||
symbol = _Symbol(
|
||
"manager_instance",
|
||
"injected_event_manager",
|
||
)
|
||
if symbol is not None:
|
||
class_fields[target.attr] = symbol
|
||
|
||
direct_fields = {
|
||
canonical: dict(class_fields)
|
||
for canonical, class_fields in fields.items()
|
||
if class_fields
|
||
}
|
||
for owner, owner_fields in direct_fields.items():
|
||
def reachable(
|
||
starts: set[str],
|
||
relations: dict[str, set[str]],
|
||
) -> set[str]:
|
||
"""
|
||
返回给定继承方向上的传递闭包。
|
||
"""
|
||
pending = list(starts)
|
||
reached: set[str] = set()
|
||
while pending:
|
||
current = pending.pop()
|
||
if current in reached:
|
||
continue
|
||
reached.add(current)
|
||
pending.extend(relations[current])
|
||
return reached
|
||
|
||
descendants = reachable({owner}, descendants_by_class)
|
||
targets = set(descendants)
|
||
targets.update(reachable(descendants, bases_by_class))
|
||
for target in targets:
|
||
fields[target].update(owner_fields)
|
||
|
||
return {
|
||
(module_name, qualname): fields[canonical]
|
||
for canonical, (module_name, qualname, _node) in classes.items()
|
||
if fields[canonical]
|
||
}
|
||
|
||
|
||
class _EventFactCollector(ast.NodeVisitor):
|
||
"""
|
||
以有限词法来源追踪收集单个宿主模块的事件事实。
|
||
"""
|
||
|
||
def __init__(
|
||
self,
|
||
module_name: str,
|
||
event_members: dict[str, tuple[str, ...]],
|
||
*,
|
||
collect_facts: bool,
|
||
module_final_scope: dict[str, _ScopeValue] | None = None,
|
||
injected_fields: dict[_InjectedFieldKey, dict[str, _Symbol]] | None = None,
|
||
) -> None:
|
||
"""
|
||
初始化模块、事件枚举、收集模式及已证明的注入字段。
|
||
"""
|
||
self._module_name = module_name
|
||
self._event_members = event_members
|
||
self._collect_facts = collect_facts
|
||
self._module_final_scope = module_final_scope or {}
|
||
self._injected_fields = injected_fields or {}
|
||
self._scopes: list[dict[str, _ScopeValue]] = [{}]
|
||
self._scope_kinds = ["module"]
|
||
self._function_final_scopes: list[dict[str, _ScopeValue]] = []
|
||
self._qualnames: list[str] = []
|
||
self._class_qualnames: list[str] = []
|
||
self.producers: list[dict[str, Any]] = []
|
||
self.consumers: list[dict[str, Any]] = []
|
||
|
||
def module_scope(self) -> dict[str, _ScopeValue]:
|
||
"""
|
||
返回按模块执行顺序收敛后的符号状态。
|
||
"""
|
||
return dict(self._scopes[0])
|
||
|
||
def _lookup(self, name: str) -> _ScopeValue:
|
||
"""
|
||
从内向外解析词法绑定。
|
||
"""
|
||
skip_class_scope = self._scope_kinds[-1] == "comprehension"
|
||
for scope, kind in reversed(list(zip(self._scopes, self._scope_kinds))):
|
||
if skip_class_scope and kind == "class":
|
||
continue
|
||
if name in scope:
|
||
return scope[name]
|
||
return None
|
||
|
||
def _set(self, name: str, value: _ScopeValue) -> None:
|
||
"""
|
||
在当前词法作用域写入或清空绑定。
|
||
"""
|
||
self._scopes[-1][name] = value
|
||
|
||
@staticmethod
|
||
def _merge_scope_states(
|
||
states: list[list[dict[str, _ScopeValue]]],
|
||
) -> list[dict[str, _ScopeValue]]:
|
||
"""
|
||
仅保留所有控制流路径一致的静态来源信息。
|
||
"""
|
||
return [
|
||
{
|
||
name: (
|
||
scopes[0].get(name)
|
||
if all(
|
||
scope.get(name) == scopes[0].get(name)
|
||
for scope in scopes[1:]
|
||
)
|
||
else None
|
||
)
|
||
for name in set().union(*(scope.keys() for scope in scopes))
|
||
}
|
||
for scopes in zip(*states)
|
||
]
|
||
|
||
def _discover_scope_after(
|
||
self,
|
||
statements: list[ast.stmt],
|
||
scopes: list[dict[str, _ScopeValue]],
|
||
scope_kinds: list[str],
|
||
) -> dict[str, _ScopeValue]:
|
||
"""
|
||
无事实副作用地计算一组语句执行后的最内层作用域。
|
||
"""
|
||
discovery = _EventFactCollector(
|
||
self._module_name,
|
||
self._event_members,
|
||
collect_facts=False,
|
||
module_final_scope=self._module_final_scope,
|
||
injected_fields=self._injected_fields,
|
||
)
|
||
discovery._scopes = [dict(scope) for scope in scopes]
|
||
discovery._scope_kinds = list(scope_kinds)
|
||
discovery._qualnames = list(self._qualnames)
|
||
discovery._class_qualnames = list(self._class_qualnames)
|
||
for statement in statements:
|
||
discovery.visit(statement)
|
||
return dict(discovery._scopes[-1])
|
||
|
||
def _function_initial_scope(
|
||
self,
|
||
node: ast.FunctionDef | ast.AsyncFunctionDef | ast.Lambda,
|
||
) -> dict[str, _ScopeValue]:
|
||
"""
|
||
构造函数调用期的局部符号,并保留明确的参数端口来源信息。
|
||
"""
|
||
scope: dict[str, _ScopeValue] = {
|
||
name: None for name in _function_local_names(node)
|
||
}
|
||
canonical_aliases = {
|
||
name: "app.runtime.events.EventManager"
|
||
for name, value in self._module_final_scope.items()
|
||
if isinstance(value, _Symbol) and value.kind == "manager_class"
|
||
}
|
||
if (
|
||
isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef))
|
||
and node.name == "__init__"
|
||
and self._class_qualnames
|
||
):
|
||
scope.update(_function_parameter_symbols(node, canonical_aliases))
|
||
positional = (*node.args.posonlyargs, *node.args.args)
|
||
if (
|
||
self._module_name == "app.runtime.events"
|
||
and self._qualnames == ["EventManager"]
|
||
and positional
|
||
and positional[0].arg == "self"
|
||
):
|
||
scope["self"] = _Symbol("publisher_instance", "event_manager_self")
|
||
elif (
|
||
self._class_qualnames
|
||
and positional
|
||
and not any(
|
||
_expression_name(decorator) in {"classmethod", "staticmethod"}
|
||
for decorator in getattr(node, "decorator_list", ())
|
||
)
|
||
and (
|
||
self._module_name,
|
||
self._class_qualnames[-1],
|
||
) in self._injected_fields
|
||
):
|
||
scope[positional[0].arg] = _Symbol(
|
||
"injected_owner",
|
||
self._class_qualnames[-1],
|
||
)
|
||
return scope
|
||
|
||
def _symbol_for_canonical(self, canonical: str) -> _ScopeValue:
|
||
"""
|
||
把已知规范路径转换为收集器使用的符号。
|
||
"""
|
||
if canonical == "app.runtime.events.eventmanager":
|
||
return _Symbol("manager_instance", "canonical_singleton")
|
||
if canonical == "app.runtime.events.EventManager":
|
||
return _Symbol("manager_class")
|
||
if canonical == "typing.TYPE_CHECKING":
|
||
return _Symbol("type_checking")
|
||
if canonical in {
|
||
"app",
|
||
"app.runtime",
|
||
"app.runtime.events",
|
||
"app.schemas",
|
||
"app.schemas.types",
|
||
"typing",
|
||
}:
|
||
return _Symbol("module", canonical)
|
||
for enum_name, members in self._event_members.items():
|
||
if canonical == f"app.schemas.types.{enum_name}":
|
||
return _EventSelection(
|
||
events=tuple(f"{enum_name}.{member}" for member in members),
|
||
kind="enum",
|
||
)
|
||
return None
|
||
|
||
def _runtime_type_checking_value(self, test: ast.expr) -> bool | None:
|
||
"""
|
||
仅对可证明的 typing.TYPE_CHECKING 返回确定运行期分支。
|
||
"""
|
||
negate = isinstance(test, ast.UnaryOp) and isinstance(test.op, ast.Not)
|
||
target = test.operand if negate else test
|
||
resolved = self._resolve(target)
|
||
if isinstance(resolved, _Symbol) and resolved.kind == "type_checking":
|
||
return negate
|
||
return None
|
||
|
||
def _resolve(self, node: ast.AST) -> _ScopeValue:
|
||
"""
|
||
解析已知导入、模块和赋值别名,不跨函数推断返回值。
|
||
"""
|
||
if isinstance(node, ast.Name):
|
||
return self._lookup(node.id)
|
||
if isinstance(node, ast.Attribute):
|
||
parent = self._resolve(node.value)
|
||
if isinstance(parent, _Symbol) and parent.kind == "injected_owner":
|
||
fields = self._injected_fields.get(
|
||
(self._module_name, parent.value),
|
||
{},
|
||
)
|
||
if node.attr in fields:
|
||
return fields[node.attr]
|
||
if isinstance(parent, _Symbol) and parent.kind == "module":
|
||
return self._symbol_for_canonical(f"{parent.value}.{node.attr}")
|
||
if isinstance(parent, _Symbol) and parent.kind == "manager_class":
|
||
if node.attr == "get_existing_instance":
|
||
return _Symbol("manager_factory")
|
||
if isinstance(parent, _Symbol) and parent.kind in {
|
||
"manager_instance",
|
||
"publisher_instance",
|
||
}:
|
||
methods = (
|
||
_PRODUCER_METHODS | _EVENT_MANAGER_METHODS
|
||
if parent.kind == "manager_instance"
|
||
else _PRODUCER_METHODS
|
||
)
|
||
if node.attr in methods:
|
||
return _BoundEventMethod(node.attr, parent.value)
|
||
if isinstance(parent, _EventSelection) and parent.kind == "enum":
|
||
enum_name = parent.events[0].split(".", 1)[0] if parent.events else ""
|
||
if node.attr in self._event_members.get(enum_name, ()):
|
||
return _EventSelection(
|
||
events=(f"{enum_name}.{node.attr}",),
|
||
kind="member",
|
||
)
|
||
return _EventSelection((), "member", invalid=True)
|
||
return None
|
||
if isinstance(node, ast.List):
|
||
return self._resolve_event_selection(
|
||
node,
|
||
allow_enum=True,
|
||
allow_list=True,
|
||
)
|
||
if isinstance(node, ast.IfExp):
|
||
return self._resolve_event_selection(
|
||
node,
|
||
allow_enum=False,
|
||
allow_list=False,
|
||
)
|
||
if isinstance(node, ast.Call):
|
||
target = self._resolve(node.func)
|
||
if isinstance(target, _Symbol) and target.kind in {
|
||
"manager_class",
|
||
"manager_factory",
|
||
}:
|
||
receiver_kind = (
|
||
"constructed_manager"
|
||
if target.kind == "manager_class"
|
||
else "existing_manager"
|
||
)
|
||
return _Symbol("manager_instance", receiver_kind)
|
||
registration = self._registration(node)
|
||
if registration and registration.method == "register":
|
||
return _DecoratorFactory(
|
||
selection=registration.selection,
|
||
priority=registration.priority,
|
||
receiver_kind=registration.receiver_kind,
|
||
)
|
||
return None
|
||
|
||
def _resolve_event_selection(
|
||
self,
|
||
node: ast.AST,
|
||
*,
|
||
allow_enum: bool,
|
||
allow_list: bool,
|
||
) -> _EventSelection:
|
||
"""
|
||
解析单个事件、枚举类或 register 接受的静态列表。
|
||
"""
|
||
if isinstance(node, ast.List):
|
||
if not allow_list:
|
||
return _EventSelection((), "list", dynamic=True)
|
||
selections = [
|
||
self._resolve_event_selection(
|
||
item,
|
||
allow_enum=True,
|
||
allow_list=False,
|
||
)
|
||
for item in node.elts
|
||
]
|
||
return _EventSelection(
|
||
events=tuple(sorted({event for item in selections for event in item.events})),
|
||
kind="list",
|
||
dynamic=any(item.dynamic for item in selections),
|
||
invalid=any(item.invalid for item in selections),
|
||
)
|
||
if isinstance(node, ast.IfExp):
|
||
branches = (
|
||
self._resolve_event_selection(
|
||
node.body,
|
||
allow_enum=allow_enum,
|
||
allow_list=allow_list,
|
||
),
|
||
self._resolve_event_selection(
|
||
node.orelse,
|
||
allow_enum=allow_enum,
|
||
allow_list=allow_list,
|
||
),
|
||
)
|
||
return _EventSelection(
|
||
events=tuple(sorted({event for item in branches for event in item.events})),
|
||
kind="list" if allow_list else "member",
|
||
dynamic=any(item.dynamic for item in branches),
|
||
invalid=any(item.invalid for item in branches),
|
||
)
|
||
resolved = self._resolve(node)
|
||
if isinstance(resolved, _EventSelection):
|
||
if resolved.kind == "member":
|
||
return resolved
|
||
if resolved.kind == "enum" and allow_enum:
|
||
return resolved
|
||
if resolved.kind == "list" and allow_list:
|
||
return resolved
|
||
return _EventSelection((), "member", dynamic=True)
|
||
|
||
@staticmethod
|
||
def _bind_call_arguments(
|
||
node: ast.Call,
|
||
parameter_names: tuple[str, ...],
|
||
required_names: frozenset[str],
|
||
) -> dict[str, ast.AST] | None:
|
||
"""
|
||
按真实 Python 调用规则绑定有限签名,拒绝未知或重复参数。
|
||
"""
|
||
if len(node.args) > len(parameter_names) or any(
|
||
isinstance(argument, ast.Starred) for argument in node.args
|
||
):
|
||
return None
|
||
arguments = dict(zip(parameter_names, node.args))
|
||
for keyword in node.keywords:
|
||
if (
|
||
keyword.arg is None
|
||
or keyword.arg not in parameter_names
|
||
or keyword.arg in arguments
|
||
):
|
||
return None
|
||
arguments[keyword.arg] = keyword.value
|
||
if not required_names.issubset(arguments):
|
||
return None
|
||
return arguments
|
||
|
||
def _registration(self, node: ast.Call) -> _Registration | None:
|
||
"""
|
||
仅解析调用对象来源已证明为规范 EventManager 实例的注册。
|
||
"""
|
||
bound_method = self._resolve(node.func)
|
||
if not (
|
||
isinstance(bound_method, _BoundEventMethod)
|
||
and bound_method.method in _EVENT_MANAGER_METHODS
|
||
):
|
||
return None
|
||
method = bound_method.method
|
||
if method == "register":
|
||
arguments = self._bind_call_arguments(
|
||
node,
|
||
("etype", "priority"),
|
||
frozenset({"etype"}),
|
||
)
|
||
event_name = "etype"
|
||
handler_node = None
|
||
else:
|
||
arguments = self._bind_call_arguments(
|
||
node,
|
||
("event_type", "handler", "priority"),
|
||
frozenset({"event_type", "handler"}),
|
||
)
|
||
event_name = "event_type"
|
||
handler_node = arguments.get("handler") if arguments else None
|
||
if arguments is None:
|
||
return None
|
||
selection = self._resolve_event_selection(
|
||
arguments[event_name],
|
||
allow_enum=method == "register",
|
||
allow_list=method == "register",
|
||
)
|
||
return _Registration(
|
||
method=method,
|
||
selection=selection,
|
||
handler=_handler_identity(handler_node),
|
||
priority=_priority_identity(arguments.get("priority")),
|
||
receiver_kind=bound_method.receiver_kind,
|
||
)
|
||
|
||
def _producer_call(
|
||
self,
|
||
node: ast.Call,
|
||
) -> tuple[_BoundEventMethod, _EventSelection] | None:
|
||
"""
|
||
解析调用对象与调用签名均可证明的事件发布。
|
||
"""
|
||
bound_method = self._resolve(node.func)
|
||
if not (
|
||
isinstance(bound_method, _BoundEventMethod)
|
||
and bound_method.method in _PRODUCER_METHODS
|
||
):
|
||
return None
|
||
arguments = self._bind_call_arguments(
|
||
node,
|
||
("etype", "data", "priority"),
|
||
frozenset({"etype"}),
|
||
)
|
||
if arguments is None:
|
||
return None
|
||
return (
|
||
bound_method,
|
||
self._resolve_event_selection(
|
||
arguments["etype"],
|
||
allow_enum=False,
|
||
allow_list=False,
|
||
),
|
||
)
|
||
|
||
def _decorator_factory_application(
|
||
self,
|
||
node: ast.Call,
|
||
) -> tuple[_DecoratorFactory, ast.AST] | None:
|
||
"""
|
||
解析合法的 decorator(f) 或 decorator(f=...) 立即应用。
|
||
"""
|
||
factory = self._resolve(node.func)
|
||
if not isinstance(factory, _DecoratorFactory):
|
||
return None
|
||
arguments = self._bind_call_arguments(
|
||
node,
|
||
("f",),
|
||
frozenset({"f"}),
|
||
)
|
||
if arguments is None:
|
||
return None
|
||
return factory, arguments["f"]
|
||
|
||
def _base_fact(
|
||
self,
|
||
node: ast.AST,
|
||
selection: _EventSelection,
|
||
*,
|
||
method: str,
|
||
receiver_kind: str,
|
||
) -> dict[str, Any]:
|
||
"""
|
||
构造带不含行号的指纹的逐调用基础事实。
|
||
"""
|
||
fact = {
|
||
"caller": self._module_name,
|
||
"line": node.lineno,
|
||
"qualname": ".".join(self._qualnames) or "<module>",
|
||
"method": method,
|
||
"receiver_kind": receiver_kind,
|
||
"events": list(selection.events),
|
||
"dynamic": selection.dynamic,
|
||
"invalid": selection.invalid,
|
||
}
|
||
fact["fingerprint"] = fingerprint_event_fact(fact)
|
||
return fact
|
||
|
||
def _record_consumer(
|
||
self,
|
||
node: ast.AST,
|
||
selection: _EventSelection,
|
||
*,
|
||
method: str,
|
||
receiver_kind: str,
|
||
handler: str,
|
||
priority: str,
|
||
registration_kind: Literal["decorator", "listener"],
|
||
) -> None:
|
||
"""
|
||
记录包含处理函数、注册类型和优先级的完整事件消费事实。
|
||
"""
|
||
if not self._collect_facts:
|
||
return
|
||
fact = self._base_fact(
|
||
node,
|
||
selection,
|
||
method=method,
|
||
receiver_kind=receiver_kind,
|
||
)
|
||
fact.update({
|
||
"handler": handler,
|
||
"priority": priority,
|
||
"registration_kind": registration_kind,
|
||
})
|
||
fact["fingerprint"] = fingerprint_event_fact(fact)
|
||
self.consumers.append(fact)
|
||
|
||
def _record_producer(
|
||
self,
|
||
node: ast.Call,
|
||
method: _BoundEventMethod,
|
||
selection: _EventSelection,
|
||
) -> None:
|
||
"""
|
||
写入一条调用对象来源已证明的事件发布事实。
|
||
"""
|
||
if not self._collect_facts:
|
||
return
|
||
self.producers.append(
|
||
self._base_fact(
|
||
node,
|
||
selection,
|
||
method=method.method,
|
||
receiver_kind=method.receiver_kind,
|
||
)
|
||
)
|
||
|
||
def _record_decorator(self, decorator: ast.expr, handler: str) -> bool:
|
||
"""
|
||
记录直接或简单赋值别名形式的 register 装饰器。
|
||
"""
|
||
factory = self._resolve(decorator)
|
||
if not isinstance(factory, _DecoratorFactory):
|
||
return False
|
||
self._record_consumer(
|
||
decorator,
|
||
factory.selection,
|
||
method="register",
|
||
receiver_kind=factory.receiver_kind,
|
||
handler=handler,
|
||
priority=factory.priority,
|
||
registration_kind="decorator",
|
||
)
|
||
return True
|
||
|
||
def _visit_definition_decorators(
|
||
self,
|
||
decorators: list[ast.expr],
|
||
handler: str,
|
||
) -> None:
|
||
"""
|
||
按定义期作用域访问装饰器,事件注册只在实际应用时记账。
|
||
"""
|
||
for decorator in decorators:
|
||
if not self._record_decorator(decorator, handler):
|
||
self.visit(decorator)
|
||
|
||
def visit_Import(self, node: ast.Import) -> None:
|
||
"""
|
||
发布模块导入及其别名。
|
||
"""
|
||
for alias in node.names:
|
||
bound_name = alias.asname or alias.name.split(".", 1)[0]
|
||
canonical = alias.name if alias.asname else bound_name
|
||
self._set(bound_name, self._symbol_for_canonical(canonical))
|
||
|
||
def visit_ImportFrom(self, node: ast.ImportFrom) -> None:
|
||
"""
|
||
发布规范来源的 from-import 及其别名。
|
||
"""
|
||
if node.level or not node.module:
|
||
for alias in node.names:
|
||
if alias.name != "*":
|
||
self._set(alias.asname or alias.name, None)
|
||
return
|
||
for alias in node.names:
|
||
if alias.name == "*":
|
||
continue
|
||
self._set(
|
||
alias.asname or alias.name,
|
||
self._symbol_for_canonical(f"{node.module}.{alias.name}"),
|
||
)
|
||
|
||
def visit_Assign(self, node: ast.Assign) -> None:
|
||
"""
|
||
按执行顺序传播或清空简单赋值别名。
|
||
"""
|
||
value = self._resolve(node.value)
|
||
self.visit(node.value)
|
||
for target in node.targets:
|
||
for name in _bound_names(target):
|
||
self._set(name, value)
|
||
|
||
def visit_AnnAssign(self, node: ast.AnnAssign) -> None:
|
||
"""
|
||
传播带注解且有值的简单赋值别名。
|
||
"""
|
||
value = self._resolve(node.value) if node.value is not None else None
|
||
if node.value is not None:
|
||
self.visit(node.value)
|
||
for name in _bound_names(node.target):
|
||
self._set(name, value)
|
||
|
||
def visit_AugAssign(self, node: ast.AugAssign) -> None:
|
||
"""
|
||
增量赋值使目标来源信息失效。
|
||
"""
|
||
self.visit(node.value)
|
||
for name in _bound_names(node.target):
|
||
self._set(name, None)
|
||
|
||
def visit_NamedExpr(self, node: ast.NamedExpr) -> None:
|
||
"""
|
||
传播海象表达式的简单别名。
|
||
"""
|
||
value = self._resolve(node.value)
|
||
self.visit(node.value)
|
||
scope_index = len(self._scopes) - 1
|
||
while self._scope_kinds[scope_index] == "comprehension":
|
||
scope_index -= 1
|
||
for name in _bound_names(node.target):
|
||
self._scopes[scope_index][name] = value
|
||
|
||
def visit_Delete(self, node: ast.Delete) -> None:
|
||
"""
|
||
删除名称后清空其来源信息。
|
||
"""
|
||
for target in node.targets:
|
||
for name in _bound_names(target):
|
||
self._set(name, None)
|
||
|
||
def visit_If(self, node: ast.If) -> None:
|
||
"""
|
||
精确执行 TYPE_CHECKING 分支,其余条件保守合并绑定。
|
||
"""
|
||
self.visit(node.test)
|
||
runtime_value = self._runtime_type_checking_value(node.test)
|
||
if runtime_value is not None:
|
||
statements = node.body if runtime_value else node.orelse
|
||
for statement in statements:
|
||
self.visit(statement)
|
||
return
|
||
|
||
original = [dict(scope) for scope in self._scopes]
|
||
for statement in node.body:
|
||
self.visit(statement)
|
||
body_scopes = [dict(scope) for scope in self._scopes]
|
||
self._scopes = [dict(scope) for scope in original]
|
||
for statement in node.orelse:
|
||
self.visit(statement)
|
||
else_scopes = [dict(scope) for scope in self._scopes]
|
||
self._scopes = self._merge_scope_states([body_scopes, else_scopes])
|
||
|
||
def visit_For(self, node: ast.For) -> None:
|
||
"""
|
||
让循环目标遮蔽旧绑定,并保守合并零次与迭代路径。
|
||
"""
|
||
self._visit_for(node)
|
||
|
||
def visit_AsyncFor(self, node: ast.AsyncFor) -> None:
|
||
"""
|
||
按同步循环相同规则处理异步循环目标。
|
||
"""
|
||
self._visit_for(node)
|
||
|
||
def _visit_for(self, node: ast.For | ast.AsyncFor) -> None:
|
||
"""
|
||
实现同步和异步循环共享的来源信息分析。
|
||
"""
|
||
self.visit(node.iter)
|
||
original = [dict(scope) for scope in self._scopes]
|
||
for name in _bound_names(node.target):
|
||
self._set(name, None)
|
||
for statement in node.body:
|
||
self.visit(statement)
|
||
body_scopes = [dict(scope) for scope in self._scopes]
|
||
loop_exit = self._merge_scope_states([original, body_scopes])
|
||
self._scopes = [dict(scope) for scope in loop_exit]
|
||
for statement in node.orelse:
|
||
self.visit(statement)
|
||
else_scopes = [dict(scope) for scope in self._scopes]
|
||
self._scopes = self._merge_scope_states([loop_exit, else_scopes])
|
||
|
||
def visit_While(self, node: ast.While) -> None:
|
||
"""
|
||
保守合并 while 的零次、迭代和 else 路径。
|
||
"""
|
||
self.visit(node.test)
|
||
original = [dict(scope) for scope in self._scopes]
|
||
for statement in node.body:
|
||
self.visit(statement)
|
||
body_scopes = [dict(scope) for scope in self._scopes]
|
||
loop_exit = self._merge_scope_states([original, body_scopes])
|
||
self._scopes = [dict(scope) for scope in loop_exit]
|
||
for statement in node.orelse:
|
||
self.visit(statement)
|
||
else_scopes = [dict(scope) for scope in self._scopes]
|
||
self._scopes = self._merge_scope_states([loop_exit, else_scopes])
|
||
|
||
def visit_With(self, node: ast.With) -> None:
|
||
"""
|
||
按进入顺序分析上下文管理器,并清空 with 目标的旧来源信息。
|
||
"""
|
||
self._visit_with(node)
|
||
|
||
def visit_AsyncWith(self, node: ast.AsyncWith) -> None:
|
||
"""
|
||
按同步 with 相同规则处理异步上下文目标。
|
||
"""
|
||
self._visit_with(node)
|
||
|
||
def _visit_with(self, node: ast.With | ast.AsyncWith) -> None:
|
||
"""
|
||
实现同步和异步上下文管理器共享的绑定分析。
|
||
"""
|
||
for item in node.items:
|
||
self.visit(item.context_expr)
|
||
if item.optional_vars is not None:
|
||
for name in _bound_names(item.optional_vars):
|
||
self._set(name, None)
|
||
for statement in node.body:
|
||
self.visit(statement)
|
||
|
||
def visit_Match(self, node: ast.Match) -> None:
|
||
"""
|
||
隔离 match case 捕获名称并保守合并所有匹配路径。
|
||
"""
|
||
self.visit(node.subject)
|
||
original = [dict(scope) for scope in self._scopes]
|
||
states = [original]
|
||
for case in node.cases:
|
||
self._scopes = [dict(scope) for scope in original]
|
||
self.visit(case.pattern)
|
||
for name in _pattern_bound_names(case.pattern):
|
||
self._set(name, None)
|
||
if case.guard is not None:
|
||
self.visit(case.guard)
|
||
for statement in case.body:
|
||
self.visit(statement)
|
||
states.append([dict(scope) for scope in self._scopes])
|
||
self._scopes = self._merge_scope_states(states)
|
||
|
||
def visit_Try(self, node: ast.Try) -> None:
|
||
"""
|
||
隔离异常处理路径,并让异常别名在异常处理体内外失效。
|
||
"""
|
||
self._visit_try(node)
|
||
|
||
def visit_TryStar(self, node: ast.TryStar) -> None:
|
||
"""
|
||
按普通 try 相同规则处理异常组分支。
|
||
"""
|
||
self._visit_try(node)
|
||
|
||
def _visit_try(self, node: ast.Try | ast.TryStar) -> None:
|
||
"""
|
||
实现 try 与 try-star 共享的保守控制流合并。
|
||
"""
|
||
original = [dict(scope) for scope in self._scopes]
|
||
exception_states = [original]
|
||
for statement in node.body:
|
||
exception_states.append([dict(scope) for scope in self._scopes])
|
||
self.visit(statement)
|
||
exception_states.append([dict(scope) for scope in self._scopes])
|
||
exception_entry = self._merge_scope_states(exception_states)
|
||
for statement in node.orelse:
|
||
self.visit(statement)
|
||
continuing_states = [[dict(scope) for scope in self._scopes]]
|
||
for handler in node.handlers:
|
||
self._scopes = [dict(scope) for scope in exception_entry]
|
||
if handler.type is not None:
|
||
self.visit(handler.type)
|
||
if handler.name:
|
||
self._set(handler.name, None)
|
||
for statement in handler.body:
|
||
self.visit(statement)
|
||
if handler.name:
|
||
self._set(handler.name, None)
|
||
continuing_states.append([dict(scope) for scope in self._scopes])
|
||
self._scopes = self._merge_scope_states(
|
||
[*continuing_states, exception_entry]
|
||
if node.finalbody
|
||
else continuing_states
|
||
)
|
||
for statement in node.finalbody:
|
||
self.visit(statement)
|
||
|
||
def _visit_comprehension(
|
||
self,
|
||
generators: list[ast.comprehension],
|
||
expressions: tuple[ast.expr, ...],
|
||
) -> None:
|
||
"""
|
||
在独立词法作用域内分析推导式目标和表达式。
|
||
"""
|
||
first, *remaining = generators
|
||
self.visit(first.iter)
|
||
outer_original = [dict(scope) for scope in self._scopes]
|
||
self._scopes.append({})
|
||
self._scope_kinds.append("comprehension")
|
||
for name in _bound_names(first.target):
|
||
self._set(name, None)
|
||
for condition in first.ifs:
|
||
self.visit(condition)
|
||
for generator in remaining:
|
||
self.visit(generator.iter)
|
||
for name in _bound_names(generator.target):
|
||
self._set(name, None)
|
||
for condition in generator.ifs:
|
||
self.visit(condition)
|
||
for expression in expressions:
|
||
self.visit(expression)
|
||
self._scope_kinds.pop()
|
||
self._scopes.pop()
|
||
outer_after = [dict(scope) for scope in self._scopes]
|
||
self._scopes = self._merge_scope_states([outer_original, outer_after])
|
||
|
||
def visit_ListComp(self, node: ast.ListComp) -> None:
|
||
"""
|
||
在独立作用域内分析列表推导式。
|
||
"""
|
||
self._visit_comprehension(node.generators, (node.elt,))
|
||
|
||
def visit_SetComp(self, node: ast.SetComp) -> None:
|
||
"""
|
||
在独立作用域内分析集合推导式。
|
||
"""
|
||
self._visit_comprehension(node.generators, (node.elt,))
|
||
|
||
def visit_GeneratorExp(self, node: ast.GeneratorExp) -> None:
|
||
"""
|
||
在独立作用域内分析生成器表达式。
|
||
"""
|
||
self._visit_comprehension(node.generators, (node.elt,))
|
||
|
||
def visit_DictComp(self, node: ast.DictComp) -> None:
|
||
"""
|
||
在独立作用域内分析字典推导式。
|
||
"""
|
||
self._visit_comprehension(node.generators, (node.key, node.value))
|
||
|
||
def visit_FunctionDef(self, node: ast.FunctionDef) -> None:
|
||
"""
|
||
按定义期装饰器和调用期词法作用域分别分析函数。
|
||
"""
|
||
self._visit_function(node)
|
||
|
||
def visit_AsyncFunctionDef(self, node: ast.AsyncFunctionDef) -> None:
|
||
"""
|
||
按普通函数相同规则分析异步函数。
|
||
"""
|
||
self._visit_function(node)
|
||
|
||
def _visit_function(
|
||
self,
|
||
node: ast.FunctionDef | ast.AsyncFunctionDef,
|
||
) -> None:
|
||
"""
|
||
实现同步和异步函数共享的作用域分析。
|
||
"""
|
||
if not self._collect_facts:
|
||
self._set(node.name, None)
|
||
return
|
||
handler = ".".join((*self._qualnames, node.name))
|
||
self._visit_definition_decorators(node.decorator_list, handler)
|
||
for expression in (
|
||
*node.args.defaults,
|
||
*(default for default in node.args.kw_defaults if default is not None),
|
||
):
|
||
self.visit(expression)
|
||
|
||
saved_scopes = self._scopes
|
||
saved_kinds = self._scope_kinds
|
||
body_scopes = [
|
||
dict(self._module_final_scope),
|
||
*(dict(scope) for scope in self._function_final_scopes),
|
||
self._function_initial_scope(node),
|
||
]
|
||
body_kinds = [
|
||
"module",
|
||
*("function" for _ in self._function_final_scopes),
|
||
"function",
|
||
]
|
||
final_scope = self._discover_scope_after(node.body, body_scopes, body_kinds)
|
||
self._scopes = body_scopes
|
||
self._scope_kinds = body_kinds
|
||
self._function_final_scopes.append(final_scope)
|
||
self._qualnames.append(node.name)
|
||
for statement in node.body:
|
||
self.visit(statement)
|
||
self._qualnames.pop()
|
||
self._function_final_scopes.pop()
|
||
self._scopes = saved_scopes
|
||
self._scope_kinds = saved_kinds
|
||
self._set(node.name, None)
|
||
|
||
def visit_ClassDef(self, node: ast.ClassDef) -> None:
|
||
"""
|
||
在类定义期作用域记录类装饰器和方法装饰器。
|
||
"""
|
||
if not self._collect_facts:
|
||
self._set(node.name, None)
|
||
return
|
||
handler = ".".join((*self._qualnames, node.name))
|
||
self._visit_definition_decorators(node.decorator_list, handler)
|
||
for expression in (*node.bases, *(keyword.value for keyword in node.keywords)):
|
||
self.visit(expression)
|
||
self._qualnames.append(node.name)
|
||
self._class_qualnames.append(".".join(self._qualnames))
|
||
self._scopes.append({})
|
||
self._scope_kinds.append("class")
|
||
for statement in node.body:
|
||
self.visit(statement)
|
||
self._scope_kinds.pop()
|
||
self._scopes.pop()
|
||
self._class_qualnames.pop()
|
||
self._qualnames.pop()
|
||
self._set(node.name, None)
|
||
|
||
def visit_Lambda(self, node: ast.Lambda) -> None:
|
||
"""
|
||
让 lambda 参数遮蔽同名模块别名。
|
||
"""
|
||
if not self._collect_facts:
|
||
return
|
||
for expression in (
|
||
*node.args.defaults,
|
||
*(default for default in node.args.kw_defaults if default is not None),
|
||
):
|
||
self.visit(expression)
|
||
saved_scopes = self._scopes
|
||
saved_kinds = self._scope_kinds
|
||
body_scopes = [
|
||
dict(self._module_final_scope),
|
||
*(dict(scope) for scope in self._function_final_scopes),
|
||
self._function_initial_scope(node),
|
||
]
|
||
body_kinds = [
|
||
"module",
|
||
*("function" for _ in self._function_final_scopes),
|
||
"function",
|
||
]
|
||
final_scope = self._discover_scope_after(
|
||
[ast.Expr(value=node.body)],
|
||
body_scopes,
|
||
body_kinds,
|
||
)
|
||
self._scopes = body_scopes
|
||
self._scope_kinds = body_kinds
|
||
self._function_final_scopes.append(final_scope)
|
||
self.visit(node.body)
|
||
self._function_final_scopes.pop()
|
||
self._scopes = saved_scopes
|
||
self._scope_kinds = saved_kinds
|
||
|
||
def visit_Call(self, node: ast.Call) -> None:
|
||
"""
|
||
记录调用对象与调用签名均可证明的事件发布或消费。
|
||
"""
|
||
producer = self._producer_call(node)
|
||
if producer is not None:
|
||
method, selection = producer
|
||
self._record_producer(node, method, selection)
|
||
|
||
factory_application = self._decorator_factory_application(node)
|
||
if factory_application:
|
||
factory, handler_node = factory_application
|
||
self._record_consumer(
|
||
node,
|
||
factory.selection,
|
||
method="register",
|
||
receiver_kind=factory.receiver_kind,
|
||
handler=_handler_identity(handler_node),
|
||
priority=factory.priority,
|
||
registration_kind="decorator",
|
||
)
|
||
else:
|
||
registration = self._registration(node)
|
||
if registration and registration.method == "add_event_listener":
|
||
self._record_consumer(
|
||
node,
|
||
registration.selection,
|
||
method="add_event_listener",
|
||
receiver_kind=registration.receiver_kind,
|
||
handler=registration.handler,
|
||
priority=registration.priority,
|
||
registration_kind="listener",
|
||
)
|
||
self.generic_visit(node)
|
||
|
||
|
||
def collect_event_facts(
|
||
modules: dict[str, Path],
|
||
event_members: dict[str, tuple[str, ...]],
|
||
) -> dict[str, list[dict[str, Any]]]:
|
||
"""
|
||
收集宿主 EventManager 的逐调用事件发布与消费事实。
|
||
|
||
只有可追溯到规范 EventManager、其内部 ``self`` 或明确注入事件端口的
|
||
调用才进入事实。插件模块与未知同名调用对象始终忽略;每条事实携带排除行号
|
||
的字段敏感 SHA256,供基线稳定追踪。
|
||
|
||
:param modules: 宿主模块名到 Python 源码路径的映射
|
||
:param event_members: EventType/ChainEventType 到公开成员名的映射
|
||
:return: ``producers`` 与 ``consumers`` 两组稳定排序的逐调用事实
|
||
"""
|
||
trees = {
|
||
module_name: ast.parse(
|
||
path.read_text(encoding="utf-8-sig"),
|
||
filename=str(path),
|
||
)
|
||
for module_name, path in sorted(modules.items())
|
||
if module_name != "app.plugins"
|
||
and not module_name.startswith("app.plugins.")
|
||
}
|
||
package_modules = {
|
||
module_name
|
||
for module_name, path in modules.items()
|
||
if path.name == "__init__.py"
|
||
}
|
||
injected_fields = _discover_injected_event_fields(
|
||
trees,
|
||
package_modules,
|
||
)
|
||
producers: list[dict[str, Any]] = []
|
||
consumers: list[dict[str, Any]] = []
|
||
for module_name, tree in sorted(trees.items()):
|
||
discovery = _EventFactCollector(
|
||
module_name,
|
||
event_members,
|
||
collect_facts=False,
|
||
injected_fields=injected_fields,
|
||
)
|
||
discovery.visit(tree)
|
||
collector = _EventFactCollector(
|
||
module_name,
|
||
event_members,
|
||
collect_facts=True,
|
||
module_final_scope=discovery.module_scope(),
|
||
injected_fields=injected_fields,
|
||
)
|
||
collector.visit(tree)
|
||
producers.extend(collector.producers)
|
||
consumers.extend(collector.consumers)
|
||
|
||
return {
|
||
"producers": sorted(producers, key=_fact_sort_key),
|
||
"consumers": sorted(consumers, key=_fact_sort_key),
|
||
}
|