diff --git a/.github/reviewer-bot-tests/conftest.py b/.github/reviewer-bot-tests/conftest.py deleted file mode 100644 index fdd82486b..000000000 --- a/.github/reviewer-bot-tests/conftest.py +++ /dev/null @@ -1,115 +0,0 @@ -import pytest - -from scripts import reviewer_bot - - -@pytest.fixture(autouse=True) -def clear_env(): - env_vars = { - "COMMENT_BODY", - "COMMENT_AUTHOR", - "COMMENT_ID", - "COMMENT_SOURCE_EVENT_KEY", - "ALLOW_EMPTY_ACTIVE_REVIEWS_WRITE", - "EVENT_ACTION", - "EVENT_NAME", - "ISSUE_NUMBER", - "ISSUE_AUTHOR", - "IS_PULL_REQUEST", - "ISSUE_LABELS", - "LABEL_NAME", - "MANUAL_ACTION", - "PR_IS_CROSS_REPOSITORY", - "REVIEW_AUTHOR", - "REVIEW_STATE", - "REPO_OWNER", - "REPO_NAME", - "WORKFLOW_RUN_EVENT", - "WORKFLOW_RUN_EVENT_ACTION", - "WORKFLOW_RUN_HEAD_SHA", - "WORKFLOW_RUN_RECONCILE_PR_NUMBER", - "WORKFLOW_RUN_RECONCILE_HEAD_SHA", - "WORKFLOW_RUN_ID", - "WORKFLOW_NAME", - "WORKFLOW_JOB_NAME", - } - with pytest.MonkeyPatch().context() as monkeypatch: - for name in env_vars: - monkeypatch.delenv(name, raising=False) - monkeypatch.setattr( - reviewer_bot, - "ACTIVE_LEASE_CONTEXT", - reviewer_bot.LeaseContext( - lock_token="test-lock-token", - lock_owner_run_id="test-run", - lock_owner_workflow="test-workflow", - lock_owner_job="test-job", - state_issue_url="https://example.com/state", - ), - ) - monkeypatch.setattr(reviewer_bot, "TOUCHED_ISSUE_NUMBERS", set()) - yield - - -@pytest.fixture -def stub_api(monkeypatch): - monkeypatch.setattr(reviewer_bot, "github_api", lambda *args, **kwargs: {}) - monkeypatch.setattr( - reviewer_bot, - "github_api_request", - lambda *args, **kwargs: reviewer_bot.GitHubApiResult( - status_code=200, - payload={}, - headers={}, - text="", - ok=True, - ), - ) - monkeypatch.setattr(reviewer_bot, "add_reaction", lambda *args, **kwargs: True) - monkeypatch.setattr(reviewer_bot, "post_comment", lambda *args, **kwargs: True) - monkeypatch.setattr(reviewer_bot, "assign_reviewer", lambda *args, **kwargs: True) - monkeypatch.setattr( - reviewer_bot, - "request_reviewer_assignment", - lambda *args, **kwargs: reviewer_bot.AssignmentAttempt(success=True, status_code=201), - ) - monkeypatch.setattr(reviewer_bot, "unassign_reviewer", lambda *args, **kwargs: True) - monkeypatch.setattr(reviewer_bot, "remove_pr_reviewer", lambda *args, **kwargs: True) - monkeypatch.setattr(reviewer_bot, "remove_assignee", lambda *args, **kwargs: True) - monkeypatch.setattr(reviewer_bot, "get_repo_labels", lambda *args, **kwargs: {"a", "b"}) - monkeypatch.setattr(reviewer_bot, "add_label", lambda *args, **kwargs: True) - monkeypatch.setattr(reviewer_bot, "add_label_with_status", lambda *args, **kwargs: True) - monkeypatch.setattr(reviewer_bot, "remove_label", lambda *args, **kwargs: True) - monkeypatch.setattr(reviewer_bot, "remove_label_with_status", lambda *args, **kwargs: True) - monkeypatch.setattr(reviewer_bot, "ensure_label_exists", lambda *args, **kwargs: True) - monkeypatch.setattr(reviewer_bot, "fetch_members", lambda *args, **kwargs: []) - - -@pytest.fixture -def captured_comments(monkeypatch): - comments = [] - - def record_comment(issue_number, body): - comments.append({"issue_number": issue_number, "body": body}) - return True - - monkeypatch.setattr(reviewer_bot, "post_comment", record_comment) - return comments - - -@pytest.fixture -def captured_status_label_ops(monkeypatch): - operations = [] - - def record_add(issue_number, label): - operations.append(("add", issue_number, label)) - return True - - def record_remove(issue_number, label): - operations.append(("remove", issue_number, label)) - return True - - monkeypatch.setattr(reviewer_bot, "add_label_with_status", record_add) - monkeypatch.setattr(reviewer_bot, "remove_label_with_status", record_remove) - monkeypatch.setattr(reviewer_bot, "ensure_label_exists", lambda *args, **kwargs: True) - return operations diff --git a/.github/reviewer-bot-tests/test_main.py b/.github/reviewer-bot-tests/test_main.py deleted file mode 100644 index 65eb43232..000000000 --- a/.github/reviewer-bot-tests/test_main.py +++ /dev/null @@ -1,1014 +0,0 @@ -import pytest - -from scripts import reviewer_bot -from scripts.reviewer_bot_lib.context import ( - GitHubTransportContext, - LeaseLockContext, - ReviewerBotContext, - StateStoreContext, -) - - -def make_state(): - return { - "schema_version": reviewer_bot.STATE_SCHEMA_VERSION, - "freshness_runtime_epoch": reviewer_bot.FRESHNESS_RUNTIME_EPOCH_V18, - "status_projection_epoch": reviewer_bot.STATUS_PROJECTION_EPOCH, - "last_updated": None, - "current_index": 0, - "queue": [ - {"github": "alice", "name": "Alice"}, - {"github": "bob", "name": "Bob"}, - {"github": "carol", "name": "Carol"}, - ], - "pass_until": [], - "recent_assignments": [], - "active_reviews": {}, - } - - -def test_reviewer_bot_exports_runtime_modules(): - assert reviewer_bot.requests is not None - assert reviewer_bot.sys is not None - assert reviewer_bot.datetime is not None - assert reviewer_bot.timezone is not None - - -def test_reviewer_bot_satisfies_runtime_context_protocol(): - assert isinstance(reviewer_bot, ReviewerBotContext) - - -def test_reviewer_bot_satisfies_narrower_lock_and_state_protocols(): - assert isinstance(reviewer_bot, GitHubTransportContext) - assert isinstance(reviewer_bot, StateStoreContext) - assert isinstance(reviewer_bot, LeaseLockContext) - - -def test_render_lock_commit_message_uses_direct_json_import(): - rendered = reviewer_bot.render_lock_commit_message({"lock_state": "unlocked"}) - assert reviewer_bot.LOCK_COMMIT_MARKER in rendered - - -def test_main_show_state_uses_direct_yaml_import(monkeypatch, capsys): - monkeypatch.setenv("EVENT_NAME", "workflow_dispatch") - monkeypatch.setenv("EVENT_ACTION", "") - monkeypatch.setenv("MANUAL_ACTION", "show-state") - monkeypatch.setattr(reviewer_bot, "load_state", lambda *args, **kwargs: make_state()) - - reviewer_bot.main() - - output = capsys.readouterr().out - assert "Current state:" in output - assert "freshness_runtime_epoch" in output - - -def _valid_reviewer_board_metadata(): - return { - "data": { - "organization": { - "projectV2": { - "id": "PVT_kwDOB", - "title": "Reviewer Board", - "fields": { - "nodes": [ - { - "__typename": "ProjectV2SingleSelectField", - "id": "field-review-state", - "name": "Review State", - "options": [ - {"id": "opt-ar", "name": "Awaiting Reviewer"}, - {"id": "opt-ac", "name": "Awaiting Contributor"}, - {"id": "opt-aw", "name": "Awaiting Write Approval"}, - {"id": "opt-done", "name": "Done"}, - {"id": "opt-unassigned", "name": "Unassigned"}, - ], - }, - { - "__typename": "ProjectV2Field", - "dataType": "TEXT", - "id": "field-reviewer", - "name": "Reviewer", - }, - { - "__typename": "ProjectV2Field", - "dataType": "DATE", - "id": "field-assigned-at", - "name": "Assigned At", - }, - { - "__typename": "ProjectV2Field", - "dataType": "DATE", - "id": "field-waiting-since", - "name": "Waiting Since", - }, - { - "__typename": "ProjectV2SingleSelectField", - "id": "field-needs-attention", - "name": "Needs Attention", - "options": [ - {"id": "opt-no", "name": "No"}, - {"id": "opt-warning", "name": "Warning Sent"}, - {"id": "opt-notice", "name": "Transition Notice Sent"}, - {"id": "opt-triage", "name": "Triage Approval Required"}, - {"id": "opt-repair", "name": "Projection Repair Required"}, - ], - }, - ] - }, - } - } - } - } - - -def test_classify_event_intent_cross_repo_review_is_non_mutating_defer(monkeypatch): - monkeypatch.setenv("PR_IS_CROSS_REPOSITORY", "true") - intent = reviewer_bot.classify_event_intent("pull_request_review", "submitted") - assert intent == reviewer_bot.EVENT_INTENT_NON_MUTATING_DEFER - - -def test_classify_event_intent_preview_reviewer_board_is_non_mutating(monkeypatch): - monkeypatch.setenv("MANUAL_ACTION", "preview-reviewer-board") - intent = reviewer_bot.classify_event_intent("workflow_dispatch", "") - assert intent == reviewer_bot.EVENT_INTENT_NON_MUTATING_READONLY - - -def test_classify_event_intent_same_repo_review_is_non_mutating_defer(monkeypatch): - intent = reviewer_bot.classify_event_intent("pull_request_review", "submitted") - assert intent == reviewer_bot.EVENT_INTENT_NON_MUTATING_DEFER - - -def test_classify_event_intent_same_repo_dismissed_review_is_non_mutating_defer(monkeypatch): - intent = reviewer_bot.classify_event_intent("pull_request_review", "dismissed") - assert intent == reviewer_bot.EVENT_INTENT_NON_MUTATING_DEFER - - -def test_classify_event_intent_review_comment_is_non_mutating_defer(monkeypatch): - intent = reviewer_bot.classify_event_intent("pull_request_review_comment", "created") - assert intent == reviewer_bot.EVENT_INTENT_NON_MUTATING_DEFER - - -def test_classify_event_intent_workflow_run_dismissed_review_is_mutating(monkeypatch): - monkeypatch.setenv("WORKFLOW_RUN_EVENT", "pull_request_review") - monkeypatch.setenv("WORKFLOW_RUN_EVENT_ACTION", "dismissed") - intent = reviewer_bot.classify_event_intent("workflow_run", "completed") - assert intent == reviewer_bot.EVENT_INTENT_MUTATING - - -def test_main_cross_repo_review_does_not_acquire_lock(monkeypatch): - monkeypatch.setenv("EVENT_NAME", "pull_request_review") - monkeypatch.setenv("EVENT_ACTION", "submitted") - monkeypatch.setenv("PR_IS_CROSS_REPOSITORY", "true") - - acquire_called = {"value": False} - - def fail_if_called(): - acquire_called["value"] = True - raise AssertionError("acquire_state_issue_lease_lock should not be called") - - monkeypatch.setattr(reviewer_bot, "acquire_state_issue_lease_lock", fail_if_called) - monkeypatch.setattr(reviewer_bot, "load_state", lambda *args, **kwargs: make_state()) - monkeypatch.setattr(reviewer_bot, "handle_pull_request_review_event", lambda state: False) - - reviewer_bot.main() - - assert acquire_called["value"] is False - - -def test_main_same_repo_review_does_not_acquire_lock(monkeypatch): - monkeypatch.setenv("EVENT_NAME", "pull_request_review") - monkeypatch.setenv("EVENT_ACTION", "submitted") - - acquire_called = {"value": False} - - def fail_if_called(): - acquire_called["value"] = True - raise AssertionError("acquire_state_issue_lease_lock should not be called") - - monkeypatch.setattr(reviewer_bot, "acquire_state_issue_lease_lock", fail_if_called) - monkeypatch.setattr(reviewer_bot, "load_state", lambda *args, **kwargs: make_state()) - monkeypatch.setattr(reviewer_bot, "process_pass_until_expirations", lambda state: (state, [])) - monkeypatch.setattr(reviewer_bot, "sync_members_with_queue", lambda state: (state, [])) - monkeypatch.setattr(reviewer_bot, "handle_pull_request_review_event", lambda state: False) - - reviewer_bot.main() - - assert acquire_called["value"] is False - - -def test_main_workflow_run_reconcile_acquires_lock(monkeypatch): - monkeypatch.setenv("EVENT_NAME", "workflow_run") - monkeypatch.setenv("EVENT_ACTION", "completed") - monkeypatch.setenv("WORKFLOW_RUN_EVENT", "pull_request_review") - - acquire_called = {"value": False} - - def fake_acquire(): - acquire_called["value"] = True - return reviewer_bot.LeaseContext( - lock_token="token", - lock_owner_run_id="run", - lock_owner_workflow="workflow", - lock_owner_job="job", - state_issue_url="https://example.com/issues/314", - lock_ref="refs/heads/reviewer-bot-state-lock", - lock_expires_at="2999-01-01T00:00:00+00:00", - ) - - monkeypatch.setattr(reviewer_bot, "acquire_state_issue_lease_lock", fake_acquire) - monkeypatch.setattr(reviewer_bot, "release_state_issue_lease_lock", lambda: True) - monkeypatch.setattr(reviewer_bot, "load_state", lambda *args, **kwargs: make_state()) - monkeypatch.setattr(reviewer_bot, "process_pass_until_expirations", lambda state: (state, [])) - monkeypatch.setattr(reviewer_bot, "sync_members_with_queue", lambda state: (state, [])) - monkeypatch.setattr(reviewer_bot, "handle_workflow_run_event", lambda state: False) - - reviewer_bot.main() - - assert acquire_called["value"] is True - - -def test_main_workflow_run_review_comment_reconcile_acquires_lock(monkeypatch): - monkeypatch.setenv("EVENT_NAME", "workflow_run") - monkeypatch.setenv("EVENT_ACTION", "completed") - monkeypatch.setenv("WORKFLOW_RUN_EVENT", "pull_request_review_comment") - - acquire_called = {"value": False} - - def fake_acquire(): - acquire_called["value"] = True - return reviewer_bot.LeaseContext( - lock_token="token", - lock_owner_run_id="run", - lock_owner_workflow="workflow", - lock_owner_job="job", - state_issue_url="https://example.com/issues/314", - lock_ref="refs/heads/reviewer-bot-state-lock", - lock_expires_at="2999-01-01T00:00:00+00:00", - ) - - monkeypatch.setattr(reviewer_bot, "acquire_state_issue_lease_lock", fake_acquire) - monkeypatch.setattr(reviewer_bot, "release_state_issue_lease_lock", lambda: True) - monkeypatch.setattr(reviewer_bot, "load_state", lambda *args, **kwargs: make_state()) - monkeypatch.setattr(reviewer_bot, "process_pass_until_expirations", lambda state: (state, [])) - monkeypatch.setattr(reviewer_bot, "sync_members_with_queue", lambda state: (state, [])) - monkeypatch.setattr(reviewer_bot, "handle_workflow_run_event", lambda state: False) - - reviewer_bot.main() - - assert acquire_called["value"] is True - - -def test_main_reloads_state_before_syncing_status_labels(monkeypatch): - monkeypatch.setenv("EVENT_NAME", "issue_comment") - monkeypatch.setenv("EVENT_ACTION", "created") - - initial_state = make_state() - reloaded_state = make_state() - load_calls = {"count": 0} - call_order = [] - - def fake_load_state(*, fail_on_unavailable=False): - load_calls["count"] += 1 - call_order.append(f"load:{load_calls['count']}") - if load_calls["count"] == 1: - return initial_state - return reloaded_state - - def fake_handle_comment_event(state): - assert state is initial_state - reviewer_bot.collect_touched_item(42) - call_order.append("handle") - return True - - def fake_save_state(state): - assert state is initial_state - call_order.append("save") - return True - - def fake_sync_status_labels_for_items(state, issue_numbers): - call_order.append("sync") - assert state is reloaded_state - assert list(issue_numbers) == [42] - return True - - monkeypatch.setattr(reviewer_bot, "acquire_state_issue_lease_lock", lambda: None) - monkeypatch.setattr(reviewer_bot, "release_state_issue_lease_lock", lambda: True) - monkeypatch.setattr(reviewer_bot, "load_state", fake_load_state) - monkeypatch.setattr(reviewer_bot, "process_pass_until_expirations", lambda state: (state, [])) - monkeypatch.setattr(reviewer_bot, "sync_members_with_queue", lambda state: (state, [])) - monkeypatch.setattr(reviewer_bot, "handle_comment_event", fake_handle_comment_event) - monkeypatch.setattr(reviewer_bot, "save_state", fake_save_state) - monkeypatch.setattr(reviewer_bot, "sync_status_labels_for_items", fake_sync_status_labels_for_items) - - reviewer_bot.main() - - assert call_order == [ - "load:1", - "handle", - "load:2", - "save", - "load:3", - "load:4", - "sync", - ] - - -def test_main_preview_reviewer_board_disabled_is_clean_noop(monkeypatch, capsys): - monkeypatch.setenv("EVENT_NAME", "workflow_dispatch") - monkeypatch.setenv("EVENT_ACTION", "") - monkeypatch.setenv("MANUAL_ACTION", "preview-reviewer-board") - monkeypatch.setenv("REVIEWER_BOARD_ENABLED", "false") - - monkeypatch.setattr(reviewer_bot, "load_state", lambda *args, **kwargs: make_state()) - monkeypatch.setattr( - reviewer_bot, - "acquire_state_issue_lease_lock", - lambda: (_ for _ in ()).throw(AssertionError("preview should not acquire lock")), - ) - monkeypatch.setattr( - reviewer_bot, - "process_pass_until_expirations", - lambda state: (_ for _ in ()).throw(AssertionError("preview should skip pass-until processing")), - ) - monkeypatch.setattr( - reviewer_bot, - "sync_members_with_queue", - lambda state: (_ for _ in ()).throw(AssertionError("preview should skip member sync")), - ) - monkeypatch.setattr( - reviewer_bot, - "save_state", - lambda state: (_ for _ in ()).throw(AssertionError("preview should not save state")), - ) - monkeypatch.setattr( - reviewer_bot, - "sync_status_labels_for_items", - lambda state, issue_numbers: (_ for _ in ()).throw(AssertionError("preview should not sync labels")), - ) - - reviewer_bot.main() - - output = capsys.readouterr().out - assert "Reviewer board preview skipped: reviewer board is disabled." in output - - -def test_main_preview_reviewer_board_missing_token_fails_clearly(monkeypatch, capsys): - monkeypatch.setenv("EVENT_NAME", "workflow_dispatch") - monkeypatch.setenv("EVENT_ACTION", "") - monkeypatch.setenv("MANUAL_ACTION", "preview-reviewer-board") - monkeypatch.setenv("REVIEWER_BOARD_ENABLED", "true") - monkeypatch.setattr(reviewer_bot, "_reviewer_board_project_metadata", None, raising=False) - - monkeypatch.setattr(reviewer_bot, "load_state", lambda *args, **kwargs: make_state()) - monkeypatch.setattr( - reviewer_bot, - "acquire_state_issue_lease_lock", - lambda: (_ for _ in ()).throw(AssertionError("preview should not acquire lock")), - ) - monkeypatch.setattr( - reviewer_bot, - "process_pass_until_expirations", - lambda state: (_ for _ in ()).throw(AssertionError("preview should skip pass-until processing")), - ) - monkeypatch.setattr( - reviewer_bot, - "sync_members_with_queue", - lambda state: (_ for _ in ()).throw(AssertionError("preview should skip member sync")), - ) - - with pytest.raises(SystemExit) as excinfo: - reviewer_bot.main() - - assert excinfo.value.code == 1 - assert "REVIEWER_BOARD_TOKEN not set" in capsys.readouterr().err - - -def test_main_preview_reviewer_board_invalid_manifest_fails_clearly(monkeypatch, capsys): - monkeypatch.setenv("EVENT_NAME", "workflow_dispatch") - monkeypatch.setenv("EVENT_ACTION", "") - monkeypatch.setenv("MANUAL_ACTION", "preview-reviewer-board") - monkeypatch.setenv("REVIEWER_BOARD_ENABLED", "true") - monkeypatch.setenv("REVIEWER_BOARD_TOKEN", "board-token") - monkeypatch.setattr(reviewer_bot, "_reviewer_board_project_metadata", None, raising=False) - - monkeypatch.setattr(reviewer_bot, "load_state", lambda *args, **kwargs: make_state()) - monkeypatch.setattr( - reviewer_bot, - "acquire_state_issue_lease_lock", - lambda: (_ for _ in ()).throw(AssertionError("preview should not acquire lock")), - ) - monkeypatch.setattr( - reviewer_bot, - "github_graphql", - lambda query, variables=None, *, token=None: { - "data": { - "organization": { - "projectV2": { - "id": "PVT_kwDOB", - "title": "Reviewer Board", - "fields": {"nodes": []}, - } - } - } - }, - ) - - with pytest.raises(SystemExit) as excinfo: - reviewer_bot.main() - - assert excinfo.value.code == 1 - assert "Missing reviewer board field: Review State" in capsys.readouterr().err - - -def test_main_preview_reviewer_board_is_read_only(monkeypatch, capsys): - monkeypatch.setenv("EVENT_NAME", "workflow_dispatch") - monkeypatch.setenv("EVENT_ACTION", "") - monkeypatch.setenv("MANUAL_ACTION", "preview-reviewer-board") - monkeypatch.setenv("REVIEWER_BOARD_ENABLED", "true") - monkeypatch.setenv("REVIEWER_BOARD_TOKEN", "board-token") - monkeypatch.setenv("ISSUE_NUMBER", "42") - monkeypatch.setattr(reviewer_bot, "_reviewer_board_project_metadata", None, raising=False) - - state = make_state() - state["status_projection_epoch"] = "status_projection_v1" - review = reviewer_bot.ensure_review_entry(state, 42, create=True) - assert review is not None - review["current_reviewer"] = "alice" - review["assigned_at"] = "2026-03-20T12:34:56Z" - review["active_cycle_started_at"] = "2026-03-20T12:34:56Z" - - monkeypatch.setattr(reviewer_bot, "load_state", lambda *args, **kwargs: state) - monkeypatch.setattr( - reviewer_bot, - "acquire_state_issue_lease_lock", - lambda: (_ for _ in ()).throw(AssertionError("preview should not acquire lock")), - ) - monkeypatch.setattr( - reviewer_bot, - "process_pass_until_expirations", - lambda current: (_ for _ in ()).throw(AssertionError("preview should skip pass-until processing")), - ) - monkeypatch.setattr( - reviewer_bot, - "sync_members_with_queue", - lambda current: (_ for _ in ()).throw(AssertionError("preview should skip member sync")), - ) - monkeypatch.setattr( - reviewer_bot, - "save_state", - lambda current: (_ for _ in ()).throw(AssertionError("preview should not save state")), - ) - monkeypatch.setattr( - reviewer_bot, - "sync_status_labels_for_items", - lambda current, issue_numbers: (_ for _ in ()).throw(AssertionError("preview should not sync labels")), - ) - monkeypatch.setattr(reviewer_bot, "github_graphql", lambda query, variables=None, *, token=None: _valid_reviewer_board_metadata()) - monkeypatch.setattr( - reviewer_bot, - "get_issue_or_pr_snapshot", - lambda issue_number: {"number": issue_number, "state": "open", "pull_request": None, "labels": []}, - ) - - reviewer_bot.main() - - output = capsys.readouterr().out - assert "classification: open_tracked_assigned" in output - assert "ensure_membership: true" in output - - -def test_issue_close_then_close_comment_does_not_leave_active_review(monkeypatch): - state = make_state() - review = reviewer_bot.ensure_review_entry(state, 42, create=True) - assert review is not None - review["current_reviewer"] = "alice" - - monkeypatch.setenv("ISSUE_NUMBER", "42") - monkeypatch.setenv("IS_PULL_REQUEST", "false") - monkeypatch.setenv("ISSUE_TITLE", "Validation issue") - monkeypatch.setenv("ISSUE_BODY", "body") - monkeypatch.setenv("ISSUE_AUTHOR", "dana") - monkeypatch.setenv("ISSUE_STATE", "closed") - monkeypatch.setenv("COMMENT_USER_TYPE", "User") - monkeypatch.setenv("COMMENT_AUTHOR", "dana") - monkeypatch.setenv("COMMENT_ID", "100") - monkeypatch.setenv("COMMENT_CREATED_AT", "2026-03-17T10:00:00Z") - monkeypatch.setenv("COMMENT_BODY", "reviewer-bot validation close-path comment") - - assert reviewer_bot.handle_closed_event(state) is True - assert "42" not in state["active_reviews"] - assert reviewer_bot.handle_comment_event(state) is False - assert "42" not in state["active_reviews"] - - -def test_main_fails_when_save_state_fails(monkeypatch): - monkeypatch.setenv("EVENT_NAME", "issue_comment") - monkeypatch.setenv("EVENT_ACTION", "created") - monkeypatch.setattr(reviewer_bot, "acquire_state_issue_lease_lock", lambda: None) - monkeypatch.setattr(reviewer_bot, "release_state_issue_lease_lock", lambda: True) - monkeypatch.setattr(reviewer_bot, "load_state", lambda *args, **kwargs: make_state()) - monkeypatch.setattr(reviewer_bot, "process_pass_until_expirations", lambda state: (state, [])) - monkeypatch.setattr(reviewer_bot, "sync_members_with_queue", lambda state: (state, [])) - monkeypatch.setattr(reviewer_bot, "handle_comment_event", lambda state: True) - monkeypatch.setattr(reviewer_bot, "save_state", lambda state: False) - - with pytest.raises(SystemExit) as excinfo: - reviewer_bot.main() - - assert excinfo.value.code == 1 - - -def test_main_workflow_run_fails_closed_on_invalid_context(monkeypatch): - monkeypatch.setenv("EVENT_NAME", "workflow_run") - monkeypatch.setenv("EVENT_ACTION", "completed") - monkeypatch.setenv("WORKFLOW_RUN_EVENT", "pull_request_review") - monkeypatch.setenv("WORKFLOW_RUN_EVENT_ACTION", "submitted") - monkeypatch.setattr(reviewer_bot, "acquire_state_issue_lease_lock", lambda: None) - monkeypatch.setattr(reviewer_bot, "release_state_issue_lease_lock", lambda: True) - monkeypatch.setattr(reviewer_bot, "load_state", lambda *args, **kwargs: make_state()) - monkeypatch.setattr(reviewer_bot, "process_pass_until_expirations", lambda state: (state, [])) - monkeypatch.setattr(reviewer_bot, "sync_members_with_queue", lambda state: (state, [])) - monkeypatch.setattr( - reviewer_bot, - "handle_workflow_run_event", - lambda state: (_ for _ in ()).throw(RuntimeError("invalid deferred context")), - ) - - with pytest.raises(SystemExit) as excinfo: - reviewer_bot.main() - - assert excinfo.value.code == 1 - - -def test_main_schedule_backfills_existing_transition_notice_without_duplicate_comment(monkeypatch): - monkeypatch.setenv("EVENT_NAME", "schedule") - monkeypatch.setenv("EVENT_ACTION", "") - state = make_state() - review = reviewer_bot.ensure_review_entry(state, 42, create=True) - assert review is not None - review["current_reviewer"] = "alice" - review["assigned_at"] = "2026-03-01T00:00:00Z" - review["last_reviewer_activity"] = "2026-03-01T00:00:00Z" - review["transition_warning_sent"] = "2026-03-10T00:00:00Z" - - monkeypatch.setattr(reviewer_bot, "acquire_state_issue_lease_lock", lambda: None) - monkeypatch.setattr(reviewer_bot, "release_state_issue_lease_lock", lambda: True) - monkeypatch.setattr(reviewer_bot, "load_state", lambda *args, **kwargs: state) - monkeypatch.setattr(reviewer_bot, "process_pass_until_expirations", lambda current: (current, [])) - monkeypatch.setattr(reviewer_bot, "sync_members_with_queue", lambda current: (current, [])) - monkeypatch.setattr(reviewer_bot.maintenance_module, "sweep_deferred_gaps", lambda bot, current: False) - monkeypatch.setattr(reviewer_bot.maintenance_module, "maybe_record_head_observation_repair", lambda bot, issue_number, review_data: False) - monkeypatch.setattr(reviewer_bot, "get_pull_request_reviews", lambda issue_number: []) - monkeypatch.setattr(reviewer_bot, "get_issue_or_pr_snapshot", lambda issue_number: {"number": issue_number, "state": "open", "pull_request": {}, "labels": []}) - monkeypatch.setattr(reviewer_bot, "save_state", lambda current: True) - monkeypatch.setattr(reviewer_bot, "sync_status_labels_for_items", lambda current, issue_numbers: True) - posted = [] - monkeypatch.setattr(reviewer_bot, "post_comment", lambda issue_number, body: posted.append(body) or True) - - def fake_api(method, endpoint, data=None): - if endpoint == "issues/42/comments?per_page=100": - return [{"id": 99, "created_at": "2026-03-25T15:22:42Z", "body": "🔔 **Transition Period Ended**\n\nExisting notice", "user": {"login": "github-actions[bot]"}}] - raise AssertionError(endpoint) - - monkeypatch.setattr(reviewer_bot, "github_api", fake_api) - reviewer_bot.main() - assert review["transition_notice_sent_at"] == "2026-03-25T15:22:42Z" - assert posted == [] - - -def test_main_schedule_reviewer_review_repair_marks_item_for_label_sync(monkeypatch): - monkeypatch.setenv("EVENT_NAME", "schedule") - monkeypatch.setenv("EVENT_ACTION", "") - state = make_state() - review = reviewer_bot.ensure_review_entry(state, 42, create=True) - assert review is not None - review["current_reviewer"] = "alice" - review["active_cycle_started_at"] = "2026-03-17T09:00:00Z" - - synced_issue_numbers = [] - - monkeypatch.setattr(reviewer_bot, "acquire_state_issue_lease_lock", lambda: None) - monkeypatch.setattr(reviewer_bot, "release_state_issue_lease_lock", lambda: True) - monkeypatch.setattr(reviewer_bot, "load_state", lambda *args, **kwargs: state) - monkeypatch.setattr(reviewer_bot, "process_pass_until_expirations", lambda current: (current, [])) - monkeypatch.setattr(reviewer_bot, "sync_members_with_queue", lambda current: (current, [])) - monkeypatch.setattr(reviewer_bot.maintenance_module, "sweep_deferred_gaps", lambda bot, current: False) - monkeypatch.setattr(reviewer_bot.maintenance_module, "maybe_record_head_observation_repair", lambda bot, issue_number, review_data: False) - monkeypatch.setattr(reviewer_bot.maintenance_module, "check_overdue_reviews", lambda bot, current: []) - monkeypatch.setattr( - reviewer_bot, - "get_issue_or_pr_snapshot", - lambda issue_number: {"number": issue_number, "state": "open", "pull_request": {}, "labels": []}, - ) - monkeypatch.setattr( - reviewer_bot, - "get_pull_request_reviews", - lambda issue_number: [ - { - "id": 10, - "state": "COMMENTED", - "submitted_at": "2026-03-17T10:01:00Z", - "commit_id": "head-1", - "user": {"login": "alice"}, - } - ], - ) - monkeypatch.setattr(reviewer_bot, "save_state", lambda current: True) - monkeypatch.setattr( - reviewer_bot, - "sync_status_labels_for_items", - lambda current, issue_numbers: synced_issue_numbers.extend(issue_numbers) or True, - ) - - reviewer_bot.main() - - assert review["reviewer_review"]["accepted"]["semantic_key"] == "pull_request_review:10" - assert synced_issue_numbers == [42] - - -def test_main_schedule_status_projection_epoch_mismatch_triggers_label_repair_sweep(monkeypatch): - monkeypatch.setenv("EVENT_NAME", "schedule") - monkeypatch.setenv("EVENT_ACTION", "") - state = make_state() - state["status_projection_epoch"] = "status_projection_v1" - review = reviewer_bot.ensure_review_entry(state, 42, create=True) - assert review is not None - review["current_reviewer"] = "alice" - synced_issue_numbers = [] - saved_epochs = [] - - monkeypatch.setattr(reviewer_bot, "acquire_state_issue_lease_lock", lambda: None) - monkeypatch.setattr(reviewer_bot, "release_state_issue_lease_lock", lambda: True) - monkeypatch.setattr(reviewer_bot, "load_state", lambda *args, **kwargs: state) - monkeypatch.setattr(reviewer_bot, "process_pass_until_expirations", lambda current: (current, [])) - monkeypatch.setattr(reviewer_bot, "sync_members_with_queue", lambda current: (current, [])) - monkeypatch.setattr(reviewer_bot, "handle_scheduled_check", lambda current: False) - monkeypatch.setattr(reviewer_bot, "list_open_items_with_status_labels", lambda: [99]) - monkeypatch.setattr( - reviewer_bot, - "sync_status_labels_for_items", - lambda current, issue_numbers: synced_issue_numbers.extend(issue_numbers) or True, - ) - monkeypatch.setattr( - reviewer_bot, - "save_state", - lambda current: saved_epochs.append(current.get("status_projection_epoch")) or True, - ) - - reviewer_bot.main() - - assert synced_issue_numbers == [42, 99] - assert saved_epochs[-1] == reviewer_bot.STATUS_PROJECTION_EPOCH - - -def test_main_schedule_status_projection_epoch_not_advanced_on_label_sync_failure(monkeypatch): - monkeypatch.setenv("EVENT_NAME", "schedule") - monkeypatch.setenv("EVENT_ACTION", "") - state = make_state() - state["status_projection_epoch"] = "status_projection_v1" - review = reviewer_bot.ensure_review_entry(state, 42, create=True) - assert review is not None - review["current_reviewer"] = "alice" - saved_epochs = [] - - monkeypatch.setattr(reviewer_bot, "acquire_state_issue_lease_lock", lambda: None) - monkeypatch.setattr(reviewer_bot, "release_state_issue_lease_lock", lambda: True) - monkeypatch.setattr(reviewer_bot, "load_state", lambda *args, **kwargs: state) - monkeypatch.setattr(reviewer_bot, "process_pass_until_expirations", lambda current: (current, [])) - monkeypatch.setattr(reviewer_bot, "sync_members_with_queue", lambda current: (current, [])) - monkeypatch.setattr(reviewer_bot, "handle_scheduled_check", lambda current: False) - monkeypatch.setattr(reviewer_bot, "list_open_items_with_status_labels", lambda: [42]) - monkeypatch.setattr( - reviewer_bot, - "sync_status_labels_for_items", - lambda current, issue_numbers: (_ for _ in ()).throw(RuntimeError("projection exploded")), - ) - monkeypatch.setattr( - reviewer_bot, - "save_state", - lambda current: saved_epochs.append(current.get("status_projection_epoch")) or True, - ) - - reviewer_bot.main() - assert all(epoch != reviewer_bot.STATUS_PROJECTION_EPOCH for epoch in saved_epochs) - - -def test_main_schedule_projection_epoch_repair_relabels_previously_repaired_pr(monkeypatch): - monkeypatch.setenv("EVENT_NAME", "schedule") - monkeypatch.setenv("EVENT_ACTION", "") - state = make_state() - state["status_projection_epoch"] = "status_projection_v1" - review = reviewer_bot.ensure_review_entry(state, 256, create=True) - assert review is not None - review["current_reviewer"] = "vccjgust" - review["reviewer_review"]["accepted"] = { - "semantic_key": "pull_request_review:3821749029", - "timestamp": "2026-02-18T20:28:12Z", - "actor": "vccjgust", - "reviewed_head_sha": "head-1", - "source_precedence": 1, - "payload": {}, - } - review["active_head_sha"] = "head-1" - synced_issue_numbers = [] - - monkeypatch.setattr(reviewer_bot, "acquire_state_issue_lease_lock", lambda: None) - monkeypatch.setattr(reviewer_bot, "release_state_issue_lease_lock", lambda: True) - monkeypatch.setattr(reviewer_bot, "load_state", lambda *args, **kwargs: state) - monkeypatch.setattr(reviewer_bot, "process_pass_until_expirations", lambda current: (current, [])) - monkeypatch.setattr(reviewer_bot, "sync_members_with_queue", lambda current: (current, [])) - monkeypatch.setattr(reviewer_bot, "handle_scheduled_check", lambda current: False) - monkeypatch.setattr(reviewer_bot, "list_open_items_with_status_labels", lambda: [256]) - monkeypatch.setattr( - reviewer_bot, - "sync_status_labels_for_items", - lambda current, issue_numbers: synced_issue_numbers.extend(issue_numbers) or True, - ) - monkeypatch.setattr(reviewer_bot, "save_state", lambda current: True) - - reviewer_bot.main() - - assert synced_issue_numbers == [256] - - -def test_main_mutating_event_fails_closed_when_state_unavailable(monkeypatch): - monkeypatch.setenv("EVENT_NAME", "issue_comment") - monkeypatch.setenv("EVENT_ACTION", "created") - monkeypatch.setattr(reviewer_bot, "acquire_state_issue_lease_lock", lambda: None) - monkeypatch.setattr(reviewer_bot, "release_state_issue_lease_lock", lambda: True) - - def fail_load(*, fail_on_unavailable=False): - assert fail_on_unavailable is True - raise RuntimeError("state unavailable") - - monkeypatch.setattr(reviewer_bot, "load_state", fail_load) - - with pytest.raises(SystemExit) as excinfo: - reviewer_bot.main() - - assert excinfo.value.code == 1 - - -def test_main_mutating_event_does_not_sync_or_save_when_state_unavailable(monkeypatch): - monkeypatch.setenv("EVENT_NAME", "issue_comment") - monkeypatch.setenv("EVENT_ACTION", "created") - monkeypatch.setattr(reviewer_bot, "acquire_state_issue_lease_lock", lambda: None) - monkeypatch.setattr(reviewer_bot, "release_state_issue_lease_lock", lambda: True) - - called = { - "pass_until": False, - "sync": False, - "handler": False, - "save": False, - } - - def fail_load(*, fail_on_unavailable=False): - assert fail_on_unavailable is True - raise RuntimeError("state unavailable") - - def track_pass_until(state): - called["pass_until"] = True - return state, [] - - def track_sync(state): - called["sync"] = True - return state, [] - - def track_handler(state): - called["handler"] = True - return True - - def track_save(state): - called["save"] = True - return True - - monkeypatch.setattr(reviewer_bot, "load_state", fail_load) - monkeypatch.setattr(reviewer_bot, "process_pass_until_expirations", track_pass_until) - monkeypatch.setattr(reviewer_bot, "sync_members_with_queue", track_sync) - monkeypatch.setattr(reviewer_bot, "handle_comment_event", track_handler) - monkeypatch.setattr(reviewer_bot, "save_state", track_save) - - with pytest.raises(SystemExit) as excinfo: - reviewer_bot.main() - - assert excinfo.value.code == 1 - assert called == { - "pass_until": False, - "sync": False, - "handler": False, - "save": False, - } - - -def test_acquire_lock_retries_until_expected_token_visible(monkeypatch): - monkeypatch.setattr( - reviewer_bot.lease_lock_module, - "get_lock_owner_context", - lambda: ("local-run", "reviewer-bot", "reviewer-bot"), - ) - snapshots = iter( - [ - ("old-ref", "tree", {"lock_state": "unlocked", "lock_token": None}), - ("stale-ref", "tree", {"lock_state": "unlocked", "lock_token": None}), - ("stale-ref-2", "tree", {"lock_state": "unlocked", "lock_token": None}), - ( - "new-ref", - "tree", - { - "lock_state": "locked", - "lock_token": "token-123", - "lock_owner_run_id": "local-run", - "lock_owner_workflow": "reviewer-bot", - "lock_owner_job": "reviewer-bot", - "lock_expires_at": "2999-01-01T00:00:00+00:00", - }, - ), - ] - ) - - monkeypatch.setattr(reviewer_bot.lease_lock_module.uuid, "uuid4", lambda: type("U", (), {"hex": "token-123"})()) - monkeypatch.setattr(reviewer_bot.lease_lock_module.time, "sleep", lambda seconds: None) - monkeypatch.setattr(reviewer_bot, "get_lock_ref_snapshot", lambda: next(snapshots)) - monkeypatch.setattr(reviewer_bot, "create_lock_commit", lambda parent_sha, tree_sha, lock_meta: reviewer_bot.GitHubApiResult(201, {"sha": "commit-1"}, {}, "", True)) - monkeypatch.setattr(reviewer_bot, "cas_update_lock_ref", lambda new_sha: reviewer_bot.GitHubApiResult(200, {}, {}, "", True)) - monkeypatch.setattr(reviewer_bot, "get_state_issue_html_url", lambda: "https://example.com/issues/314") - monkeypatch.setattr(reviewer_bot, "ACTIVE_LEASE_CONTEXT", None) - - context = reviewer_bot.acquire_state_issue_lease_lock() - - assert context.lock_token == "token-123" - assert reviewer_bot.ACTIVE_LEASE_CONTEXT is context - - -def test_acquire_lock_fails_closed_on_conflicting_visible_token(monkeypatch): - snapshots = iter( - [ - ("old-ref", "tree", {"lock_state": "unlocked", "lock_token": None}), - ("new-ref", "tree", {"lock_state": "locked", "lock_token": "other-token"}), - ] - ) - - monkeypatch.setattr(reviewer_bot.lease_lock_module.uuid, "uuid4", lambda: type("U", (), {"hex": "token-123"})()) - monkeypatch.setattr(reviewer_bot.lease_lock_module.time, "sleep", lambda seconds: None) - monkeypatch.setattr(reviewer_bot, "get_lock_ref_snapshot", lambda: next(snapshots)) - monkeypatch.setattr(reviewer_bot, "create_lock_commit", lambda parent_sha, tree_sha, lock_meta: reviewer_bot.GitHubApiResult(201, {"sha": "commit-1"}, {}, "", True)) - monkeypatch.setattr(reviewer_bot, "cas_update_lock_ref", lambda new_sha: reviewer_bot.GitHubApiResult(200, {}, {}, "", True)) - monkeypatch.setattr(reviewer_bot, "get_state_issue_html_url", lambda: "https://example.com/issues/314") - monkeypatch.setattr(reviewer_bot, "ACTIVE_LEASE_CONTEXT", None) - - with pytest.raises(RuntimeError, match="unexpected lock state"): - reviewer_bot.acquire_state_issue_lease_lock() - - -def test_acquire_lock_succeeds_when_later_loop_observes_own_valid_token(monkeypatch): - monkeypatch.setattr( - reviewer_bot.lease_lock_module, - "get_lock_owner_context", - lambda: ("local-run", "reviewer-bot", "reviewer-bot"), - ) - snapshots = iter( - [ - ("old-ref", "tree", {"lock_state": "unlocked", "lock_token": None}), - ("stale-ref", "tree", {"lock_state": "unlocked", "lock_token": None}), - ( - "new-ref", - "tree", - { - "lock_state": "locked", - "lock_token": "token-123", - "lock_owner_run_id": "local-run", - "lock_owner_workflow": "reviewer-bot", - "lock_owner_job": "reviewer-bot", - "lock_expires_at": "2999-01-01T00:00:00+00:00", - }, - ), - ] - ) - - monkeypatch.setattr(reviewer_bot.lease_lock_module.uuid, "uuid4", lambda: type("U", (), {"hex": "token-123"})()) - monkeypatch.setattr(reviewer_bot.lease_lock_module.time, "sleep", lambda seconds: None) - monkeypatch.setattr(reviewer_bot, "get_lock_ref_snapshot", lambda: next(snapshots)) - monkeypatch.setattr(reviewer_bot, "create_lock_commit", lambda parent_sha, tree_sha, lock_meta: reviewer_bot.GitHubApiResult(201, {"sha": "commit-1"}, {}, "", True)) - monkeypatch.setattr(reviewer_bot, "cas_update_lock_ref", lambda new_sha: reviewer_bot.GitHubApiResult(200, {}, {}, "", True)) - monkeypatch.setattr(reviewer_bot, "get_state_issue_html_url", lambda: "https://example.com/issues/314") - monkeypatch.setattr(reviewer_bot, "ACTIVE_LEASE_CONTEXT", None) - - context = reviewer_bot.acquire_state_issue_lease_lock() - - assert context.lock_token == "token-123" - assert reviewer_bot.ACTIVE_LEASE_CONTEXT is context - - -def test_acquire_lock_fails_closed_when_own_token_has_mismatched_owner(monkeypatch): - monkeypatch.setattr( - reviewer_bot.lease_lock_module, - "get_lock_owner_context", - lambda: ("local-run", "reviewer-bot", "reviewer-bot"), - ) - snapshots = iter( - [ - ( - "new-ref", - "tree", - { - "lock_state": "locked", - "lock_token": "token-123", - "lock_owner_run_id": "someone-else", - "lock_owner_workflow": "reviewer-bot", - "lock_owner_job": "reviewer-bot", - "lock_expires_at": "2999-01-01T00:00:00+00:00", - }, - ) - ] - ) - - monkeypatch.setattr(reviewer_bot.lease_lock_module.uuid, "uuid4", lambda: type("U", (), {"hex": "token-123"})()) - monkeypatch.setattr(reviewer_bot.lease_lock_module.time, "sleep", lambda seconds: None) - monkeypatch.setattr(reviewer_bot, "get_lock_ref_snapshot", lambda: next(snapshots)) - monkeypatch.setattr(reviewer_bot, "get_state_issue_html_url", lambda: "https://example.com/issues/314") - monkeypatch.setattr(reviewer_bot, "ACTIVE_LEASE_CONTEXT", None) - - with pytest.raises(RuntimeError, match="owner metadata drifted"): - reviewer_bot.acquire_state_issue_lease_lock() - - -def test_release_lock_retries_stale_unlocked_predecessor(monkeypatch): - context = reviewer_bot.LeaseContext( - lock_token="token-123", - lock_owner_run_id="run", - lock_owner_workflow="workflow", - lock_owner_job="job", - state_issue_url="https://example.com/issues/314", - lock_ref="refs/heads/reviewer-bot-state-lock", - lock_expires_at="2999-01-01T00:00:00+00:00", - ) - snapshots = iter( - [ - ("stale-ref", "tree", {"lock_state": "unlocked", "lock_token": None}), - ("new-ref", "tree", {"lock_state": "locked", "lock_token": "token-123"}), - ] - ) - - monkeypatch.setattr(reviewer_bot, "ACTIVE_LEASE_CONTEXT", context) - monkeypatch.setattr(reviewer_bot.lease_lock_module.time, "sleep", lambda seconds: None) - monkeypatch.setattr(reviewer_bot, "get_lock_ref_snapshot", lambda: next(snapshots)) - monkeypatch.setattr(reviewer_bot, "create_lock_commit", lambda parent_sha, tree_sha, lock_meta: reviewer_bot.GitHubApiResult(201, {"sha": "commit-2"}, {}, "", True)) - monkeypatch.setattr(reviewer_bot, "cas_update_lock_ref", lambda new_sha: reviewer_bot.GitHubApiResult(200, {}, {}, "", True)) - - assert reviewer_bot.release_state_issue_lease_lock() is True - assert reviewer_bot.ACTIVE_LEASE_CONTEXT is None - - -def test_release_lock_fails_closed_on_conflicting_token(monkeypatch): - context = reviewer_bot.LeaseContext( - lock_token="token-123", - lock_owner_run_id="run", - lock_owner_workflow="workflow", - lock_owner_job="job", - state_issue_url="https://example.com/issues/314", - lock_ref="refs/heads/reviewer-bot-state-lock", - lock_expires_at="2999-01-01T00:00:00+00:00", - ) - monkeypatch.setattr(reviewer_bot, "ACTIVE_LEASE_CONTEXT", context) - monkeypatch.setattr(reviewer_bot, "get_lock_ref_snapshot", lambda: ("new-ref", "tree", {"lock_state": "locked", "lock_token": "other-token"})) - - assert reviewer_bot.release_state_issue_lease_lock() is False - assert reviewer_bot.ACTIVE_LEASE_CONTEXT is None - - -def test_schedule_guard_blocks_empty_active_reviews_wipe(monkeypatch): - monkeypatch.setenv("EVENT_NAME", "schedule") - monkeypatch.setenv("EVENT_ACTION", "") - monkeypatch.setattr(reviewer_bot, "acquire_state_issue_lease_lock", lambda: None) - monkeypatch.setattr(reviewer_bot, "release_state_issue_lease_lock", lambda: True) - - state = make_state() - state["active_reviews"] = { - "42": { - "current_reviewer": "alice", - "assigned_at": "2026-01-01T00:00:00+00:00", - "last_reviewer_activity": "2026-01-01T00:00:00+00:00", - } - } - - def wipe_active_reviews(input_state): - input_state["active_reviews"] = {} - return True - - save_called = {"value": False} - - def track_save(_state): - save_called["value"] = True - return True - - monkeypatch.setattr(reviewer_bot, "load_state", lambda *args, **kwargs: state) - monkeypatch.setattr(reviewer_bot, "process_pass_until_expirations", lambda current: (current, [])) - monkeypatch.setattr(reviewer_bot, "sync_members_with_queue", lambda current: (current, [])) - monkeypatch.setattr(reviewer_bot, "handle_scheduled_check", wipe_active_reviews) - monkeypatch.setattr(reviewer_bot, "save_state", track_save) - - with pytest.raises(SystemExit) as excinfo: - reviewer_bot.main() - - assert excinfo.value.code == 1 - assert save_called["value"] is False diff --git a/.github/reviewer-bot-tests/test_reviewer_bot.py b/.github/reviewer-bot-tests/test_reviewer_bot.py deleted file mode 100644 index 28accb930..000000000 --- a/.github/reviewer-bot-tests/test_reviewer_bot.py +++ /dev/null @@ -1,2759 +0,0 @@ -import json -import os -import subprocess -from datetime import timedelta -from pathlib import Path - -import pytest -import yaml - -from builder import build_cli -from scripts import reviewer_bot -from scripts.reviewer_bot_lib import comment_routing, sweeper - - -def make_state(epoch: str = "freshness_v15"): - return { - "schema_version": reviewer_bot.STATE_SCHEMA_VERSION, - "freshness_runtime_epoch": epoch, - "last_updated": None, - "current_index": 0, - "queue": [], - "pass_until": [], - "recent_assignments": [], - "active_reviews": {}, - } - - -def valid_reviewer_board_metadata(): - return { - "data": { - "organization": { - "projectV2": { - "id": "PVT_kwDOB", - "title": "Reviewer Board", - "fields": { - "nodes": [ - { - "__typename": "ProjectV2SingleSelectField", - "id": "field-review-state", - "name": "Review State", - "options": [ - {"id": "opt-ar", "name": "Awaiting Reviewer"}, - {"id": "opt-ac", "name": "Awaiting Contributor"}, - {"id": "opt-aw", "name": "Awaiting Write Approval"}, - {"id": "opt-done", "name": "Done"}, - {"id": "opt-unassigned", "name": "Unassigned"}, - ], - }, - { - "__typename": "ProjectV2Field", - "dataType": "TEXT", - "id": "field-reviewer", - "name": "Reviewer", - }, - { - "__typename": "ProjectV2Field", - "dataType": "DATE", - "id": "field-assigned-at", - "name": "Assigned At", - }, - { - "__typename": "ProjectV2Field", - "dataType": "DATE", - "id": "field-waiting-since", - "name": "Waiting Since", - }, - { - "__typename": "ProjectV2SingleSelectField", - "id": "field-needs-attention", - "name": "Needs Attention", - "options": [ - {"id": "opt-no", "name": "No"}, - {"id": "opt-warning", "name": "Warning Sent"}, - {"id": "opt-notice", "name": "Transition Notice Sent"}, - {"id": "opt-triage", "name": "Triage Approval Required"}, - {"id": "opt-repair", "name": "Projection Repair Required"}, - ], - }, - ] - }, - } - } - } - } - - -def iso_z(dt): - return dt.isoformat().replace("+00:00", "Z") - - -@pytest.fixture(autouse=True) -def clean_env(monkeypatch): - keys = [ - "EVENT_NAME", - "EVENT_ACTION", - "ISSUE_NUMBER", - "ISSUE_AUTHOR", - "IS_PULL_REQUEST", - "COMMENT_BODY", - "COMMENT_AUTHOR", - "COMMENT_ID", - "COMMENT_SOURCE_EVENT_KEY", - "COMMENT_CREATED_AT", - "COMMENT_USER_TYPE", - "COMMENT_AUTHOR_ASSOCIATION", - "COMMENT_SENDER_TYPE", - "COMMENT_INSTALLATION_ID", - "COMMENT_PERFORMED_VIA_GITHUB_APP", - "CURRENT_WORKFLOW_FILE", - "GITHUB_REPOSITORY", - "GITHUB_REF", - "ISSUE_BODY", - "ISSUE_UPDATED_AT", - "ISSUE_CHANGES_TITLE_FROM", - "ISSUE_CHANGES_BODY_FROM", - "SENDER_LOGIN", - "DEFERRED_CONTEXT_PATH", - "DEFERRED_ARTIFACT_RETENTION_DAYS", - "WORKFLOW_RUN_TRIGGERING_NAME", - "WORKFLOW_RUN_TRIGGERING_ID", - "WORKFLOW_RUN_TRIGGERING_ATTEMPT", - "WORKFLOW_RUN_TRIGGERING_CONCLUSION", - "MANUAL_ACTION", - "PRIVILEGED_SOURCE_EVENT_KEY", - ] - for key in keys: - monkeypatch.delenv(key, raising=False) - monkeypatch.setattr(reviewer_bot, "ACTIVE_LEASE_CONTEXT", object()) - monkeypatch.setattr(reviewer_bot, "_reviewer_board_project_metadata", None, raising=False) - - -def test_load_state_sets_schema_and_epoch_defaults(monkeypatch): - monkeypatch.setattr(reviewer_bot, "get_state_issue", lambda: {"body": "queue: []\n"}) - state = reviewer_bot.load_state() - assert state["schema_version"] == reviewer_bot.STATE_SCHEMA_VERSION - assert state["freshness_runtime_epoch"] == reviewer_bot.FRESHNESS_RUNTIME_EPOCH_LEGACY - - -def test_reviewer_board_preflight_validates_manifest(monkeypatch): - monkeypatch.setenv("REVIEWER_BOARD_ENABLED", "true") - monkeypatch.setenv("REVIEWER_BOARD_TOKEN", "board-token") - monkeypatch.setattr(reviewer_bot, "github_graphql", lambda query, variables=None, *, token=None: valid_reviewer_board_metadata()) - - preflight = reviewer_bot.reviewer_board_preflight() - - assert preflight.enabled is True - assert preflight.valid is True - assert preflight.project_id == "PVT_kwDOB" - - -@pytest.mark.parametrize( - ("env", "expected"), - [ - ({"COMMENT_USER_TYPE": "Bot", "COMMENT_AUTHOR": "dependabot[bot]"}, "bot_account"), - ({"COMMENT_USER_TYPE": "User", "COMMENT_AUTHOR": "alice", "COMMENT_INSTALLATION_ID": "7"}, "github_app_or_other_automation"), - ({"COMMENT_USER_TYPE": "User", "COMMENT_AUTHOR": "alice"}, "repo_user_principal"), - ({"COMMENT_AUTHOR": "mystery"}, "unknown_actor"), - ], -) -def test_classify_issue_comment_actor(monkeypatch, env, expected): - for key, value in env.items(): - monkeypatch.setenv(key, value) - assert comment_routing.classify_issue_comment_actor() == expected - - -def test_classify_comment_payload_distinguishes_command_plus_text(): - payload = comment_routing.classify_comment_payload(reviewer_bot, "hello\n@guidelines-bot /queue") - assert payload["comment_class"] == "command_plus_text" - assert payload["has_non_command_text"] is True - - -def test_route_issue_comment_trust_allows_only_same_repo_repo_user_principal(monkeypatch): - monkeypatch.setenv("IS_PULL_REQUEST", "true") - monkeypatch.setenv("COMMENT_USER_TYPE", "User") - monkeypatch.setenv("COMMENT_AUTHOR", "alice") - monkeypatch.setenv("COMMENT_AUTHOR_ASSOCIATION", "MEMBER") - monkeypatch.setenv("CURRENT_WORKFLOW_FILE", ".github/workflows/reviewer-bot-pr-comment-trusted.yml") - monkeypatch.setenv("GITHUB_REPOSITORY", "rustfoundation/safety-critical-rust-coding-guidelines") - monkeypatch.setenv("GITHUB_REF", "refs/heads/main") - monkeypatch.setattr( - reviewer_bot, - "github_api", - lambda method, endpoint, data=None: { - "head": {"repo": {"full_name": "rustfoundation/safety-critical-rust-coding-guidelines"}}, - "user": {"login": "carol"}, - }, - ) - assert comment_routing.route_issue_comment_trust(reviewer_bot, 42) == "pr_trusted_direct" - - -def test_route_issue_comment_trust_fails_closed_for_ambiguous_same_repo(monkeypatch): - monkeypatch.setenv("IS_PULL_REQUEST", "true") - monkeypatch.setenv("COMMENT_USER_TYPE", "") - monkeypatch.setenv("COMMENT_AUTHOR", "alice") - monkeypatch.setenv("COMMENT_AUTHOR_ASSOCIATION", "MEMBER") - monkeypatch.setenv("CURRENT_WORKFLOW_FILE", ".github/workflows/reviewer-bot-pr-comment-trusted.yml") - monkeypatch.setenv("GITHUB_REPOSITORY", "rustfoundation/safety-critical-rust-coding-guidelines") - monkeypatch.setenv("GITHUB_REF", "refs/heads/main") - monkeypatch.setattr( - reviewer_bot, - "github_api", - lambda method, endpoint, data=None: { - "head": {"repo": {"full_name": "rustfoundation/safety-critical-rust-coding-guidelines"}}, - "user": {"login": "carol"}, - }, - ) - with pytest.raises(RuntimeError, match="Ambiguous same-repo PR comment trust posture"): - comment_routing.route_issue_comment_trust(reviewer_bot, 42) - - -def test_handle_non_pr_issue_comment_creates_pending_privileged_command(monkeypatch): - state = make_state() - entry = reviewer_bot.ensure_review_entry(state, 42, create=True) - assert entry is not None - entry["current_reviewer"] = "alice" - monkeypatch.setenv("IS_PULL_REQUEST", "false") - monkeypatch.setenv("ISSUE_NUMBER", "42") - monkeypatch.setenv("ISSUE_AUTHOR", "dana") - monkeypatch.setenv("COMMENT_USER_TYPE", "User") - monkeypatch.setenv("COMMENT_AUTHOR", "dana") - monkeypatch.setenv("COMMENT_ID", "100") - monkeypatch.setenv("COMMENT_CREATED_AT", "2026-03-17T10:00:00Z") - monkeypatch.setenv("COMMENT_BODY", "@guidelines-bot /accept-no-fls-changes") - monkeypatch.setattr(reviewer_bot, "parse_issue_labels", lambda: [reviewer_bot.FLS_AUDIT_LABEL]) - monkeypatch.setattr(reviewer_bot, "check_user_permission", lambda username, required_permission="triage": True) - monkeypatch.setattr(reviewer_bot, "add_reaction", lambda *args, **kwargs: True) - monkeypatch.setattr(reviewer_bot, "post_comment", lambda *args, **kwargs: True) - assert reviewer_bot.handle_comment_event(state) is True - pending = state["active_reviews"]["42"]["pending_privileged_commands"] - assert pending["issue_comment:100"]["command_name"] == "accept-no-fls-changes" - assert pending["issue_comment:100"]["authorization"]["authorized"] is True - - -def test_closed_non_pr_plain_text_comment_does_not_create_review_entry(monkeypatch): - state = make_state() - monkeypatch.setenv("IS_PULL_REQUEST", "false") - monkeypatch.setenv("ISSUE_STATE", "closed") - monkeypatch.setenv("ISSUE_NUMBER", "42") - monkeypatch.setenv("ISSUE_AUTHOR", "dana") - monkeypatch.setenv("COMMENT_USER_TYPE", "User") - monkeypatch.setenv("COMMENT_AUTHOR", "dana") - monkeypatch.setenv("COMMENT_ID", "100") - monkeypatch.setenv("COMMENT_CREATED_AT", "2026-03-17T10:00:00Z") - monkeypatch.setenv("COMMENT_BODY", "reviewer-bot validation: close comment") - assert reviewer_bot.handle_comment_event(state) is False - assert state["active_reviews"] == {} - - -def test_closed_non_pr_command_comment_does_not_create_pending_privileged_command(monkeypatch): - state = make_state() - monkeypatch.setenv("IS_PULL_REQUEST", "false") - monkeypatch.setenv("ISSUE_STATE", "closed") - monkeypatch.setenv("ISSUE_NUMBER", "42") - monkeypatch.setenv("ISSUE_AUTHOR", "dana") - monkeypatch.setenv("COMMENT_USER_TYPE", "User") - monkeypatch.setenv("COMMENT_AUTHOR", "dana") - monkeypatch.setenv("COMMENT_ID", "100") - monkeypatch.setenv("COMMENT_CREATED_AT", "2026-03-17T10:00:00Z") - monkeypatch.setenv("COMMENT_BODY", "@guidelines-bot /accept-no-fls-changes") - called = {"post_comment": 0} - monkeypatch.setattr(reviewer_bot, "parse_issue_labels", lambda: [reviewer_bot.FLS_AUDIT_LABEL]) - monkeypatch.setattr(reviewer_bot, "check_user_permission", lambda username, required_permission="triage": True) - monkeypatch.setattr(reviewer_bot, "add_reaction", lambda *args, **kwargs: True) - monkeypatch.setattr(reviewer_bot, "post_comment", lambda *args, **kwargs: called.__setitem__("post_comment", called["post_comment"] + 1) or True) - assert reviewer_bot.handle_comment_event(state) is False - assert state["active_reviews"] == {} - assert called["post_comment"] == 0 - - -def test_closed_non_pr_comment_removes_stale_review_entry(monkeypatch): - state = make_state() - review = reviewer_bot.ensure_review_entry(state, 42, create=True) - assert review is not None - review["current_reviewer"] = "alice" - monkeypatch.setenv("IS_PULL_REQUEST", "false") - monkeypatch.setenv("ISSUE_STATE", "closed") - monkeypatch.setenv("ISSUE_NUMBER", "42") - monkeypatch.setenv("ISSUE_AUTHOR", "dana") - monkeypatch.setenv("COMMENT_USER_TYPE", "User") - monkeypatch.setenv("COMMENT_AUTHOR", "dana") - monkeypatch.setenv("COMMENT_ID", "100") - monkeypatch.setenv("COMMENT_CREATED_AT", "2026-03-17T10:00:00Z") - monkeypatch.setenv("COMMENT_BODY", "reviewer-bot validation: close comment") - assert reviewer_bot.handle_comment_event(state) is False - assert "42" not in state["active_reviews"] - - -def test_open_non_pr_plain_text_comment_still_updates_freshness(monkeypatch): - state = make_state() - review = reviewer_bot.ensure_review_entry(state, 42, create=True) - assert review is not None - review["current_reviewer"] = "alice" - monkeypatch.setenv("IS_PULL_REQUEST", "false") - monkeypatch.setenv("ISSUE_STATE", "open") - monkeypatch.setenv("ISSUE_NUMBER", "42") - monkeypatch.setenv("ISSUE_AUTHOR", "dana") - monkeypatch.setenv("COMMENT_USER_TYPE", "User") - monkeypatch.setenv("COMMENT_AUTHOR", "dana") - monkeypatch.setenv("COMMENT_ID", "100") - monkeypatch.setenv("COMMENT_CREATED_AT", "2026-03-17T10:00:00Z") - monkeypatch.setenv("COMMENT_BODY", "reviewer-bot validation: contributor plain text comment") - assert reviewer_bot.handle_comment_event(state) is True - accepted = state["active_reviews"]["42"]["contributor_comment"]["accepted"] - assert accepted["semantic_key"] == "issue_comment:100" - - -def test_label_signoff_create_pr_marks_issue_review_complete_and_syncs_status(monkeypatch): - state = make_state() - review = reviewer_bot.ensure_review_entry(state, 42, create=True) - assert review is not None - review["current_reviewer"] = "alice" - monkeypatch.setenv("IS_PULL_REQUEST", "false") - monkeypatch.setenv("ISSUE_NUMBER", "42") - monkeypatch.setenv("ISSUE_AUTHOR", "dana") - monkeypatch.setenv("COMMENT_USER_TYPE", "User") - monkeypatch.setenv("COMMENT_AUTHOR", "alice") - monkeypatch.setenv("COMMENT_ID", "100") - monkeypatch.setenv("COMMENT_CREATED_AT", "2026-03-17T10:00:00Z") - monkeypatch.setenv("COMMENT_BODY", "@guidelines-bot /label +sign-off: create pr") - monkeypatch.setattr(reviewer_bot, "get_repo_labels", lambda: ["sign-off: create pr"]) - monkeypatch.setattr(reviewer_bot, "add_label", lambda issue_number, label: True) - synced = [] - monkeypatch.setattr( - reviewer_bot, - "sync_status_labels_for_items", - lambda state_obj, issue_numbers: synced.append(list(issue_numbers)) or True, - ) - monkeypatch.setattr(reviewer_bot, "add_reaction", lambda *args, **kwargs: True) - posted = [] - monkeypatch.setattr(reviewer_bot, "post_comment", lambda issue_number, body: posted.append((issue_number, body)) or True) - assert reviewer_bot.handle_comment_event(state) is True - assert review["review_completion_source"] == "issue_label: sign-off: create pr" - assert review["current_cycle_completion"]["completed"] is True - assert synced == [[42]] - assert posted == [(42, "✅ Added label `sign-off: create pr`")] - - -def test_label_signoff_create_pr_on_pr_does_not_mark_issue_complete(monkeypatch): - state = make_state() - review = reviewer_bot.ensure_review_entry(state, 42, create=True) - assert review is not None - review["current_reviewer"] = "alice" - monkeypatch.setenv("IS_PULL_REQUEST", "true") - monkeypatch.setenv("ISSUE_NUMBER", "42") - monkeypatch.setenv("ISSUE_AUTHOR", "dana") - monkeypatch.setenv("COMMENT_USER_TYPE", "User") - monkeypatch.setenv("COMMENT_AUTHOR", "alice") - monkeypatch.setenv("COMMENT_AUTHOR_ASSOCIATION", "MEMBER") - monkeypatch.setenv("COMMENT_ID", "100") - monkeypatch.setenv("COMMENT_CREATED_AT", "2026-03-17T10:00:00Z") - monkeypatch.setenv("COMMENT_BODY", "@guidelines-bot /label +sign-off: create pr") - monkeypatch.setenv("CURRENT_WORKFLOW_FILE", ".github/workflows/reviewer-bot-pr-comment-trusted.yml") - monkeypatch.setenv("GITHUB_REPOSITORY", "rustfoundation/safety-critical-rust-coding-guidelines") - monkeypatch.setenv("GITHUB_REF", "refs/heads/main") - monkeypatch.setattr( - reviewer_bot, - "github_api", - lambda method, endpoint, data=None: { - "head": {"repo": {"full_name": "rustfoundation/safety-critical-rust-coding-guidelines"}}, - "user": {"login": "dana"}, - }, - ) - monkeypatch.setattr(reviewer_bot, "get_repo_labels", lambda: ["sign-off: create pr"]) - monkeypatch.setattr(reviewer_bot, "add_label", lambda issue_number, label: True) - monkeypatch.setattr(reviewer_bot, "sync_status_labels_for_items", lambda *args, **kwargs: pytest.fail("status sync should not run for PR sign-off label command")) - monkeypatch.setattr(reviewer_bot, "add_reaction", lambda *args, **kwargs: True) - monkeypatch.setattr(reviewer_bot, "post_comment", lambda *args, **kwargs: True) - assert reviewer_bot.handle_comment_event(state) is False - assert review["review_completion_source"] is None - - -def test_pr_comment_direct_path_is_epoch_gated(monkeypatch): - state = make_state(epoch="legacy_v14") - entry = reviewer_bot.ensure_review_entry(state, 42, create=True) - assert entry is not None - entry["current_reviewer"] = "alice" - monkeypatch.setenv("IS_PULL_REQUEST", "true") - monkeypatch.setenv("ISSUE_NUMBER", "42") - monkeypatch.setenv("ISSUE_AUTHOR", "dana") - monkeypatch.setenv("COMMENT_USER_TYPE", "User") - monkeypatch.setenv("COMMENT_AUTHOR", "alice") - monkeypatch.setenv("COMMENT_AUTHOR_ASSOCIATION", "MEMBER") - monkeypatch.setenv("COMMENT_ID", "100") - monkeypatch.setenv("COMMENT_CREATED_AT", "2026-03-17T10:00:00Z") - monkeypatch.setenv("COMMENT_BODY", "hello") - monkeypatch.setenv("CURRENT_WORKFLOW_FILE", ".github/workflows/reviewer-bot-pr-comment-trusted.yml") - monkeypatch.setenv("GITHUB_REPOSITORY", "rustfoundation/safety-critical-rust-coding-guidelines") - monkeypatch.setenv("GITHUB_REF", "refs/heads/main") - monkeypatch.setattr( - reviewer_bot, - "github_api", - lambda method, endpoint, data=None: { - "head": {"repo": {"full_name": "rustfoundation/safety-critical-rust-coding-guidelines"}}, - "user": {"login": "dana"}, - }, - ) - assert reviewer_bot.handle_comment_event(state) is False - - -def test_check_overdue_reviews_skips_transition_after_transition_notice_sent(monkeypatch): - state = make_state() - review = reviewer_bot.ensure_review_entry(state, 42, create=True) - assert review is not None - review["current_reviewer"] = "alice" - review["assigned_at"] = "2026-03-01T00:00:00Z" - review["last_reviewer_activity"] = "2026-03-01T00:00:00Z" - review["transition_warning_sent"] = "2026-03-10T00:00:00Z" - review["transition_notice_sent_at"] = "2026-03-25T00:00:00Z" - monkeypatch.setattr( - reviewer_bot, - "get_issue_or_pr_snapshot", - lambda issue_number: {"number": issue_number, "state": "open", "pull_request": {}, "labels": []}, - ) - monkeypatch.setattr(reviewer_bot, "get_pull_request_reviews", lambda issue_number: []) - assert reviewer_bot.maintenance_module.check_overdue_reviews(reviewer_bot, state) == [] - - -def test_handle_transition_notice_records_transition_notice_sent_at_once(monkeypatch): - state = make_state() - review = reviewer_bot.ensure_review_entry(state, 42, create=True) - assert review is not None - review["current_reviewer"] = "alice" - posted = [] - monkeypatch.setattr(reviewer_bot, "post_comment", lambda issue_number, body: posted.append((issue_number, body)) or True) - assert reviewer_bot.handle_transition_notice(state, 42, "alice") is True - assert review["transition_notice_sent_at"] is not None - assert reviewer_bot.handle_transition_notice(state, 42, "alice") is False - assert len(posted) == 1 - - -def test_handle_transition_notice_message_does_not_claim_reassignment(monkeypatch): - state = make_state() - reviewer_bot.ensure_review_entry(state, 42, create=True) - posted = [] - monkeypatch.setattr(reviewer_bot, "post_comment", lambda issue_number, body: posted.append(body) or True) - assert reviewer_bot.handle_transition_notice(state, 42, "alice") is True - assert "reassigned to the next person in the queue" not in posted[0] - assert "/pass" in posted[0] - - -def test_reviewer_comment_clears_warning_and_transition_notice_markers(monkeypatch): - state = make_state() - review = reviewer_bot.ensure_review_entry(state, 42, create=True) - assert review is not None - review["current_reviewer"] = "alice" - review["transition_warning_sent"] = "2026-03-10T00:00:00Z" - review["transition_notice_sent_at"] = "2026-03-25T00:00:00Z" - monkeypatch.setenv("IS_PULL_REQUEST", "true") - monkeypatch.setenv("ISSUE_NUMBER", "42") - monkeypatch.setenv("ISSUE_AUTHOR", "dana") - monkeypatch.setenv("COMMENT_USER_TYPE", "User") - monkeypatch.setenv("COMMENT_AUTHOR", "alice") - monkeypatch.setenv("COMMENT_AUTHOR_ASSOCIATION", "MEMBER") - monkeypatch.setenv("COMMENT_ID", "100") - monkeypatch.setenv("COMMENT_CREATED_AT", "2026-03-17T10:00:00Z") - monkeypatch.setenv("COMMENT_BODY", "hello") - monkeypatch.setenv("CURRENT_WORKFLOW_FILE", ".github/workflows/reviewer-bot-pr-comment-trusted.yml") - monkeypatch.setenv("GITHUB_REPOSITORY", "rustfoundation/safety-critical-rust-coding-guidelines") - monkeypatch.setenv("GITHUB_REF", "refs/heads/main") - monkeypatch.setattr( - reviewer_bot, - "github_api", - lambda method, endpoint, data=None: { - "head": {"repo": {"full_name": "rustfoundation/safety-critical-rust-coding-guidelines"}}, - "user": {"login": "dana"}, - }, - ) - assert reviewer_bot.handle_comment_event(state) is True - assert review["transition_warning_sent"] is None - assert review["transition_notice_sent_at"] is None - - -def test_scheduled_check_backfills_transition_notice_without_reposting(monkeypatch): - state = make_state() - review = reviewer_bot.ensure_review_entry(state, 42, create=True) - assert review is not None - review["current_reviewer"] = "alice" - review["assigned_at"] = "2026-03-01T00:00:00Z" - review["last_reviewer_activity"] = "2026-03-01T00:00:00Z" - review["transition_warning_sent"] = "2026-03-10T00:00:00Z" - monkeypatch.setattr(reviewer_bot.maintenance_module, "sweep_deferred_gaps", lambda bot, state: False) - monkeypatch.setattr(reviewer_bot.maintenance_module, "maybe_record_head_observation_repair", lambda bot, issue_number, review_data: False) - monkeypatch.setattr(reviewer_bot, "get_pull_request_reviews", lambda issue_number: []) - monkeypatch.setattr(reviewer_bot, "get_issue_or_pr_snapshot", lambda issue_number: {"pull_request": {}}) - posted = [] - monkeypatch.setattr(reviewer_bot, "post_comment", lambda issue_number, body: posted.append(body) or True) - - def fake_api(method, endpoint, data=None): - if endpoint == "issues/42/comments?per_page=100": - return [ - { - "id": 99, - "created_at": "2026-03-25T15:22:42Z", - "body": "🔔 **Transition Period Ended**\n\nExisting notice", - "user": {"login": "github-actions[bot]"}, - } - ] - raise AssertionError(endpoint) - - monkeypatch.setattr(reviewer_bot, "github_api", fake_api) - assert reviewer_bot.handle_scheduled_check(state) is True - assert review["transition_notice_sent_at"] == "2026-03-25T15:22:42Z" - assert posted == [] - - -def test_scheduled_check_repairs_missing_reviewer_review_state(monkeypatch): - state = make_state() - review = reviewer_bot.ensure_review_entry(state, 42, create=True) - assert review is not None - review["current_reviewer"] = "alice" - review["active_cycle_started_at"] = "2026-03-17T09:00:00Z" - monkeypatch.setattr(reviewer_bot.maintenance_module, "sweep_deferred_gaps", lambda bot, state: False) - monkeypatch.setattr(reviewer_bot.maintenance_module, "maybe_record_head_observation_repair", lambda bot, issue_number, review_data: False) - monkeypatch.setattr(reviewer_bot.maintenance_module, "check_overdue_reviews", lambda bot, state: []) - monkeypatch.setattr(reviewer_bot, "get_issue_or_pr_snapshot", lambda issue_number: {"pull_request": {}}) - monkeypatch.setattr( - reviewer_bot, - "get_pull_request_reviews", - lambda issue_number: [ - { - "id": 10, - "state": "COMMENTED", - "submitted_at": "2026-03-17T10:01:00Z", - "commit_id": "head-1", - "user": {"login": "alice"}, - } - ], - ) - assert reviewer_bot.handle_scheduled_check(state) is True - accepted = review["reviewer_review"]["accepted"] - assert accepted is not None - assert accepted["semantic_key"] == "pull_request_review:10" - assert review["last_reviewer_activity"] == "2026-03-17T10:01:00Z" - - -def test_check_overdue_reviews_skips_pr_with_current_head_reviewer_review(monkeypatch): - state = make_state() - review = reviewer_bot.ensure_review_entry(state, 42, create=True) - assert review is not None - review["current_reviewer"] = "alice" - review["assigned_at"] = "2026-03-01T00:00:00Z" - review["active_cycle_started_at"] = "2026-03-01T00:00:00Z" - review["reviewer_review"]["accepted"] = { - "semantic_key": "pull_request_review:10", - "timestamp": "2026-03-02T00:00:00Z", - "actor": "alice", - "reviewed_head_sha": "head-1", - "source_precedence": 1, - "payload": {}, - } - monkeypatch.setattr( - reviewer_bot, - "get_issue_or_pr_snapshot", - lambda issue_number: {"number": issue_number, "state": "open", "pull_request": {}, "labels": []}, - ) - monkeypatch.setattr( - reviewer_bot, - "github_api", - lambda method, endpoint, data=None: {"head": {"sha": "head-1"}} if endpoint == "pulls/42" else None, - ) - monkeypatch.setattr(reviewer_bot, "get_pull_request_reviews", lambda issue_number: []) - monkeypatch.setattr( - reviewer_bot.reviews_module, - "rebuild_pr_approval_state", - lambda bot, issue_number, review_data, **kwargs: ({"completed": False}, {"has_write_approval": False}), - ) - assert reviewer_bot.maintenance_module.check_overdue_reviews(reviewer_bot, state) == [] - - -def test_check_overdue_reviews_uses_contributor_comment_timestamp_when_turn_returns_to_reviewer(monkeypatch): - now = reviewer_bot.datetime.now(reviewer_bot.timezone.utc) - assigned_at = iso_z(now - timedelta(days=reviewer_bot.REVIEW_DEADLINE_DAYS + 20)) - reviewer_review_at = iso_z(now - timedelta(days=reviewer_bot.REVIEW_DEADLINE_DAYS + 19)) - contributor_comment_at = iso_z( - now - timedelta(days=reviewer_bot.REVIEW_DEADLINE_DAYS, minutes=1) - ) - state = make_state() - review = reviewer_bot.ensure_review_entry(state, 42, create=True) - assert review is not None - review["current_reviewer"] = "alice" - review["assigned_at"] = assigned_at - review["active_cycle_started_at"] = assigned_at - review["reviewer_review"]["accepted"] = { - "semantic_key": "pull_request_review:10", - "timestamp": reviewer_review_at, - "actor": "alice", - "reviewed_head_sha": "head-1", - "source_precedence": 1, - "payload": {}, - } - review["contributor_comment"]["accepted"] = { - "semantic_key": "issue_comment:20", - "timestamp": contributor_comment_at, - "actor": "bob", - "reviewed_head_sha": None, - "source_precedence": 0, - "payload": {}, - } - monkeypatch.setattr( - reviewer_bot, - "get_issue_or_pr_snapshot", - lambda issue_number: {"number": issue_number, "state": "open", "pull_request": {}, "labels": []}, - ) - monkeypatch.setattr( - reviewer_bot, - "github_api", - lambda method, endpoint, data=None: {"head": {"sha": "head-1"}} if endpoint == "pulls/42" else None, - ) - monkeypatch.setattr(reviewer_bot, "get_pull_request_reviews", lambda issue_number: []) - monkeypatch.setattr( - reviewer_bot.reviews_module, - "rebuild_pr_approval_state", - lambda bot, issue_number, review_data, **kwargs: ({"completed": False}, {"has_write_approval": False}), - ) - overdue = reviewer_bot.maintenance_module.check_overdue_reviews(reviewer_bot, state) - assert overdue[0]["issue_number"] == 42 - assert overdue[0]["needs_warning"] is True - assert overdue[0]["days_overdue"] == 0 - - -def test_check_overdue_reviews_uses_contributor_revision_timestamp_when_head_changes_after_review(monkeypatch): - now = reviewer_bot.datetime.now(reviewer_bot.timezone.utc) - assigned_at = iso_z(now - timedelta(days=reviewer_bot.REVIEW_DEADLINE_DAYS + 20)) - reviewer_review_at = iso_z(now - timedelta(days=reviewer_bot.REVIEW_DEADLINE_DAYS + 19)) - contributor_revision_at = iso_z( - now - timedelta(days=reviewer_bot.REVIEW_DEADLINE_DAYS, minutes=1) - ) - state = make_state() - review = reviewer_bot.ensure_review_entry(state, 42, create=True) - assert review is not None - review["current_reviewer"] = "alice" - review["assigned_at"] = assigned_at - review["active_cycle_started_at"] = assigned_at - review["reviewer_review"]["accepted"] = { - "semantic_key": "pull_request_review:10", - "timestamp": reviewer_review_at, - "actor": "alice", - "reviewed_head_sha": "head-1", - "source_precedence": 1, - "payload": {}, - } - review["contributor_revision"]["accepted"] = { - "semantic_key": "pull_request_sync:42:head-2", - "timestamp": contributor_revision_at, - "actor": None, - "reviewed_head_sha": "head-2", - "source_precedence": 1, - "payload": {}, - } - monkeypatch.setattr( - reviewer_bot, - "get_issue_or_pr_snapshot", - lambda issue_number: {"number": issue_number, "state": "open", "pull_request": {}, "labels": []}, - ) - monkeypatch.setattr( - reviewer_bot, - "github_api", - lambda method, endpoint, data=None: {"head": {"sha": "head-2"}} if endpoint == "pulls/42" else None, - ) - monkeypatch.setattr(reviewer_bot, "get_pull_request_reviews", lambda issue_number: []) - overdue = reviewer_bot.maintenance_module.check_overdue_reviews(reviewer_bot, state) - assert overdue[0]["issue_number"] == 42 - assert overdue[0]["needs_warning"] is True - assert overdue[0]["days_overdue"] == 0 - - -def test_check_overdue_reviews_ignores_same_head_contributor_revision_after_valid_reviewer_review(monkeypatch): - state = make_state() - review = reviewer_bot.ensure_review_entry(state, 42, create=True) - assert review is not None - review["current_reviewer"] = "alice" - review["assigned_at"] = "2026-03-01T00:00:00Z" - review["active_cycle_started_at"] = "2026-03-01T00:00:00Z" - review["reviewer_review"]["accepted"] = { - "semantic_key": "pull_request_review:10", - "timestamp": "2026-03-02T00:00:00Z", - "actor": "alice", - "reviewed_head_sha": "head-1", - "source_precedence": 1, - "payload": {}, - } - review["contributor_revision"]["accepted"] = { - "semantic_key": "pull_request_head_observed:42:head-1", - "timestamp": "2026-03-12T00:00:00Z", - "actor": None, - "reviewed_head_sha": "head-1", - "source_precedence": 1, - "payload": {}, - } - monkeypatch.setattr( - reviewer_bot, - "get_issue_or_pr_snapshot", - lambda issue_number: {"number": issue_number, "state": "open", "pull_request": {}, "labels": []}, - ) - monkeypatch.setattr( - reviewer_bot, - "github_api", - lambda method, endpoint, data=None: {"head": {"sha": "head-1"}} if endpoint == "pulls/42" else None, - ) - monkeypatch.setattr(reviewer_bot, "get_pull_request_reviews", lambda issue_number: []) - monkeypatch.setattr( - reviewer_bot.reviews_module, - "rebuild_pr_approval_state", - lambda bot, issue_number, review_data, **kwargs: ({"completed": False}, {"has_write_approval": False}), - ) - assert reviewer_bot.maintenance_module.check_overdue_reviews(reviewer_bot, state) == [] - - -def test_issue_edit_by_author_records_contributor_freshness(monkeypatch): - state = make_state() - review = reviewer_bot.ensure_review_entry(state, 42, create=True) - assert review is not None - review["current_reviewer"] = "alice" - monkeypatch.setenv("IS_PULL_REQUEST", "false") - monkeypatch.setenv("ISSUE_NUMBER", "42") - monkeypatch.setenv("ISSUE_AUTHOR", "dana") - monkeypatch.setenv("SENDER_LOGIN", "dana") - monkeypatch.setenv("ISSUE_TITLE", "New title") - monkeypatch.setenv("ISSUE_BODY", "body") - monkeypatch.setenv("ISSUE_CHANGES_TITLE_FROM", "Old title") - monkeypatch.setenv("ISSUE_CHANGES_BODY_FROM", "body") - monkeypatch.setenv("ISSUE_UPDATED_AT", "2026-03-17T10:00:00Z") - assert reviewer_bot.handle_issue_edited_event(state) is True - accepted = review["contributor_comment"]["accepted"] - assert accepted["semantic_key"].startswith("issues_edit_title:42:") - - -def test_project_status_labels_uses_commit_id_and_comment_freshness(monkeypatch): - state = make_state() - review = reviewer_bot.ensure_review_entry(state, 42, create=True) - assert review is not None - review["current_reviewer"] = "alice" - review["active_cycle_started_at"] = "2026-03-17T09:00:00Z" - reviewer_bot.reviews_module.accept_channel_event( - review, - "reviewer_comment", - semantic_key="issue_comment:1", - timestamp="2026-03-17T10:00:00Z", - actor="alice", - ) - reviewer_bot.reviews_module.accept_channel_event( - review, - "reviewer_review", - semantic_key="pull_request_review:10", - timestamp="2026-03-17T10:01:00Z", - actor="alice", - reviewed_head_sha="head-1", - source_precedence=1, - ) - monkeypatch.setattr( - reviewer_bot, - "get_issue_or_pr_snapshot", - lambda issue_number: {"number": issue_number, "state": "open", "pull_request": {}, "labels": []}, - ) - monkeypatch.setattr( - reviewer_bot, - "github_api", - lambda method, endpoint, data=None: {"head": {"sha": "head-2"}} if endpoint == "pulls/42" else None, - ) - desired_labels, metadata = reviewer_bot.project_status_labels_for_item(42, state) - assert desired_labels == {reviewer_bot.STATUS_AWAITING_REVIEWER_RESPONSE_LABEL} - assert metadata["reason"] == "review_head_stale" - - -def test_project_status_labels_uses_live_current_reviewer_review_when_channel_state_missing(monkeypatch): - state = make_state() - review = reviewer_bot.ensure_review_entry(state, 42, create=True) - assert review is not None - review["current_reviewer"] = "alice" - review["active_cycle_started_at"] = "2026-03-17T09:00:00Z" - monkeypatch.setattr( - reviewer_bot, - "get_issue_or_pr_snapshot", - lambda issue_number: {"number": issue_number, "state": "open", "pull_request": {}, "labels": []}, - ) - monkeypatch.setattr( - reviewer_bot, - "github_api", - lambda method, endpoint, data=None: {"head": {"sha": "head-1"}} if endpoint == "pulls/42" else None, - ) - monkeypatch.setattr( - reviewer_bot, - "get_pull_request_reviews", - lambda issue_number: [ - { - "id": 10, - "state": "COMMENTED", - "submitted_at": "2026-03-17T10:01:00Z", - "commit_id": "head-1", - "user": {"login": "alice"}, - } - ], - ) - desired_labels, metadata = reviewer_bot.project_status_labels_for_item(42, state) - assert desired_labels == {reviewer_bot.STATUS_AWAITING_CONTRIBUTOR_RESPONSE_LABEL} - assert metadata["reason"] == "completion_missing" - - -def test_preview_board_projection_valid_manifest_yields_preview_output(monkeypatch): - state = make_state() - review = reviewer_bot.ensure_review_entry(state, 42, create=True) - assert review is not None - review["current_reviewer"] = "alice" - review["assigned_at"] = "2026-03-20T12:34:56Z" - review["active_cycle_started_at"] = "2026-03-20T12:34:56Z" - monkeypatch.setattr( - reviewer_bot, - "get_issue_or_pr_snapshot", - lambda issue_number: {"number": issue_number, "state": "open", "pull_request": None, "labels": []}, - ) - - preview = reviewer_bot.preview_board_projection_for_item(state, 42) - - assert preview.classification == "open_tracked_assigned" - assert preview.eligible is True - assert preview.desired is not None - assert preview.desired.review_state == "Awaiting Reviewer" - assert preview.desired.reviewer == "alice" - - -def test_preview_board_projection_tracked_unassigned_maps_to_unassigned(monkeypatch): - state = make_state() - reviewer_bot.ensure_review_entry(state, 42, create=True) - monkeypatch.setattr( - reviewer_bot, - "get_issue_or_pr_snapshot", - lambda issue_number: {"number": issue_number, "state": "open", "pull_request": None, "labels": []}, - ) - - preview = reviewer_bot.preview_board_projection_for_item(state, 42) - - assert preview.classification == "open_tracked_unassigned" - assert preview.desired is not None - assert preview.desired.review_state == "Unassigned" - assert preview.desired.reviewer is None - assert preview.desired.waiting_since is None - assert preview.desired.needs_attention == "No" - - -def test_preview_board_projection_closed_item_maps_to_archive_intent(monkeypatch): - state = make_state() - review = reviewer_bot.ensure_review_entry(state, 42, create=True) - assert review is not None - review["current_reviewer"] = "alice" - monkeypatch.setattr( - reviewer_bot, - "get_issue_or_pr_snapshot", - lambda issue_number: {"number": issue_number, "state": "closed", "pull_request": None, "labels": []}, - ) - - preview = reviewer_bot.preview_board_projection_for_item(state, 42) - - assert preview.classification == "closed" - assert preview.eligible is False - assert preview.desired is not None - assert preview.desired.archive is True - assert preview.desired.ensure_membership is False - - -def test_preview_board_projection_open_untracked_maps_to_archive_intent(monkeypatch): - state = make_state() - monkeypatch.setattr( - reviewer_bot, - "get_issue_or_pr_snapshot", - lambda issue_number: {"number": issue_number, "state": "open", "pull_request": None, "labels": []}, - ) - - preview = reviewer_bot.preview_board_projection_for_item(state, 42) - - assert preview.classification == "open_untracked" - assert preview.eligible is False - assert preview.desired is not None - assert preview.desired.archive is True - - -def test_preview_board_projection_formats_dates_at_day_granularity(monkeypatch): - state = make_state() - review = reviewer_bot.ensure_review_entry(state, 42, create=True) - assert review is not None - review["current_reviewer"] = "alice" - review["assigned_at"] = "2026-03-20T12:34:56Z" - review["active_cycle_started_at"] = "2026-03-20T12:34:56Z" - reviewer_bot.reviews_module.accept_channel_event( - review, - "reviewer_comment", - semantic_key="issue_comment:1", - timestamp="2026-03-21T08:00:00Z", - actor="alice", - ) - reviewer_bot.reviews_module.accept_channel_event( - review, - "reviewer_review", - semantic_key="pull_request_review:10", - timestamp="2026-03-21T08:00:00Z", - actor="alice", - reviewed_head_sha="head-1", - source_precedence=1, - ) - monkeypatch.setattr( - reviewer_bot, - "get_issue_or_pr_snapshot", - lambda issue_number: {"number": issue_number, "state": "open", "pull_request": {}, "labels": []}, - ) - monkeypatch.setattr( - reviewer_bot, - "github_api", - lambda method, endpoint, data=None: {"head": {"sha": "head-1"}} if endpoint == "pulls/42" else None, - ) - monkeypatch.setattr( - reviewer_bot.reviews_module, - "rebuild_pr_approval_state", - lambda bot, issue_number, review_data, **kwargs: ({"completed": False}, {"has_write_approval": False}), - ) - - preview = reviewer_bot.preview_board_projection_for_item(state, 42) - - assert preview.desired is not None - assert preview.desired.assigned_at == "2026-03-20" - assert preview.desired.waiting_since == "2026-03-21" - - -def test_project_status_labels_uses_live_review_fallback_for_stale_head(monkeypatch): - state = make_state() - review = reviewer_bot.ensure_review_entry(state, 42, create=True) - assert review is not None - review["current_reviewer"] = "alice" - review["active_cycle_started_at"] = "2026-03-17T09:00:00Z" - monkeypatch.setattr( - reviewer_bot, - "get_issue_or_pr_snapshot", - lambda issue_number: {"number": issue_number, "state": "open", "pull_request": {}, "labels": []}, - ) - monkeypatch.setattr( - reviewer_bot, - "github_api", - lambda method, endpoint, data=None: {"head": {"sha": "head-2"}} if endpoint == "pulls/42" else None, - ) - monkeypatch.setattr( - reviewer_bot, - "get_pull_request_reviews", - lambda issue_number: [ - { - "id": 10, - "state": "COMMENTED", - "submitted_at": "2026-03-17T10:01:00Z", - "commit_id": "head-1", - "user": {"login": "alice"}, - } - ], - ) - desired_labels, metadata = reviewer_bot.project_status_labels_for_item(42, state) - assert desired_labels == {reviewer_bot.STATUS_AWAITING_REVIEWER_RESPONSE_LABEL} - assert metadata["reason"] == "review_head_stale" - - -def test_project_status_labels_prefers_current_head_review_over_newer_stale_review(monkeypatch): - state = make_state() - review = reviewer_bot.ensure_review_entry(state, 42, create=True) - assert review is not None - review["current_reviewer"] = "alice" - review["active_cycle_started_at"] = "2026-03-17T09:00:00Z" - monkeypatch.setattr( - reviewer_bot, - "get_issue_or_pr_snapshot", - lambda issue_number: {"number": issue_number, "state": "open", "pull_request": {}, "labels": []}, - ) - monkeypatch.setattr( - reviewer_bot, - "github_api", - lambda method, endpoint, data=None: {"head": {"sha": "head-1"}} if endpoint == "pulls/42" else None, - ) - monkeypatch.setattr( - reviewer_bot, - "get_pull_request_reviews", - lambda issue_number: [ - { - "id": 10, - "state": "COMMENTED", - "submitted_at": "2026-03-17T10:01:00Z", - "commit_id": "head-1", - "user": {"login": "alice"}, - }, - { - "id": 11, - "state": "COMMENTED", - "submitted_at": "2026-03-17T11:01:00Z", - "commit_id": "head-0", - "user": {"login": "alice"}, - }, - ], - ) - desired_labels, metadata = reviewer_bot.project_status_labels_for_item(42, state) - assert desired_labels == {reviewer_bot.STATUS_AWAITING_CONTRIBUTOR_RESPONSE_LABEL} - assert metadata["reason"] == "completion_missing" - - -def test_project_status_labels_pr256_shape_remains_awaiting_contributor_response(monkeypatch): - state = make_state() - review = reviewer_bot.ensure_review_entry(state, 42, create=True) - assert review is not None - review["current_reviewer"] = "vccjgust" - review["active_cycle_started_at"] = "2026-02-18T09:00:00Z" - reviewer_bot.reviews_module.accept_channel_event( - review, - "contributor_comment", - semantic_key="issue_comment:20", - timestamp="2026-02-18T09:30:00Z", - actor="dana", - ) - monkeypatch.setattr( - reviewer_bot, - "get_issue_or_pr_snapshot", - lambda issue_number: {"number": issue_number, "state": "open", "pull_request": {}, "labels": []}, - ) - monkeypatch.setattr( - reviewer_bot, - "github_api", - lambda method, endpoint, data=None: {"head": {"sha": "head-current"}} if endpoint == "pulls/42" else None, - ) - monkeypatch.setattr( - reviewer_bot, - "get_pull_request_reviews", - lambda issue_number: [ - { - "id": 30, - "state": "COMMENTED", - "submitted_at": "2026-02-18T10:00:00Z", - "commit_id": "head-older", - "user": {"login": "vccjgust"}, - }, - { - "id": 31, - "state": "COMMENTED", - "submitted_at": "2026-02-18T11:00:00Z", - "commit_id": "head-current", - "user": {"login": "vccjgust"}, - }, - ], - ) - desired_labels, metadata = reviewer_bot.project_status_labels_for_item(42, state) - assert desired_labels == {reviewer_bot.STATUS_AWAITING_CONTRIBUTOR_RESPONSE_LABEL} - assert metadata["reason"] == "completion_missing" - - -def test_project_status_labels_prefers_newer_contributor_comment_over_live_review_fallback(monkeypatch): - state = make_state() - review = reviewer_bot.ensure_review_entry(state, 42, create=True) - assert review is not None - review["current_reviewer"] = "alice" - review["active_cycle_started_at"] = "2026-03-17T09:00:00Z" - reviewer_bot.reviews_module.accept_channel_event( - review, - "contributor_comment", - semantic_key="issue_comment:20", - timestamp="2026-03-17T10:05:00Z", - actor="bob", - ) - monkeypatch.setattr( - reviewer_bot, - "get_issue_or_pr_snapshot", - lambda issue_number: {"number": issue_number, "state": "open", "pull_request": {}, "labels": []}, - ) - monkeypatch.setattr( - reviewer_bot, - "github_api", - lambda method, endpoint, data=None: {"head": {"sha": "head-1"}} if endpoint == "pulls/42" else None, - ) - monkeypatch.setattr( - reviewer_bot, - "get_pull_request_reviews", - lambda issue_number: [ - { - "id": 10, - "state": "COMMENTED", - "submitted_at": "2026-03-17T10:01:00Z", - "commit_id": "head-1", - "user": {"login": "alice"}, - } - ], - ) - desired_labels, metadata = reviewer_bot.project_status_labels_for_item(42, state) - assert desired_labels == {reviewer_bot.STATUS_AWAITING_REVIEWER_RESPONSE_LABEL} - assert metadata["reason"] == "contributor_comment_newer" - - -def test_record_reviewer_activity_does_not_regress_timestamp_on_legacy_backfill(): - review = reviewer_bot.ensure_review_entry(make_state(), 42, create=True) - assert review is not None - review["last_reviewer_activity"] = "2026-03-20T10:00:00Z" - review["transition_warning_sent"] = "2026-03-21T10:00:00Z" - review["transition_notice_sent_at"] = "2026-03-22T10:00:00Z" - reviewer_bot.reviews_module.record_reviewer_activity(review, "2026-03-18T10:00:00Z") - assert review["last_reviewer_activity"] == "2026-03-20T10:00:00Z" - assert review["transition_warning_sent"] is None - assert review["transition_notice_sent_at"] is None - - -def test_project_status_labels_emits_awaiting_write_approval_only_after_completion(monkeypatch): - state = make_state() - review = reviewer_bot.ensure_review_entry(state, 42, create=True) - assert review is not None - review["current_reviewer"] = "alice" - review["active_cycle_started_at"] = "2026-03-17T09:00:00Z" - reviewer_bot.reviews_module.accept_channel_event( - review, - "reviewer_comment", - semantic_key="issue_comment:1", - timestamp="2026-03-17T10:00:00Z", - actor="alice", - ) - reviewer_bot.reviews_module.accept_channel_event( - review, - "reviewer_review", - semantic_key="pull_request_review:10", - timestamp="2026-03-17T10:01:00Z", - actor="alice", - reviewed_head_sha="head-1", - source_precedence=1, - ) - monkeypatch.setattr( - reviewer_bot, - "get_issue_or_pr_snapshot", - lambda issue_number: {"number": issue_number, "state": "open", "pull_request": {}, "labels": []}, - ) - - def fake_api(method, endpoint, data=None): - if endpoint == "pulls/42": - return {"head": {"sha": "head-1"}} - return None - - monkeypatch.setattr(reviewer_bot, "github_api", fake_api) - monkeypatch.setattr( - reviewer_bot, - "get_pull_request_reviews", - lambda issue_number: [ - { - "id": 10, - "state": "APPROVED", - "submitted_at": "2026-03-17T10:01:00Z", - "commit_id": "head-1", - "user": {"login": "bob"}, - } - ], - ) - monkeypatch.setattr(reviewer_bot, "check_user_permission", lambda username, required_permission="triage": False) - desired_labels, metadata = reviewer_bot.project_status_labels_for_item(42, state) - assert desired_labels == {reviewer_bot.STATUS_AWAITING_WRITE_APPROVAL_LABEL} - assert metadata["state"] == "awaiting_write_approval" - review["mandatory_approver_required"] = True - desired_labels_again, _ = reviewer_bot.project_status_labels_for_item(42, state) - assert desired_labels_again == {reviewer_bot.STATUS_AWAITING_WRITE_APPROVAL_LABEL} - - -def test_handle_workflow_run_event_rebuilds_completion_from_live_review_commit_id(tmp_path, monkeypatch): - state = make_state() - review = reviewer_bot.ensure_review_entry(state, 42, create=True) - assert review is not None - review["current_reviewer"] = "alice" - review["active_cycle_started_at"] = "2026-03-17T09:00:00Z" - payload_path = tmp_path / "deferred.json" - payload_path.write_text( - json.dumps( - { - "schema_version": 2, - "source_workflow_name": "Reviewer Bot PR Review Submitted Observer", - "source_workflow_file": ".github/workflows/reviewer-bot-pr-review-submitted-observer.yml", - "source_run_id": 500, - "source_run_attempt": 2, - "source_event_name": "pull_request_review", - "source_event_action": "submitted", - "source_event_key": "pull_request_review:11", - "pr_number": 42, - "review_id": 11, - "source_submitted_at": "2026-03-17T10:00:00Z", - "source_review_state": "APPROVED", - "source_commit_id": "head-1", - "actor_login": "alice", - } - ), - encoding="utf-8", - ) - monkeypatch.setenv("DEFERRED_CONTEXT_PATH", str(payload_path)) - monkeypatch.setenv("WORKFLOW_RUN_TRIGGERING_NAME", "Reviewer Bot PR Review Submitted Observer") - monkeypatch.setenv("WORKFLOW_RUN_TRIGGERING_ID", "500") - monkeypatch.setenv("WORKFLOW_RUN_TRIGGERING_ATTEMPT", "2") - monkeypatch.setenv("WORKFLOW_RUN_TRIGGERING_CONCLUSION", "success") - monkeypatch.setattr( - reviewer_bot, - "github_api", - lambda method, endpoint, data=None: { - "pulls/42": {"head": {"sha": "head-2"}}, - "pulls/42/reviews/11": { - "id": 11, - "submitted_at": "2026-03-17T10:00:00Z", - "state": "APPROVED", - "commit_id": "head-1", - "user": {"login": "alice"}, - }, - }.get(endpoint), - ) - monkeypatch.setattr( - reviewer_bot, - "get_pull_request_reviews", - lambda issue_number: [ - { - "id": 11, - "submitted_at": "2026-03-17T10:00:00Z", - "state": "APPROVED", - "commit_id": "head-1", - "user": {"login": "alice"}, - } - ], - ) - assert reviewer_bot.handle_workflow_run_event(state) is True - assert state["active_reviews"]["42"]["current_cycle_completion"]["completed"] is False - - -def test_repair_missing_reviewer_review_state_refreshes_to_preferred_current_head_review(monkeypatch): - state = make_state() - review = reviewer_bot.ensure_review_entry(state, 42, create=True) - assert review is not None - review["current_reviewer"] = "alice" - review["active_cycle_started_at"] = "2026-03-17T09:00:00Z" - reviewer_bot.reviews_module.accept_channel_event( - review, - "reviewer_review", - semantic_key="pull_request_review:99", - timestamp="2026-03-17T11:00:00Z", - actor="alice", - reviewed_head_sha="head-0", - source_precedence=1, - ) - monkeypatch.setattr( - reviewer_bot, - "github_api", - lambda method, endpoint, data=None: {"head": {"sha": "head-1"}} if endpoint == "pulls/42" else None, - ) - monkeypatch.setattr( - reviewer_bot, - "get_pull_request_reviews", - lambda issue_number: [ - { - "id": 10, - "state": "COMMENTED", - "submitted_at": "2026-03-17T10:00:00Z", - "commit_id": "head-1", - "user": {"login": "alice"}, - }, - { - "id": 99, - "state": "COMMENTED", - "submitted_at": "2026-03-17T11:00:00Z", - "commit_id": "head-0", - "user": {"login": "alice"}, - }, - ], - ) - assert reviewer_bot.reviews_module.repair_missing_reviewer_review_state(reviewer_bot, 42, review) is True - accepted = review["reviewer_review"]["accepted"] - assert accepted["semantic_key"] == "pull_request_review:10" - assert accepted["reviewed_head_sha"] == "head-1" - assert "pull_request_review:99" in review["reviewer_review"]["seen_keys"] - - -def test_handle_workflow_run_event_refreshes_stale_stored_reviewer_review_to_current_head_preferred_review(tmp_path, monkeypatch): - state = make_state() - review = reviewer_bot.ensure_review_entry(state, 42, create=True) - assert review is not None - review["current_reviewer"] = "alice" - review["active_cycle_started_at"] = "2026-03-17T09:00:00Z" - reviewer_bot.reviews_module.accept_channel_event( - review, - "reviewer_review", - semantic_key="pull_request_review:99", - timestamp="2026-03-17T11:00:00Z", - actor="alice", - reviewed_head_sha="head-0", - source_precedence=1, - ) - payload_path = tmp_path / "deferred-review.json" - payload_path.write_text( - json.dumps( - { - "schema_version": 2, - "source_workflow_name": "Reviewer Bot PR Review Submitted Observer", - "source_workflow_file": ".github/workflows/reviewer-bot-pr-review-submitted-observer.yml", - "source_run_id": 500, - "source_run_attempt": 2, - "source_event_name": "pull_request_review", - "source_event_action": "submitted", - "source_event_key": "pull_request_review:99", - "pr_number": 42, - "review_id": 99, - "source_submitted_at": "2026-03-17T11:00:00Z", - "source_review_state": "COMMENTED", - "source_commit_id": "head-0", - "actor_login": "alice", - } - ), - encoding="utf-8", - ) - monkeypatch.setenv("DEFERRED_CONTEXT_PATH", str(payload_path)) - monkeypatch.setenv("WORKFLOW_RUN_TRIGGERING_NAME", "Reviewer Bot PR Review Submitted Observer") - monkeypatch.setenv("WORKFLOW_RUN_TRIGGERING_ID", "500") - monkeypatch.setenv("WORKFLOW_RUN_TRIGGERING_ATTEMPT", "2") - monkeypatch.setenv("WORKFLOW_RUN_TRIGGERING_CONCLUSION", "success") - - def fake_github_api(method, endpoint, data=None): - if endpoint == "pulls/42": - return {"head": {"sha": "head-1"}, "user": {"login": "dana"}, "labels": []} - if endpoint == "pulls/42/reviews/99": - return { - "id": 99, - "submitted_at": "2026-03-17T11:00:00Z", - "state": "COMMENTED", - "commit_id": "head-0", - "user": {"login": "alice"}, - } - raise AssertionError(f"Unexpected endpoint: {endpoint}") - - monkeypatch.setattr(reviewer_bot, "github_api", fake_github_api) - monkeypatch.setattr( - reviewer_bot, - "get_pull_request_reviews", - lambda issue_number: [ - { - "id": 10, - "submitted_at": "2026-03-17T10:00:00Z", - "state": "COMMENTED", - "commit_id": "head-1", - "user": {"login": "alice"}, - }, - { - "id": 99, - "submitted_at": "2026-03-17T11:00:00Z", - "state": "COMMENTED", - "commit_id": "head-0", - "user": {"login": "alice"}, - }, - ], - ) - assert reviewer_bot.handle_workflow_run_event(state) is True - accepted = review["reviewer_review"]["accepted"] - assert accepted["semantic_key"] == "pull_request_review:10" - assert accepted["reviewed_head_sha"] == "head-1" - - -def test_workflow_run_review_submission_clears_warning_and_transition_notice_markers(tmp_path, monkeypatch): - state = make_state() - review = reviewer_bot.ensure_review_entry(state, 42, create=True) - assert review is not None - review["current_reviewer"] = "alice" - review["active_cycle_started_at"] = "2026-03-17T09:00:00Z" - review["transition_warning_sent"] = "2026-03-18T00:00:00Z" - review["transition_notice_sent_at"] = "2026-03-25T00:00:00Z" - payload_path = tmp_path / "deferred-review.json" - payload_path.write_text( - json.dumps( - { - "schema_version": 2, - "source_workflow_name": "Reviewer Bot PR Review Submitted Observer", - "source_workflow_file": ".github/workflows/reviewer-bot-pr-review-submitted-observer.yml", - "source_run_id": 500, - "source_run_attempt": 2, - "source_event_name": "pull_request_review", - "source_event_action": "submitted", - "source_event_key": "pull_request_review:11", - "pr_number": 42, - "review_id": 11, - "source_submitted_at": "2026-03-17T10:00:00Z", - "source_review_state": "COMMENTED", - "source_commit_id": "head-1", - "actor_login": "alice", - } - ), - encoding="utf-8", - ) - monkeypatch.setenv("DEFERRED_CONTEXT_PATH", str(payload_path)) - monkeypatch.setenv("WORKFLOW_RUN_TRIGGERING_NAME", "Reviewer Bot PR Review Submitted Observer") - monkeypatch.setenv("WORKFLOW_RUN_TRIGGERING_ID", "500") - monkeypatch.setenv("WORKFLOW_RUN_TRIGGERING_ATTEMPT", "2") - monkeypatch.setenv("WORKFLOW_RUN_TRIGGERING_CONCLUSION", "success") - monkeypatch.setattr( - reviewer_bot, - "github_api", - lambda method, endpoint, data=None: { - "pulls/42": {"head": {"sha": "head-2"}, "user": {"login": "dana"}, "labels": []}, - "pulls/42/reviews/11": { - "id": 11, - "submitted_at": "2026-03-17T10:00:00Z", - "state": "COMMENTED", - "commit_id": "head-1", - "user": {"login": "alice"}, - }, - }.get(endpoint), - ) - monkeypatch.setattr( - reviewer_bot, - "get_pull_request_reviews", - lambda issue_number: [ - { - "id": 11, - "submitted_at": "2026-03-17T10:00:00Z", - "state": "COMMENTED", - "commit_id": "head-1", - "user": {"login": "alice"}, - } - ], - ) - assert reviewer_bot.handle_workflow_run_event(state) is True - assert review["transition_warning_sent"] is None - assert review["transition_notice_sent_at"] is None - - -def test_deferred_comment_missing_live_object_preserves_source_time_freshness(tmp_path, monkeypatch): - state = make_state() - review = reviewer_bot.ensure_review_entry(state, 42, create=True) - assert review is not None - review["current_reviewer"] = "alice" - payload_path = tmp_path / "deferred-comment.json" - payload_path.write_text( - json.dumps( - { - "schema_version": 2, - "source_workflow_name": "Reviewer Bot PR Comment Observer", - "source_workflow_file": ".github/workflows/reviewer-bot-pr-comment-observer.yml", - "source_run_id": 501, - "source_run_attempt": 1, - "source_event_name": "issue_comment", - "source_event_action": "created", - "source_event_key": "issue_comment:99", - "pr_number": 42, - "comment_id": 99, - "comment_class": "plain_text", - "has_non_command_text": True, - "source_body_digest": "abc", - "source_created_at": "2026-03-17T10:00:00Z", - "actor_login": "alice", - } - ), - encoding="utf-8", - ) - monkeypatch.setenv("DEFERRED_CONTEXT_PATH", str(payload_path)) - monkeypatch.setenv("WORKFLOW_RUN_TRIGGERING_NAME", "Reviewer Bot PR Comment Observer") - monkeypatch.setenv("WORKFLOW_RUN_TRIGGERING_ID", "501") - monkeypatch.setenv("WORKFLOW_RUN_TRIGGERING_ATTEMPT", "1") - monkeypatch.setenv("WORKFLOW_RUN_TRIGGERING_CONCLUSION", "success") - monkeypatch.setattr( - reviewer_bot, - "github_api", - lambda method, endpoint, data=None: ( - {"user": {"login": "dana"}, "labels": []} if endpoint == "pulls/42" else None - ), - ) - assert reviewer_bot.handle_workflow_run_event(state) is True - assert state["active_reviews"]["42"]["reviewer_comment"]["accepted"]["semantic_key"] == "issue_comment:99" - assert state["active_reviews"]["42"]["deferred_gaps"]["issue_comment:99"]["reason"] == "reconcile_failed_closed" - - -def test_deferred_review_comment_reconcile_records_contributor_freshness(tmp_path, monkeypatch): - state = make_state() - review = reviewer_bot.ensure_review_entry(state, 42, create=True) - assert review is not None - review["current_reviewer"] = "alice" - live_body = "author reply in review thread" - payload_path = tmp_path / "deferred-review-comment.json" - payload_path.write_text( - json.dumps( - { - "schema_version": 2, - "source_workflow_name": "Reviewer Bot PR Review Comment Observer", - "source_workflow_file": ".github/workflows/reviewer-bot-pr-review-comment-observer.yml", - "source_run_id": 701, - "source_run_attempt": 1, - "source_event_name": "pull_request_review_comment", - "source_event_action": "created", - "source_event_key": "pull_request_review_comment:301", - "pr_number": 42, - "comment_id": 301, - "comment_class": "plain_text", - "has_non_command_text": True, - "source_body_digest": comment_routing._digest_body(live_body), - "source_created_at": "2026-03-17T10:00:00Z", - "actor_login": "dana", - "actor_id": 5, - "actor_class": "repo_user_principal", - "pull_request_review_id": 10, - "in_reply_to_id": 200, - "source_artifact_name": "reviewer-bot-review-comment-context-701-attempt-1", - } - ), - encoding="utf-8", - ) - monkeypatch.setenv("DEFERRED_CONTEXT_PATH", str(payload_path)) - monkeypatch.setenv("WORKFLOW_RUN_TRIGGERING_NAME", "Reviewer Bot PR Review Comment Observer") - monkeypatch.setenv("WORKFLOW_RUN_TRIGGERING_ID", "701") - monkeypatch.setenv("WORKFLOW_RUN_TRIGGERING_ATTEMPT", "1") - monkeypatch.setenv("WORKFLOW_RUN_TRIGGERING_CONCLUSION", "success") - - def fake_github_api(method, endpoint, data=None): - if endpoint == "pulls/42": - return {"user": {"login": "dana"}, "labels": []} - if endpoint == "pulls/comments/301": - return { - "body": live_body, - "user": {"login": "dana", "type": "User"}, - "author_association": "CONTRIBUTOR", - "performed_via_github_app": None, - } - raise AssertionError(f"Unexpected endpoint: {endpoint}") - - monkeypatch.setattr(reviewer_bot, "github_api", fake_github_api) - assert reviewer_bot.handle_workflow_run_event(state) is True - assert review["contributor_comment"]["accepted"]["semantic_key"] == "pull_request_review_comment:301" - - -def test_deferred_review_comment_reconcile_records_reviewer_freshness(tmp_path, monkeypatch): - state = make_state() - review = reviewer_bot.ensure_review_entry(state, 42, create=True) - assert review is not None - review["current_reviewer"] = "alice" - live_body = "reviewer reply in thread" - payload_path = tmp_path / "deferred-review-comment.json" - payload_path.write_text( - json.dumps( - { - "schema_version": 2, - "source_workflow_name": "Reviewer Bot PR Review Comment Observer", - "source_workflow_file": ".github/workflows/reviewer-bot-pr-review-comment-observer.yml", - "source_run_id": 702, - "source_run_attempt": 1, - "source_event_name": "pull_request_review_comment", - "source_event_action": "created", - "source_event_key": "pull_request_review_comment:302", - "pr_number": 42, - "comment_id": 302, - "comment_class": "plain_text", - "has_non_command_text": True, - "source_body_digest": comment_routing._digest_body(live_body), - "source_created_at": "2026-03-17T11:00:00Z", - "actor_login": "alice", - "actor_id": 6, - "actor_class": "repo_user_principal", - "pull_request_review_id": 10, - "in_reply_to_id": 200, - "source_artifact_name": "reviewer-bot-review-comment-context-702-attempt-1", - } - ), - encoding="utf-8", - ) - monkeypatch.setenv("DEFERRED_CONTEXT_PATH", str(payload_path)) - monkeypatch.setenv("WORKFLOW_RUN_TRIGGERING_NAME", "Reviewer Bot PR Review Comment Observer") - monkeypatch.setenv("WORKFLOW_RUN_TRIGGERING_ID", "702") - monkeypatch.setenv("WORKFLOW_RUN_TRIGGERING_ATTEMPT", "1") - monkeypatch.setenv("WORKFLOW_RUN_TRIGGERING_CONCLUSION", "success") - - def fake_github_api(method, endpoint, data=None): - if endpoint == "pulls/42": - return {"user": {"login": "dana"}, "labels": []} - if endpoint == "pulls/comments/302": - return { - "body": live_body, - "user": {"login": "alice", "type": "User"}, - "author_association": "MEMBER", - "performed_via_github_app": None, - } - raise AssertionError(f"Unexpected endpoint: {endpoint}") - - monkeypatch.setattr(reviewer_bot, "github_api", fake_github_api) - assert reviewer_bot.handle_workflow_run_event(state) is True - assert review["reviewer_comment"]["accepted"]["semantic_key"] == "pull_request_review_comment:302" - - -def test_deferred_review_comment_missing_live_object_preserves_source_time_freshness(tmp_path, monkeypatch): - state = make_state() - review = reviewer_bot.ensure_review_entry(state, 42, create=True) - assert review is not None - review["current_reviewer"] = "alice" - payload_path = tmp_path / "deferred-review-comment.json" - payload_path.write_text( - json.dumps( - { - "schema_version": 2, - "source_workflow_name": "Reviewer Bot PR Review Comment Observer", - "source_workflow_file": ".github/workflows/reviewer-bot-pr-review-comment-observer.yml", - "source_run_id": 703, - "source_run_attempt": 1, - "source_event_name": "pull_request_review_comment", - "source_event_action": "created", - "source_event_key": "pull_request_review_comment:303", - "pr_number": 42, - "comment_id": 303, - "comment_class": "plain_text", - "has_non_command_text": True, - "source_body_digest": "abc", - "source_created_at": "2026-03-17T10:00:00Z", - "actor_login": "alice", - "actor_id": 6, - "actor_class": "repo_user_principal", - "pull_request_review_id": 10, - "in_reply_to_id": 200, - "source_artifact_name": "reviewer-bot-review-comment-context-703-attempt-1", - } - ), - encoding="utf-8", - ) - monkeypatch.setenv("DEFERRED_CONTEXT_PATH", str(payload_path)) - monkeypatch.setenv("WORKFLOW_RUN_TRIGGERING_NAME", "Reviewer Bot PR Review Comment Observer") - monkeypatch.setenv("WORKFLOW_RUN_TRIGGERING_ID", "703") - monkeypatch.setenv("WORKFLOW_RUN_TRIGGERING_ATTEMPT", "1") - monkeypatch.setenv("WORKFLOW_RUN_TRIGGERING_CONCLUSION", "success") - monkeypatch.setattr( - reviewer_bot, - "github_api", - lambda method, endpoint, data=None: ({"user": {"login": "dana"}, "labels": []} if endpoint == "pulls/42" else None), - ) - assert reviewer_bot.handle_workflow_run_event(state) is True - assert review["reviewer_comment"]["accepted"]["semantic_key"] == "pull_request_review_comment:303" - assert review["deferred_gaps"]["pull_request_review_comment:303"]["reason"] == "reconcile_failed_closed" - - -def test_review_comment_artifact_identity_validation(tmp_path, monkeypatch): - state = make_state() - review = reviewer_bot.ensure_review_entry(state, 42, create=True) - assert review is not None - review["current_reviewer"] = "alice" - payload_path = tmp_path / "deferred-review-comment.json" - payload_path.write_text( - json.dumps( - { - "schema_version": 2, - "source_workflow_name": "Reviewer Bot PR Review Comment Observer", - "source_workflow_file": ".github/workflows/reviewer-bot-pr-review-comment-observer.yml", - "source_run_id": 704, - "source_run_attempt": 1, - "source_event_name": "pull_request_review_comment", - "source_event_action": "created", - "source_event_key": "pull_request_review_comment:304", - "pr_number": 42, - "comment_id": 304, - "comment_class": "plain_text", - "has_non_command_text": True, - "source_body_digest": "abc", - "source_created_at": "2026-03-17T10:00:00Z", - "actor_login": "alice", - "actor_id": 6, - "actor_class": "repo_user_principal", - "pull_request_review_id": 10, - "in_reply_to_id": 200, - "source_artifact_name": "reviewer-bot-review-comment-context-704-attempt-1", - } - ), - encoding="utf-8", - ) - monkeypatch.setenv("DEFERRED_CONTEXT_PATH", str(payload_path)) - monkeypatch.setenv("WORKFLOW_RUN_TRIGGERING_NAME", "Reviewer Bot PR Review Comment Observer") - monkeypatch.setenv("WORKFLOW_RUN_TRIGGERING_ID", "704") - monkeypatch.setenv("WORKFLOW_RUN_TRIGGERING_ATTEMPT", "1") - monkeypatch.setenv("WORKFLOW_RUN_TRIGGERING_CONCLUSION", "success") - monkeypatch.setattr(reviewer_bot, "github_api", lambda method, endpoint, data=None: {"user": {"login": "dana"}, "labels": []} if endpoint == "pulls/42" else None) - assert reviewer_bot.handle_workflow_run_event(state) is True - - -def test_deferred_comment_reconcile_hydrates_pr_author_context_for_contributor_freshness(tmp_path, monkeypatch): - state = make_state() - review = reviewer_bot.ensure_review_entry(state, 42, create=True) - assert review is not None - review["current_reviewer"] = "alice" - payload_path = tmp_path / "deferred-comment.json" - live_body = "reviewer-bot validation: contributor plain text comment" - payload_path.write_text( - json.dumps( - { - "schema_version": 2, - "source_workflow_name": "Reviewer Bot PR Comment Observer", - "source_workflow_file": ".github/workflows/reviewer-bot-pr-comment-observer.yml", - "source_run_id": 601, - "source_run_attempt": 1, - "source_event_name": "issue_comment", - "source_event_action": "created", - "source_event_key": "issue_comment:199", - "pr_number": 42, - "comment_id": 199, - "comment_class": "plain_text", - "has_non_command_text": True, - "source_body_digest": comment_routing._digest_body(live_body), - "source_created_at": "2026-03-17T10:00:00Z", - "actor_login": "dana", - } - ), - encoding="utf-8", - ) - monkeypatch.setenv("DEFERRED_CONTEXT_PATH", str(payload_path)) - monkeypatch.setenv("WORKFLOW_RUN_TRIGGERING_NAME", "Reviewer Bot PR Comment Observer") - monkeypatch.setenv("WORKFLOW_RUN_TRIGGERING_ID", "601") - monkeypatch.setenv("WORKFLOW_RUN_TRIGGERING_ATTEMPT", "1") - monkeypatch.setenv("WORKFLOW_RUN_TRIGGERING_CONCLUSION", "success") - - def fake_github_api(method, endpoint, data=None): - if endpoint == "pulls/42": - return {"user": {"login": "dana"}, "labels": [{"name": "coding guideline"}]} - if endpoint == "issues/comments/199": - return { - "body": live_body, - "user": {"login": "dana", "type": "User"}, - "author_association": "CONTRIBUTOR", - "performed_via_github_app": None, - } - raise AssertionError(f"Unexpected endpoint: {endpoint}") - - monkeypatch.setattr(reviewer_bot, "github_api", fake_github_api) - assert reviewer_bot.handle_workflow_run_event(state) is True - assert state["active_reviews"]["42"]["contributor_comment"]["accepted"]["semantic_key"] == "issue_comment:199" - assert state["active_reviews"]["42"]["reviewer_comment"]["accepted"] is None - assert os.environ["IS_PULL_REQUEST"] == "true" - assert os.environ["ISSUE_AUTHOR"] == "dana" - assert json.loads(os.environ["ISSUE_LABELS"]) == ["coding guideline"] - - -def test_deferred_comment_reconcile_uses_pr_assignment_semantics_for_claim(tmp_path, monkeypatch): - state = make_state() - state["queue"] = [{"github": "bob", "name": "Bob"}] - review = reviewer_bot.ensure_review_entry(state, 42, create=True) - assert review is not None - review["current_reviewer"] = "alice" - payload_path = tmp_path / "deferred-command.json" - live_body = "@guidelines-bot /claim" - payload_path.write_text( - json.dumps( - { - "schema_version": 2, - "source_workflow_name": "Reviewer Bot PR Comment Observer", - "source_workflow_file": ".github/workflows/reviewer-bot-pr-comment-observer.yml", - "source_run_id": 602, - "source_run_attempt": 1, - "source_event_name": "issue_comment", - "source_event_action": "created", - "source_event_key": "issue_comment:200", - "pr_number": 42, - "comment_id": 200, - "comment_class": "command_only", - "has_non_command_text": False, - "source_body_digest": comment_routing._digest_body(live_body), - "source_created_at": "2026-03-17T10:00:00Z", - "actor_login": "bob", - } - ), - encoding="utf-8", - ) - monkeypatch.setenv("DEFERRED_CONTEXT_PATH", str(payload_path)) - monkeypatch.setenv("WORKFLOW_RUN_TRIGGERING_NAME", "Reviewer Bot PR Comment Observer") - monkeypatch.setenv("WORKFLOW_RUN_TRIGGERING_ID", "602") - monkeypatch.setenv("WORKFLOW_RUN_TRIGGERING_ATTEMPT", "1") - monkeypatch.setenv("WORKFLOW_RUN_TRIGGERING_CONCLUSION", "success") - assignment_calls = [] - removed_reviewers = [] - posted_comments = [] - - def fake_github_api(method, endpoint, data=None): - if endpoint == "pulls/42": - return { - "user": {"login": "dana"}, - "labels": [{"name": "coding guideline"}], - "requested_reviewers": [{"login": "alice"}], - } - if endpoint == "issues/comments/200": - return { - "body": live_body, - "user": {"login": "bob", "type": "User"}, - "author_association": "MEMBER", - "performed_via_github_app": None, - } - raise AssertionError(f"Unexpected endpoint: {endpoint}") - - def fake_request(issue_number, username): - assignment_calls.append( - { - "issue_number": issue_number, - "username": username, - "is_pull_request": os.environ.get("IS_PULL_REQUEST"), - "issue_author": os.environ.get("ISSUE_AUTHOR"), - } - ) - return reviewer_bot.AssignmentAttempt(success=True, status_code=201) - - monkeypatch.setattr(reviewer_bot, "github_api", fake_github_api) - monkeypatch.setattr(reviewer_bot, "request_reviewer_assignment", fake_request) - monkeypatch.setattr(reviewer_bot, "unassign_reviewer", lambda issue_number, username: removed_reviewers.append((issue_number, username)) or True) - monkeypatch.setattr(reviewer_bot, "post_comment", lambda issue_number, body: posted_comments.append((issue_number, body)) or True) - monkeypatch.setattr(reviewer_bot, "add_reaction", lambda *args, **kwargs: True) - assert reviewer_bot.handle_workflow_run_event(state) is True - assert assignment_calls == [ - { - "issue_number": 42, - "username": "bob", - "is_pull_request": "true", - "issue_author": "dana", - } - ] - assert removed_reviewers == [(42, "alice")] - assert state["active_reviews"]["42"]["current_reviewer"] == "bob" - assert posted_comments - - -def test_deferred_comment_reconcile_fails_closed_when_command_replay_is_ambiguous(tmp_path, monkeypatch): - state = make_state() - review = reviewer_bot.ensure_review_entry(state, 42, create=True) - assert review is not None - review["current_reviewer"] = "alice" - payload_path = tmp_path / "deferred-command.json" - live_body = "@guidelines-bot /claim" - payload_path.write_text( - json.dumps( - { - "schema_version": 2, - "source_workflow_name": "Reviewer Bot PR Comment Observer", - "source_workflow_file": ".github/workflows/reviewer-bot-pr-comment-observer.yml", - "source_run_id": 603, - "source_run_attempt": 1, - "source_event_name": "issue_comment", - "source_event_action": "created", - "source_event_key": "issue_comment:201", - "pr_number": 42, - "comment_id": 201, - "comment_class": "command_only", - "has_non_command_text": False, - "source_body_digest": comment_routing._digest_body(live_body), - "source_created_at": "2026-03-17T10:00:00Z", - "actor_login": "bob", - } - ), - encoding="utf-8", - ) - monkeypatch.setenv("DEFERRED_CONTEXT_PATH", str(payload_path)) - monkeypatch.setenv("WORKFLOW_RUN_TRIGGERING_NAME", "Reviewer Bot PR Comment Observer") - monkeypatch.setenv("WORKFLOW_RUN_TRIGGERING_ID", "603") - monkeypatch.setenv("WORKFLOW_RUN_TRIGGERING_ATTEMPT", "1") - monkeypatch.setenv("WORKFLOW_RUN_TRIGGERING_CONCLUSION", "success") - - def fake_github_api(method, endpoint, data=None): - if endpoint == "pulls/42": - return {"user": {"login": "dana"}, "labels": []} - if endpoint == "issues/comments/201": - return { - "body": live_body, - "user": {"login": "bob", "type": "User"}, - "author_association": "MEMBER", - "performed_via_github_app": None, - } - raise AssertionError(f"Unexpected endpoint: {endpoint}") - - monkeypatch.setattr(reviewer_bot, "github_api", fake_github_api) - monkeypatch.setattr( - reviewer_bot.reconcile_module, - "classify_comment_payload", - lambda bot, body: { - "comment_class": "command_only", - "has_non_command_text": False, - "command_count": 2, - "command": None, - "args": [], - "normalized_body": body, - }, - ) - command_calls = [] - monkeypatch.setattr( - reviewer_bot.reconcile_module, - "_handle_command", - lambda *args, **kwargs: command_calls.append("called") or True, - ) - - assert reviewer_bot.handle_workflow_run_event(state) is False - assert command_calls == [] - assert state["active_reviews"]["42"]["deferred_gaps"]["issue_comment:201"]["reason"] == "reconcile_failed_closed" - assert "issue_comment:201" not in state["active_reviews"]["42"]["reconciled_source_events"] - - -def test_deferred_comment_reconcile_fails_closed_when_comment_classification_drifts(tmp_path, monkeypatch): - state = make_state() - review = reviewer_bot.ensure_review_entry(state, 42, create=True) - assert review is not None - review["current_reviewer"] = "alice" - payload_path = tmp_path / "deferred-comment.json" - live_body = "reviewer-bot validation: contributor plain text comment" - payload_path.write_text( - json.dumps( - { - "schema_version": 2, - "source_workflow_name": "Reviewer Bot PR Comment Observer", - "source_workflow_file": ".github/workflows/reviewer-bot-pr-comment-observer.yml", - "source_run_id": 604, - "source_run_attempt": 1, - "source_event_name": "issue_comment", - "source_event_action": "created", - "source_event_key": "issue_comment:202", - "pr_number": 42, - "comment_id": 202, - "comment_class": "plain_text", - "has_non_command_text": True, - "source_body_digest": comment_routing._digest_body(live_body), - "source_created_at": "2026-03-17T10:00:00Z", - "actor_login": "dana", - } - ), - encoding="utf-8", - ) - monkeypatch.setenv("DEFERRED_CONTEXT_PATH", str(payload_path)) - monkeypatch.setenv("WORKFLOW_RUN_TRIGGERING_NAME", "Reviewer Bot PR Comment Observer") - monkeypatch.setenv("WORKFLOW_RUN_TRIGGERING_ID", "604") - monkeypatch.setenv("WORKFLOW_RUN_TRIGGERING_ATTEMPT", "1") - monkeypatch.setenv("WORKFLOW_RUN_TRIGGERING_CONCLUSION", "success") - - def fake_github_api(method, endpoint, data=None): - if endpoint == "pulls/42": - return {"user": {"login": "dana"}, "labels": []} - if endpoint == "issues/comments/202": - return { - "body": live_body, - "user": {"login": "dana", "type": "User"}, - "author_association": "CONTRIBUTOR", - "performed_via_github_app": None, - } - raise AssertionError(f"Unexpected endpoint: {endpoint}") - - monkeypatch.setattr(reviewer_bot, "github_api", fake_github_api) - monkeypatch.setattr( - reviewer_bot.reconcile_module, - "classify_comment_payload", - lambda bot, body: { - "comment_class": "command_plus_text", - "has_non_command_text": True, - "command_count": 1, - "command": "claim", - "args": [], - "normalized_body": body, - }, - ) - - assert reviewer_bot.handle_workflow_run_event(state) is True - assert state["active_reviews"]["42"]["contributor_comment"]["accepted"]["semantic_key"] == "issue_comment:202" - assert state["active_reviews"]["42"]["deferred_gaps"]["issue_comment:202"]["reason"] == "reconcile_failed_closed" - assert "issue_comment:202" not in state["active_reviews"]["42"]["reconciled_source_events"] - - -def test_observer_noop_payload_is_safe_noop(tmp_path, monkeypatch): - state = make_state() - reviewer_bot.ensure_review_entry(state, 42, create=True) - payload_path = tmp_path / "observer-noop.json" - payload_path.write_text( - json.dumps( - { - "schema_version": 1, - "kind": "observer_noop", - "reason": "ignored_non_human_automation", - "source_workflow_name": "Reviewer Bot PR Comment Observer", - "source_workflow_file": ".github/workflows/reviewer-bot-pr-comment-observer.yml", - "source_run_id": 777, - "source_run_attempt": 1, - "source_event_name": "issue_comment", - "source_event_action": "created", - "source_event_key": "issue_comment:111", - "pr_number": 42, - } - ), - encoding="utf-8", - ) - monkeypatch.setenv("DEFERRED_CONTEXT_PATH", str(payload_path)) - monkeypatch.setenv("WORKFLOW_RUN_TRIGGERING_NAME", "Reviewer Bot PR Comment Observer") - monkeypatch.setenv("WORKFLOW_RUN_TRIGGERING_ID", "777") - monkeypatch.setenv("WORKFLOW_RUN_TRIGGERING_ATTEMPT", "1") - monkeypatch.setenv("WORKFLOW_RUN_TRIGGERING_CONCLUSION", "success") - assert reviewer_bot.handle_workflow_run_event(state) is False - assert state["active_reviews"]["42"]["deferred_gaps"] == {} - - -def test_execute_pending_privileged_command_revalidates_live_state(monkeypatch): - state = make_state() - review = reviewer_bot.ensure_review_entry(state, 42, create=True) - assert review is not None - review["pending_privileged_commands"]["issue_comment:100"] = { - "source_event_key": "issue_comment:100", - "command_name": "accept-no-fls-changes", - "issue_number": 42, - "actor": "alice", - "status": "pending", - } - monkeypatch.setenv("MANUAL_ACTION", "execute-pending-privileged-command") - monkeypatch.setenv("PRIVILEGED_SOURCE_EVENT_KEY", "issue_comment:100") - monkeypatch.setattr( - reviewer_bot, - "get_issue_or_pr_snapshot", - lambda issue_number: {"number": issue_number, "labels": [{"name": reviewer_bot.FLS_AUDIT_LABEL}]}, - ) - monkeypatch.setattr(reviewer_bot, "check_user_permission", lambda username, required_permission="triage": True) - monkeypatch.setattr(reviewer_bot, "handle_accept_no_fls_changes_command", lambda issue_number, actor: ("ok", True)) - assert reviewer_bot.handle_manual_dispatch(state) is True - assert review["pending_privileged_commands"]["issue_comment:100"]["status"] == "executed" - - -def test_execute_pending_privileged_command_hydrates_issue_labels_for_executor(monkeypatch): - state = make_state() - review = reviewer_bot.ensure_review_entry(state, 42, create=True) - assert review is not None - review["pending_privileged_commands"]["issue_comment:100"] = { - "source_event_key": "issue_comment:100", - "command_name": "accept-no-fls-changes", - "issue_number": 42, - "actor": "alice", - "status": "pending", - } - monkeypatch.setenv("MANUAL_ACTION", "execute-pending-privileged-command") - monkeypatch.setenv("PRIVILEGED_SOURCE_EVENT_KEY", "issue_comment:100") - monkeypatch.delenv("ISSUE_LABELS", raising=False) - monkeypatch.setattr( - reviewer_bot, - "get_issue_or_pr_snapshot", - lambda issue_number: {"number": issue_number, "labels": [{"name": reviewer_bot.FLS_AUDIT_LABEL}]}, - ) - monkeypatch.setattr(reviewer_bot, "check_user_permission", lambda username, required_permission="triage": True) - - observed = {} - - def fake_handle(issue_number, actor): - observed["issue_number"] = issue_number - observed["actor"] = actor - observed["issue_labels"] = json.loads(os.environ["ISSUE_LABELS"]) - return ("ok", True) - - monkeypatch.setattr(reviewer_bot, "handle_accept_no_fls_changes_command", fake_handle) - assert reviewer_bot.handle_manual_dispatch(state) is True - assert observed == { - "issue_number": 42, - "actor": "alice", - "issue_labels": [reviewer_bot.FLS_AUDIT_LABEL], - } - assert review["pending_privileged_commands"]["issue_comment:100"]["status"] == "executed" - - -def test_execute_pending_privileged_command_fails_closed_without_live_fls_audit_label(monkeypatch): - state = make_state() - review = reviewer_bot.ensure_review_entry(state, 42, create=True) - assert review is not None - review["pending_privileged_commands"]["issue_comment:100"] = { - "source_event_key": "issue_comment:100", - "command_name": "accept-no-fls-changes", - "issue_number": 42, - "actor": "alice", - "status": "pending", - } - monkeypatch.setenv("MANUAL_ACTION", "execute-pending-privileged-command") - monkeypatch.setenv("PRIVILEGED_SOURCE_EVENT_KEY", "issue_comment:100") - monkeypatch.setattr( - reviewer_bot, - "get_issue_or_pr_snapshot", - lambda issue_number: {"number": issue_number, "labels": [{"name": "status: awaiting reviewer response"}]}, - ) - monkeypatch.setattr(reviewer_bot, "check_user_permission", lambda username, required_permission="triage": True) - called = {"handle": 0} - monkeypatch.setattr( - reviewer_bot, - "handle_accept_no_fls_changes_command", - lambda issue_number, actor: called.__setitem__("handle", called["handle"] + 1) or ("ok", True), - ) - assert reviewer_bot.handle_manual_dispatch(state) is True - assert called["handle"] == 0 - pending = review["pending_privileged_commands"]["issue_comment:100"] - assert pending["status"] == "failed_closed" - assert pending["result"] == "live_revalidation_failed" - - -def test_assign_command_posts_pr_guidance_on_success(monkeypatch): - state = make_state() - state["queue"] = [{"github": "felix91gr", "name": "Félix Fischer"}] - monkeypatch.setenv("IS_PULL_REQUEST", "true") - monkeypatch.setenv("ISSUE_AUTHOR", "PLeVasseur") - monkeypatch.setattr(reviewer_bot, "get_issue_assignees", lambda issue_number: []) - monkeypatch.setattr(reviewer_bot, "request_reviewer_assignment", lambda issue_number, username: reviewer_bot.AssignmentAttempt(success=True, status_code=201)) - posted = [] - monkeypatch.setattr(reviewer_bot, "post_comment", lambda issue_number, body: posted.append(body) or True) - response, success = reviewer_bot.handle_assign_command(state, 42, "@felix91gr") - assert success is True - assert response == "✅ @felix91gr has been assigned as reviewer." - assert posted == [reviewer_bot.get_pr_guidance("felix91gr", "PLeVasseur")] - - -def test_claim_command_posts_pr_guidance_on_success(monkeypatch): - state = make_state() - state["queue"] = [{"github": "felix91gr", "name": "Félix Fischer"}] - monkeypatch.setenv("IS_PULL_REQUEST", "true") - monkeypatch.setenv("ISSUE_AUTHOR", "PLeVasseur") - monkeypatch.setattr(reviewer_bot, "get_issue_assignees", lambda issue_number: []) - monkeypatch.setattr(reviewer_bot, "request_reviewer_assignment", lambda issue_number, username: reviewer_bot.AssignmentAttempt(success=True, status_code=201)) - posted = [] - monkeypatch.setattr(reviewer_bot, "post_comment", lambda issue_number, body: posted.append(body) or True) - response, success = reviewer_bot.handle_claim_command(state, 42, "felix91gr") - assert success is True - assert response == "✅ @felix91gr has claimed this review." - assert posted == [reviewer_bot.get_pr_guidance("felix91gr", "PLeVasseur")] - - -def test_pass_command_posts_pr_guidance_for_new_reviewer(monkeypatch): - state = make_state() - state["queue"] = [ - {"github": "alice", "name": "Alice"}, - {"github": "felix91gr", "name": "Félix Fischer"}, - ] - review = reviewer_bot.ensure_review_entry(state, 42, create=True) - assert review is not None - review["current_reviewer"] = "alice" - monkeypatch.setenv("IS_PULL_REQUEST", "true") - monkeypatch.setenv("ISSUE_AUTHOR", "PLeVasseur") - monkeypatch.setattr(reviewer_bot, "get_issue_assignees", lambda issue_number: ["alice"]) - monkeypatch.setattr(reviewer_bot, "request_reviewer_assignment", lambda issue_number, username: reviewer_bot.AssignmentAttempt(success=True, status_code=201)) - monkeypatch.setattr(reviewer_bot, "unassign_reviewer", lambda issue_number, username: True) - posted = [] - monkeypatch.setattr(reviewer_bot, "post_comment", lambda issue_number, body: posted.append(body) or True) - response, success = reviewer_bot.handle_pass_command(state, 42, "alice", None) - assert success is True - assert "@felix91gr is now assigned as the reviewer." in response - assert posted == [reviewer_bot.get_pr_guidance("felix91gr", "PLeVasseur")] - - -def test_assign_from_queue_posts_guidance_only_once(monkeypatch): - state = make_state() - state["queue"] = [{"github": "felix91gr", "name": "Félix Fischer"}] - monkeypatch.setenv("IS_PULL_REQUEST", "true") - monkeypatch.setenv("ISSUE_AUTHOR", "PLeVasseur") - monkeypatch.setattr(reviewer_bot, "get_issue_assignees", lambda issue_number: []) - monkeypatch.setattr(reviewer_bot, "request_reviewer_assignment", lambda issue_number, username: reviewer_bot.AssignmentAttempt(success=True, status_code=201)) - posted = [] - monkeypatch.setattr(reviewer_bot, "post_comment", lambda issue_number, body: posted.append(body) or True) - response, success = reviewer_bot.handle_assign_from_queue_command(state, 42) - assert success is True - assert response == "✅ @felix91gr (next in queue) has been assigned as reviewer." - assert posted == [reviewer_bot.get_pr_guidance("felix91gr", "PLeVasseur")] - - -def test_list_changed_files_ignores_untracked_bootstrap_noise(monkeypatch, tmp_path): - commands_seen = [] - - def fake_run_command(command, cwd, check=True): - commands_seen.append(command) - if command == ["git", "diff", "--name-only"]: - return subprocess.CompletedProcess(command, 0, stdout="", stderr="") - if command == ["git", "diff", "--cached", "--name-only"]: - return subprocess.CompletedProcess(command, 0, stdout="", stderr="") - raise AssertionError(f"Unexpected command: {command}") - - monkeypatch.setattr(reviewer_bot.automation_module, "run_command", fake_run_command) - assert reviewer_bot.automation_module.list_changed_files(tmp_path) == [] - assert commands_seen == [["git", "diff", "--name-only"], ["git", "diff", "--cached", "--name-only"]] - - -def test_list_changed_files_reports_tracked_changes_only(monkeypatch, tmp_path): - def fake_run_command(command, cwd, check=True): - if command == ["git", "diff", "--name-only"]: - return subprocess.CompletedProcess(command, 0, stdout="README.md\nsrc/spec.lock\n", stderr="") - if command == ["git", "diff", "--cached", "--name-only"]: - return subprocess.CompletedProcess(command, 0, stdout="src/spec.lock\n", stderr="") - raise AssertionError(f"Unexpected command: {command}") - - monkeypatch.setattr(reviewer_bot.automation_module, "run_command", fake_run_command) - assert reviewer_bot.automation_module.list_changed_files(tmp_path) == ["README.md", "src/spec.lock"] - - -def test_privileged_commands_workflow_executes_source_entrypoint(): - workflow_text = Path(".github/workflows/reviewer-bot-privileged-commands.yml").read_text(encoding="utf-8") - assert "Fetch trusted bot source tarball" in workflow_text - assert 'REVIEWER_BOT_TARGET_REPO_ROOT: ${{ github.workspace }}' in workflow_text - assert 'run: uv run --project "$BOT_SRC_ROOT" python "$BOT_SRC_ROOT/scripts/reviewer_bot.py"' in workflow_text - - -def test_accept_no_fls_changes_honors_explicit_target_repo_root(monkeypatch, tmp_path): - monkeypatch.setenv("REVIEWER_BOT_TARGET_REPO_ROOT", str(tmp_path)) - monkeypatch.setenv("IS_PULL_REQUEST", "false") - monkeypatch.setenv("ISSUE_LABELS", json.dumps([reviewer_bot.FLS_AUDIT_LABEL])) - monkeypatch.setattr(reviewer_bot, "check_user_permission", lambda username, required_permission="triage": True) - observed = {"cwd": None} - - def fake_list_changed_files(repo_root): - observed["cwd"] = repo_root - return ["README.md"] - - monkeypatch.setattr(reviewer_bot, "list_changed_files", fake_list_changed_files) - message, success = reviewer_bot.handle_accept_no_fls_changes_command(42, "alice") - assert (message, success) == ("❌ Working tree is not clean; refusing to update spec.lock.", False) - assert observed["cwd"] == tmp_path - - -def test_accept_no_fls_changes_uses_locked_nested_uv_commands(monkeypatch, tmp_path): - monkeypatch.setenv("REVIEWER_BOT_TARGET_REPO_ROOT", str(tmp_path)) - monkeypatch.setenv("IS_PULL_REQUEST", "false") - monkeypatch.setenv("ISSUE_LABELS", json.dumps([reviewer_bot.FLS_AUDIT_LABEL])) - monkeypatch.setattr(reviewer_bot, "check_user_permission", lambda username, required_permission="triage": True) - list_calls = {"count": 0} - - def fake_list_changed_files(repo_root): - list_calls["count"] += 1 - assert repo_root == tmp_path - return [] - - commands = [] - - def fake_run_command(command, cwd, check=False): - commands.append((command, cwd, check)) - return subprocess.CompletedProcess(command, 0, stdout="", stderr="") - - monkeypatch.setattr(reviewer_bot, "list_changed_files", fake_list_changed_files) - monkeypatch.setattr(reviewer_bot, "run_command", fake_run_command) - message, success = reviewer_bot.handle_accept_no_fls_changes_command(42, "alice") - assert (message, success) == ("✅ `src/spec.lock` is already up to date; no PR needed.", True) - assert list_calls["count"] == 2 - assert commands == [ - (["uv", "run", "--locked", "python", "scripts/fls_audit.py", "--summary-only", "--fail-on-impact"], tmp_path, False), - (["uv", "run", "--locked", "python", "./make.py", "--update-spec-lock-file"], tmp_path, False), - ] - - -def test_accept_no_fls_changes_surfaces_locked_uv_failure_details(monkeypatch, tmp_path): - monkeypatch.setenv("REVIEWER_BOT_TARGET_REPO_ROOT", str(tmp_path)) - monkeypatch.setenv("IS_PULL_REQUEST", "false") - monkeypatch.setenv("ISSUE_LABELS", json.dumps([reviewer_bot.FLS_AUDIT_LABEL])) - monkeypatch.setattr(reviewer_bot, "check_user_permission", lambda username, required_permission="triage": True) - monkeypatch.setattr(reviewer_bot, "list_changed_files", lambda repo_root: []) - - def fake_run_command(command, cwd, check=False): - return subprocess.CompletedProcess( - command, - 1, - stdout="", - stderr="error: lockfile at uv.lock needs to be updated, but --locked was provided", - ) - - monkeypatch.setattr(reviewer_bot, "run_command", fake_run_command) - message, success = reviewer_bot.handle_accept_no_fls_changes_command(42, "alice") - assert success is False - assert "Audit command failed." in message - assert "--locked was provided" in message - - -def test_update_spec_lock_file_mode_exits_before_build_docs(monkeypatch, tmp_path): - monkeypatch.setattr(build_cli.argparse.ArgumentParser, "parse_args", lambda self: type("Args", (), { - "clear": False, - "offline": False, - "ignore_spec_lock_diff": False, - "update_spec_lock_file": True, - "validate_urls": False, - "serve": False, - "check_links": False, - "xml": False, - "verbose": False, - "debug": False, - })()) - called = {"update": 0, "build": 0} - monkeypatch.setattr(build_cli, "update_spec_lockfile", lambda url, path: called.__setitem__("update", called["update"] + 1) or True) - monkeypatch.setattr(build_cli, "build_docs", lambda *args, **kwargs: called.__setitem__("build", called["build"] + 1)) - with pytest.raises(SystemExit) as exc_info: - build_cli.main(tmp_path) - assert exc_info.value.code == 0 - assert called == {"update": 1, "build": 0} - - -def test_observer_run_reason_mapping_and_near_miss_signature(): - signature = {"status": "waiting", "conclusion": None, "name": "approval_pending"} - assert sweeper.observer_run_reason_from_details({"status": "waiting", "conclusion": None, "name": "approval_pending"}, signature) == "awaiting_observer_approval" - assert sweeper.observer_run_reason_from_details({"status": "waiting", "conclusion": None, "name": "almost"}, signature) == "observer_state_unknown" - - -def test_negative_missing_run_requires_full_scan_and_recheck(): - gap = { - "source_event_created_at": "2026-03-15T00:00:00Z", - "full_scan_complete": True, - "later_recheck_complete": True, - "correlated_run_found": False, - "approval_pending_evidence_retained": False, - } - assert sweeper.can_mark_observer_run_missing(gap) is True - gap["later_recheck_complete"] = False - assert sweeper.can_mark_observer_run_missing(gap) is False - - -def test_stage_a_candidate_run_correlation_is_exact_to_workflow_event_pr_and_window(): - os.environ["GITHUB_REPOSITORY"] = "rustfoundation/safety-critical-rust-coding-guidelines" - result = sweeper.correlate_candidate_observer_runs( - "issue_comment:101", - source_event_kind="issue_comment:created", - source_event_created_at="2026-03-17T10:00:00Z", - pr_number=42, - workflow_file=".github/workflows/reviewer-bot-pr-comment-observer.yml", - workflow_runs=[ - { - "id": 1, - "event": "issue_comment", - "path": ".github/workflows/reviewer-bot-pr-comment-observer.yml", - "created_at": "2026-03-17T10:05:00Z", - "repository": {"full_name": "rustfoundation/safety-critical-rust-coding-guidelines"}, - "pull_requests": [{"number": 42}], - }, - { - "id": 2, - "event": "issue_comment", - "path": ".github/workflows/reviewer-bot-pr-comment-observer.yml", - "created_at": "2026-03-17T10:40:00Z", - "repository": {"full_name": "rustfoundation/safety-critical-rust-coding-guidelines"}, - "pull_requests": [{"number": 42}], - }, - ], - ) - assert result["candidate_run_ids"] == [1] - - -def test_stage_b_artifact_correlation_rejects_ambiguous_exact_matches(): - result = sweeper.correlate_run_artifacts_exact( - { - 10: [{"source_event_key": "issue_comment:101", "source_run_id": 10, "source_run_attempt": 1, "pr_number": 42}], - 11: [{"source_event_key": "issue_comment:101", "source_run_id": 11, "source_run_attempt": 1, "pr_number": 42}], - }, - "issue_comment:101", - pr_number=42, - ) - assert result["status"] == "observer_state_unknown" - assert result["reason"] == "ambiguous_exact_artifact_matches" - - -def test_evaluate_gap_state_only_emits_missing_after_negative_inference_contract(): - reason, diagnostic = sweeper.evaluate_deferred_gap_state( - { - "source_event_created_at": "2026-03-15T00:00:00Z", - "full_scan_complete": True, - "later_recheck_complete": True, - "correlated_run_found": False, - "approval_pending_evidence_retained": False, - }, - { - "status": "no_candidate_runs", - "full_scan_complete": True, - "later_recheck_complete": True, - "correlated_run": None, - }, - None, - None, - ) - assert reason == "observer_run_missing" - assert diagnostic == "negative_inference_satisfied" - - -def test_evaluate_gap_state_completed_success_without_exact_artifact_is_artifact_missing(): - reason, diagnostic = sweeper.evaluate_deferred_gap_state( - {"source_event_created_at": "2026-03-17T00:00:00Z"}, - {"status": "candidate_runs_found", "correlated_run": 10}, - {"status": "completed", "conclusion": "success"}, - {"status": "no_exact_artifact_match", "reason": "no_exact_source_event_key_match"}, - ) - assert reason == "artifact_missing" - assert diagnostic == "no_exact_source_event_key_match" - - -def test_evaluate_gap_state_completed_success_with_expired_artifact_marks_artifact_expired(): - reason, diagnostic = sweeper.evaluate_deferred_gap_state( - {"source_event_created_at": "2026-03-17T00:00:00Z"}, - {"status": "candidate_runs_found", "correlated_run": 10}, - {"status": "completed", "conclusion": "success"}, - {"status": "no_exact_artifact_match", "artifact_scan_outcomes": {10: "expired"}}, - ) - assert reason == "artifact_expired" - assert diagnostic == "prior_visibility_or_retention_proof_required" - - -def test_artifact_gap_reason_requires_prior_visibility_or_documented_retention(): - expired = { - "artifact_seen_at": "2026-03-10T00:00:00Z", - "run_created_at": "2026-03-10T00:00:00Z", - } - assert sweeper.classify_artifact_gap_reason(expired) == "artifact_expired" - missing = { - "artifact_inspection_complete": True, - "run_created_at": "2026-03-17T00:00:00Z", - } - assert sweeper.classify_artifact_gap_reason(missing) == "artifact_missing" - - -def test_discover_visible_comment_events_skips_github_actions_and_bot_comments(monkeypatch): - state = make_state() - review = reviewer_bot.ensure_review_entry(state, 42, create=True) - assert review is not None - monkeypatch.setattr( - reviewer_bot, - "github_api", - lambda method, endpoint, data=None: [ - { - "id": 100, - "created_at": "2026-03-25T10:00:00Z", - "user": {"login": "github-actions[bot]", "type": "Bot"}, - }, - { - "id": 101, - "created_at": "2026-03-25T11:00:00Z", - "user": {"login": "alice", "type": "User"}, - }, - ], - ) - discovered, complete = sweeper._discover_visible_comment_events(reviewer_bot, 42, review) - assert complete is True - assert [item["source_event_key"] for item in discovered] == ["issue_comment:101"] - - -def test_sweeper_creates_keyed_deferred_gaps_for_visible_comments_reviews_and_dismissals(monkeypatch): - state = make_state() - review = reviewer_bot.ensure_review_entry(state, 42, create=True) - assert review is not None - review["current_reviewer"] = "alice" - monkeypatch.setattr( - reviewer_bot, - "github_api", - lambda method, endpoint, data=None: { - "pulls/42": {"state": "open", "head": {"sha": "head-1"}}, - "issues/42/comments?per_page=100&page=1": [{"id": 101, "created_at": "2026-03-25T10:00:00Z"}], - }.get(endpoint), - ) - monkeypatch.setattr( - reviewer_bot, - "get_pull_request_reviews", - lambda issue_number: [ - {"id": 202, "submitted_at": "2026-03-25T11:00:00Z", "state": "APPROVED"}, - {"id": 303, "submitted_at": "2026-03-25T09:00:00Z", "updated_at": "2026-03-25T12:00:00Z", "state": "DISMISSED"}, - ], - ) - assert sweeper.sweep_deferred_gaps(reviewer_bot, state) is True - gaps = state["active_reviews"]["42"]["deferred_gaps"] - assert "issue_comment:101" in gaps - assert "pull_request_review:202" in gaps - assert "pull_request_review_dismissed:303" in gaps - assert gaps["pull_request_review_dismissed:303"]["source_workflow_file"] == ".github/workflows/reviewer-bot-pr-review-dismissed-observer.yml" - - -def test_sweeper_creates_keyed_deferred_gap_for_visible_review_comments(monkeypatch): - state = make_state() - review = reviewer_bot.ensure_review_entry(state, 42, create=True) - assert review is not None - review["current_reviewer"] = "alice" - - def fake_github_api(method, endpoint, data=None): - if endpoint == "pulls/42": - return {"state": "open", "head": {"sha": "head-1"}} - if endpoint == "issues/42/comments?per_page=100&page=1": - return [] - if endpoint == "pulls/42/comments?per_page=100": - return [{"id": 404, "created_at": "2026-03-25T10:30:00Z", "user": {"login": "dana", "type": "User"}}] - if endpoint.startswith("actions/workflows/"): - return {"workflow_runs": []} - return None - - monkeypatch.setattr(reviewer_bot, "github_api", fake_github_api) - monkeypatch.setattr(reviewer_bot, "get_pull_request_reviews", lambda issue_number: []) - assert sweeper.sweep_deferred_gaps(reviewer_bot, state) is True - gaps = state["active_reviews"]["42"]["deferred_gaps"] - assert "pull_request_review_comment:404" in gaps - assert gaps["pull_request_review_comment:404"]["source_workflow_file"] == ".github/workflows/reviewer-bot-pr-review-comment-observer.yml" - - -def test_sweeper_skips_dismissed_reviews_already_reconciled_by_source_event_key(monkeypatch): - state = make_state() - review = reviewer_bot.ensure_review_entry(state, 42, create=True) - assert review is not None - review["current_reviewer"] = "alice" - review["reconciled_source_events"] = ["pull_request_review_dismissed:303"] - monkeypatch.setattr( - reviewer_bot, - "github_api", - lambda method, endpoint, data=None: { - "pulls/42": {"state": "open", "head": {"sha": "head-1"}}, - "issues/42/comments?per_page=100&page=1": [], - }.get(endpoint), - ) - monkeypatch.setattr( - reviewer_bot, - "get_pull_request_reviews", - lambda issue_number: [ - {"id": 303, "submitted_at": "2026-03-17T09:00:00Z", "updated_at": "2026-03-17T12:00:00Z", "state": "DISMISSED"}, - ], - ) - assert sweeper.sweep_deferred_gaps(reviewer_bot, state) is False - assert state["active_reviews"]["42"]["deferred_gaps"] == {} - - -def test_sweeper_skips_events_already_reconciled_by_source_event_key(monkeypatch): - state = make_state() - review = reviewer_bot.ensure_review_entry(state, 42, create=True) - assert review is not None - review["current_reviewer"] = "alice" - review["reconciled_source_events"] = ["issue_comment:101", "pull_request_review:202"] - monkeypatch.setattr( - reviewer_bot, - "github_api", - lambda method, endpoint, data=None: { - "pulls/42": {"state": "open", "head": {"sha": "head-1"}}, - "issues/42/comments?per_page=100&page=1": [{"id": 101, "created_at": "2026-03-17T10:00:00Z"}], - }.get(endpoint), - ) - monkeypatch.setattr(reviewer_bot, "get_pull_request_reviews", lambda issue_number: [{"id": 202, "submitted_at": "2026-03-17T11:00:00Z", "state": "APPROVED"}]) - assert sweeper.sweep_deferred_gaps(reviewer_bot, state) is False - assert state["active_reviews"]["42"]["deferred_gaps"] == {} - - -def test_sweeper_fetches_single_candidate_run_detail_without_exact_artifact_match(monkeypatch): - run_correlation = { - "candidate_run_ids": [123], - "correlated_run": None, - "correlated_run_found": False, - } - monkeypatch.setattr(sweeper, "_fetch_run_detail", lambda bot, run_id: {"id": run_id, "status": "completed", "conclusion": "action_required"}) - detail = sweeper._maybe_fetch_single_candidate_run_detail(reviewer_bot, run_correlation, {"status": "no_exact_artifact_match"}) - assert detail == {"id": 123, "status": "completed", "conclusion": "action_required"} - assert run_correlation["correlated_run"] == 123 - - -def test_sweeper_visible_review_repair_refreshes_current_reviewer_activity_without_artifact(monkeypatch): - state = make_state() - review = reviewer_bot.ensure_review_entry(state, 42, create=True) - assert review is not None - review["current_reviewer"] = "alice" - review["active_cycle_started_at"] = "2026-03-17T09:00:00Z" - review["transition_warning_sent"] = "2026-03-18T00:00:00Z" - review["transition_notice_sent_at"] = "2026-03-25T00:00:00Z" - review["deferred_gaps"]["pull_request_review:202"] = {"reason": "artifact_missing"} - monkeypatch.setattr( - reviewer_bot, - "github_api", - lambda method, endpoint, data=None: {"state": "open", "head": {"sha": "head-1"}} if endpoint == "pulls/42" else {"workflow_runs": []}, - ) - monkeypatch.setattr( - reviewer_bot, - "get_pull_request_reviews", - lambda issue_number: [ - { - "id": 202, - "submitted_at": "2026-03-25T11:00:00Z", - "state": "COMMENTED", - "commit_id": "head-1", - "user": {"login": "alice"}, - } - ], - ) - assert sweeper.sweep_deferred_gaps(reviewer_bot, state) is True - assert review["last_reviewer_activity"] == "2026-03-25T11:00:00Z" - assert review["transition_warning_sent"] is None - assert review["transition_notice_sent_at"] is None - assert "pull_request_review:202" not in review["deferred_gaps"] - assert "pull_request_review:202" in review["reconciled_source_events"] - - -def test_workflow_policy_split_and_lock_only_boundaries(): - workflows_dir = Path(".github/workflows") - required = { - "reviewer-bot-issues.yml", - "reviewer-bot-issue-comment-direct.yml", - "reviewer-bot-sweeper-repair.yml", - "reviewer-bot-pr-metadata.yml", - "reviewer-bot-pr-comment-trusted.yml", - "reviewer-bot-pr-comment-observer.yml", - "reviewer-bot-pr-review-submitted-observer.yml", - "reviewer-bot-pr-review-dismissed-observer.yml", - "reviewer-bot-pr-review-comment-observer.yml", - "reviewer-bot-reconcile.yml", - "reviewer-bot-privileged-commands.yml", - } - assert required.issubset({path.name for path in workflows_dir.glob("reviewer-bot-*.yml")}) - for path in required: - data = yaml.safe_load((workflows_dir / path).read_text(encoding="utf-8")) - jobs = data.get("jobs", {}) - for job in jobs.values(): - permissions = job.get("permissions", {}) - steps = job.get("steps", []) - uses_values = [step.get("uses", "") for step in steps if isinstance(step, dict)] - text = (workflows_dir / path).read_text(encoding="utf-8") - if "observer" in path: - assert permissions.get("contents") == "read" - assert all("checkout" not in value for value in uses_values) - if permissions.get("contents") == "write" and path != "reviewer-bot-privileged-commands.yml": - assert all("checkout" not in value for value in uses_values) - assert "Temporary lock debt" in text - for value in uses_values: - if value: - assert "@" in value and len(value.split("@", 1)[1]) == 40 - - -def test_sweeper_repair_workflow_exposes_reviewer_board_preview_dispatch(): - data = yaml.safe_load(Path(".github/workflows/reviewer-bot-sweeper-repair.yml").read_text(encoding="utf-8")) - on_block = data.get("on", data.get(True)) - workflow_dispatch = on_block["workflow_dispatch"] - action_input = workflow_dispatch["inputs"]["action"] - assert "preview-reviewer-board" in action_input["options"] - issue_number_input = workflow_dispatch["inputs"]["issue_number"] - assert issue_number_input["required"] is False - assert issue_number_input["type"] == "string" - - -def test_sweeper_repair_workflow_scopes_reviewer_board_env_to_preview_only(): - workflow_text = Path(".github/workflows/reviewer-bot-sweeper-repair.yml").read_text(encoding="utf-8") - assert "ISSUE_NUMBER: ${{ github.event.inputs.issue_number }}" in workflow_text - assert ( - "REVIEWER_BOARD_ENABLED: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.action == 'preview-reviewer-board' && 'true' || 'false' }}" - in workflow_text - ) - assert ( - "REVIEWER_BOARD_TOKEN: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.action == 'preview-reviewer-board' && secrets.REVIEWER_BOARD_TOKEN || '' }}" - in workflow_text - ) - - -def test_pr_comment_observer_workflow_builds_payload_inline_without_bot_src_root(): - workflow = Path(".github/workflows/reviewer-bot-pr-comment-observer.yml").read_text(encoding="utf-8") - assert "BOT_SRC_ROOT" not in workflow - assert "build_pr_comment_observer_payload" not in workflow - assert "Fetch trusted bot source tarball" not in workflow - - -def test_workflow_summaries_and_runbook_references_exist(): - runbook = Path("docs/reviewer-bot-review-freshness-operator-runbook.md") - assert runbook.exists() - reconcile = Path(".github/workflows/reviewer-bot-reconcile.yml").read_text(encoding="utf-8") - assert "docs/reviewer-bot-review-freshness-operator-runbook.md" in reconcile - - -def test_trusted_pr_comment_workflow_preflights_same_repo_before_mutation(): - data = yaml.safe_load(Path(".github/workflows/reviewer-bot-pr-comment-trusted.yml").read_text(encoding="utf-8")) - job = data["jobs"]["reviewer-bot-pr-comment-trusted"] - steps = job["steps"] - assert steps[0]["name"] == "Decide whether same-repo trusted path applies" - assert steps[1]["if"] == "env.RUN_TRUSTED_PR_COMMENT == 'true'" - assert steps[2]["if"] == "env.RUN_TRUSTED_PR_COMMENT == 'true'" - assert steps[3]["if"] == "env.RUN_TRUSTED_PR_COMMENT == 'true'" - assert steps[4]["name"] == "Trusted path skipped" - assert steps[4]["if"] == "env.RUN_TRUSTED_PR_COMMENT != 'true'" - workflow_text = Path(".github/workflows/reviewer-bot-pr-comment-trusted.yml").read_text(encoding="utf-8") - assert "https://api.github.com/repos/{repo}/pulls/{pr_number}" in workflow_text - assert "RUN_TRUSTED_PR_COMMENT" in workflow_text - - -def test_pr_comment_observer_workflow_uses_inline_payload_builder(): - data = yaml.safe_load(Path(".github/workflows/reviewer-bot-pr-comment-observer.yml").read_text(encoding="utf-8")) - job = data["jobs"]["observer"] - steps = job["steps"] - assert steps[0]["name"] == "Build deferred comment artifact" - assert steps[1]["name"] == "Upload deferred comment artifact" - workflow_text = Path(".github/workflows/reviewer-bot-pr-comment-observer.yml").read_text(encoding="utf-8") - assert "build_pr_comment_observer_payload" not in workflow_text - assert 'uv run --project "$BOT_SRC_ROOT"' not in workflow_text - - -def test_review_comment_observer_workflow_exists_and_is_read_only(): - data = yaml.safe_load(Path(".github/workflows/reviewer-bot-pr-review-comment-observer.yml").read_text(encoding="utf-8")) - on_block = data.get("on", data.get(True)) - assert on_block["pull_request_review_comment"]["types"] == ["created"] - job = data["jobs"]["observer"] - assert job["permissions"]["contents"] == "read" - steps = job["steps"] - assert steps[0]["name"] == "Build deferred review comment artifact" - assert steps[1]["name"] == "Upload deferred review comment artifact" - workflow_text = Path(".github/workflows/reviewer-bot-pr-review-comment-observer.yml").read_text(encoding="utf-8") - assert "checkout" not in workflow_text - assert "pull_request_review_comment" in workflow_text - - -def test_build_pr_comment_observer_payload_marks_trusted_direct_same_repo_as_observer_noop(monkeypatch): - monkeypatch.setenv("GITHUB_REPOSITORY", "rustfoundation/safety-critical-rust-coding-guidelines") - monkeypatch.setenv("COMMENT_USER_TYPE", "User") - monkeypatch.setenv("COMMENT_AUTHOR", "PLeVasseur") - monkeypatch.setenv("COMMENT_AUTHOR_ASSOCIATION", "COLLABORATOR") - monkeypatch.setenv("COMMENT_SENDER_TYPE", "User") - monkeypatch.setenv("COMMENT_INSTALLATION_ID", "") - monkeypatch.setenv("COMMENT_PERFORMED_VIA_GITHUB_APP", "false") - monkeypatch.setenv("COMMENT_BODY", "@guidelines-bot /r? @felix91gr") - monkeypatch.setenv("COMMENT_ID", "100") - monkeypatch.setenv("COMMENT_AUTHOR_ID", "123") - monkeypatch.setenv("COMMENT_CREATED_AT", "2026-03-20T20:48:25Z") - monkeypatch.setenv("GITHUB_RUN_ID", "999") - monkeypatch.setenv("GITHUB_RUN_ATTEMPT", "1") - monkeypatch.setattr( - reviewer_bot, - "github_api", - lambda method, endpoint, data=None: { - "head": {"repo": {"full_name": "rustfoundation/safety-critical-rust-coding-guidelines"}}, - "user": {"login": "PLeVasseur"}, - }, - ) - payload = reviewer_bot.build_pr_comment_observer_payload(42) - assert payload["kind"] == "observer_noop" - assert payload["reason"] == "trusted_direct_same_repo_human_comment" - assert payload["source_event_key"] == "issue_comment:100" - - -def test_issue_comment_direct_workflow_exports_issue_state(): - workflow_text = Path(".github/workflows/reviewer-bot-issue-comment-direct.yml").read_text(encoding="utf-8") - assert "ISSUE_STATE: ${{ github.event.issue.state }}" in workflow_text - - -def test_mutating_reviewer_bot_workflows_do_not_share_global_github_concurrency(): - workflow_paths = [ - ".github/workflows/reviewer-bot-issues.yml", - ".github/workflows/reviewer-bot-issue-comment-direct.yml", - ".github/workflows/reviewer-bot-sweeper-repair.yml", - ".github/workflows/reviewer-bot-pr-metadata.yml", - ".github/workflows/reviewer-bot-pr-comment-trusted.yml", - ".github/workflows/reviewer-bot-reconcile.yml", - ".github/workflows/reviewer-bot-privileged-commands.yml", - ] - for workflow_path in workflow_paths: - data = yaml.safe_load(Path(workflow_path).read_text(encoding="utf-8")) - for job in data.get("jobs", {}).values(): - assert "concurrency" not in job - - -def test_classify_event_intent_treats_supported_workflow_run_sources_as_mutating(monkeypatch): - monkeypatch.setenv("WORKFLOW_RUN_EVENT", "issue_comment") - assert reviewer_bot.classify_event_intent("workflow_run", "completed") == reviewer_bot.EVENT_INTENT_MUTATING - monkeypatch.setenv("WORKFLOW_RUN_EVENT", "pull_request_review_comment") - assert reviewer_bot.classify_event_intent("workflow_run", "completed") == reviewer_bot.EVENT_INTENT_MUTATING - - -def test_main_records_repair_needed_when_projection_fails(monkeypatch, tmp_path): - state = make_state() - review = reviewer_bot.ensure_review_entry(state, 42, create=True) - assert review is not None - review["current_reviewer"] = "alice" - monkeypatch.setenv("EVENT_NAME", "issue_comment") - monkeypatch.setenv("EVENT_ACTION", "created") - monkeypatch.setenv("IS_PULL_REQUEST", "false") - monkeypatch.setenv("ISSUE_NUMBER", "42") - monkeypatch.setenv("ISSUE_AUTHOR", "dana") - monkeypatch.setenv("COMMENT_USER_TYPE", "User") - monkeypatch.setenv("COMMENT_AUTHOR", "dana") - monkeypatch.setenv("COMMENT_ID", "100") - monkeypatch.setenv("COMMENT_CREATED_AT", "2026-03-17T10:00:00Z") - monkeypatch.setenv("COMMENT_BODY", "plain text") - monkeypatch.setattr(reviewer_bot, "acquire_state_issue_lease_lock", lambda: None) - monkeypatch.setattr(reviewer_bot, "release_state_issue_lease_lock", lambda: True) - saved_states = [] - - def fake_load_state(*, fail_on_unavailable=False): - return json.loads(json.dumps(state)) - - def fake_save_state(updated_state): - saved_states.append(json.loads(json.dumps(updated_state))) - state.clear() - state.update(json.loads(json.dumps(updated_state))) - return True - - monkeypatch.setattr(reviewer_bot, "load_state", fake_load_state) - monkeypatch.setattr(reviewer_bot, "save_state", fake_save_state) - monkeypatch.setattr(reviewer_bot, "process_pass_until_expirations", lambda current_state: (current_state, [])) - monkeypatch.setattr(reviewer_bot, "sync_members_with_queue", lambda current_state: (current_state, [])) - monkeypatch.setattr(reviewer_bot, "get_issue_or_pr_snapshot", lambda issue_number: {"number": issue_number, "state": "open", "labels": [], "pull_request": None}) - monkeypatch.setattr(reviewer_bot, "sync_status_labels_for_items", lambda current_state, issue_numbers: (_ for _ in ()).throw(RuntimeError("projection failed"))) - output_path = tmp_path / "github-output.txt" - monkeypatch.setenv("GITHUB_OUTPUT", str(output_path)) - reviewer_bot.app_module.main(reviewer_bot) - assert state["active_reviews"]["42"]["repair_needed"]["kind"] == "projection_failure" - assert len(saved_states) >= 2 diff --git a/.github/workflows/reviewer-bot-issue-comment-direct.yml b/.github/workflows/reviewer-bot-issue-comment-direct.yml index b999d468e..2e389a7b1 100644 --- a/.github/workflows/reviewer-bot-issue-comment-direct.yml +++ b/.github/workflows/reviewer-bot-issue-comment-direct.yml @@ -61,7 +61,6 @@ jobs: COMMENT_ID: ${{ github.event.comment.id }} COMMENT_CREATED_AT: ${{ github.event.comment.created_at }} COMMENT_USER_TYPE: ${{ github.event.comment.user.type }} - COMMENT_AUTHOR_ASSOCIATION: ${{ github.event.comment.author_association }} COMMENT_SENDER_TYPE: ${{ github.event.sender.type }} COMMENT_INSTALLATION_ID: ${{ github.event.installation.id }} COMMENT_PERFORMED_VIA_GITHUB_APP: ${{ github.event.comment.performed_via_github_app != null }} @@ -72,5 +71,4 @@ jobs: WORKFLOW_RUN_ID: ${{ github.run_id }} WORKFLOW_NAME: ${{ github.workflow }} WORKFLOW_JOB_NAME: ${{ github.job }} - CURRENT_WORKFLOW_FILE: .github/workflows/reviewer-bot-issue-comment-direct.yml run: uv run --project "$BOT_SRC_ROOT" reviewer-bot diff --git a/.github/workflows/reviewer-bot-issues.yml b/.github/workflows/reviewer-bot-issues.yml index 5067c7f86..c8600f4bb 100644 --- a/.github/workflows/reviewer-bot-issues.yml +++ b/.github/workflows/reviewer-bot-issues.yml @@ -53,6 +53,7 @@ jobs: ISSUE_TITLE: ${{ github.event.issue.title }} ISSUE_BODY: ${{ github.event.issue.body }} ISSUE_UPDATED_AT: ${{ github.event.issue.updated_at }} + EVENT_CREATED_AT: ${{ github.event.issue.updated_at }} ISSUE_AUTHOR: ${{ github.event.issue.user.login }} ISSUE_HTML_URL: ${{ github.event.issue.html_url }} ISSUE_LABELS: ${{ toJson(github.event.issue.labels.*.name) }} @@ -67,5 +68,4 @@ jobs: WORKFLOW_RUN_ID: ${{ github.run_id }} WORKFLOW_NAME: ${{ github.workflow }} WORKFLOW_JOB_NAME: ${{ github.job }} - CURRENT_WORKFLOW_FILE: .github/workflows/reviewer-bot-issues.yml run: uv run --project "$BOT_SRC_ROOT" reviewer-bot diff --git a/.github/workflows/reviewer-bot-pr-comment-observer.yml b/.github/workflows/reviewer-bot-pr-comment-observer.yml deleted file mode 100644 index 3e75ea75e..000000000 --- a/.github/workflows/reviewer-bot-pr-comment-observer.yml +++ /dev/null @@ -1,90 +0,0 @@ -name: Reviewer Bot PR Comment Observer - -on: - issue_comment: - types: [created] - -permissions: - contents: read - -jobs: - observer: - if: ${{ github.event.issue.pull_request != null }} - runs-on: ubuntu-latest - permissions: - contents: read - steps: - - name: Build deferred comment artifact - env: - PAYLOAD_PATH: ${{ runner.temp }}/deferred-comment.json - COMMENT_BODY: ${{ github.event.comment.body }} - PR_NUMBER: ${{ github.event.issue.number }} - COMMENT_ID: ${{ github.event.comment.id }} - COMMENT_CREATED_AT: ${{ github.event.comment.created_at }} - COMMENT_AUTHOR: ${{ github.event.comment.user.login }} - COMMENT_AUTHOR_ID: ${{ github.event.comment.user.id }} - COMMENT_USER_TYPE: ${{ github.event.comment.user.type }} - COMMENT_AUTHOR_ASSOCIATION: ${{ github.event.comment.author_association }} - COMMENT_SENDER_TYPE: ${{ github.event.sender.type }} - COMMENT_INSTALLATION_ID: ${{ github.event.installation.id }} - COMMENT_PERFORMED_VIA_GITHUB_APP: ${{ github.event.comment.performed_via_github_app != null }} - run: | - python - <<'PY' - import hashlib - import json - import os - - body = os.environ['COMMENT_BODY'] - normalized = "\n".join(line.rstrip() for line in body.replace("\r\n", "\n").split("\n")).strip() - has_non_command_text = any( - line.strip() and not line.strip().startswith('@guidelines-bot /') - for line in normalized.split("\n") - ) - command_lines = [line for line in normalized.split("\n") if line.strip().startswith('@guidelines-bot /')] - if len(command_lines) == 0: - comment_class = 'plain_text' - elif len(command_lines) == 1 and has_non_command_text: - comment_class = 'command_plus_text' - elif len(command_lines) == 1: - comment_class = 'command_only' - else: - comment_class = 'command_plus_text' - actor_class = 'repo_user_principal' - user_type = os.environ.get('COMMENT_USER_TYPE', '') - author = os.environ.get('COMMENT_AUTHOR', '') - if user_type == 'Bot' or author.endswith('[bot]'): - actor_class = 'bot_account' - elif os.environ.get('COMMENT_INSTALLATION_ID', '').strip(): - actor_class = 'github_app_or_other_automation' - elif not user_type: - actor_class = 'unknown_actor' - payload = { - 'schema_version': 2, - 'source_workflow_name': 'Reviewer Bot PR Comment Observer', - 'source_workflow_file': '.github/workflows/reviewer-bot-pr-comment-observer.yml', - 'source_run_id': int(os.environ['GITHUB_RUN_ID']), - 'source_run_attempt': int(os.environ['GITHUB_RUN_ATTEMPT']), - 'source_event_name': 'issue_comment', - 'source_event_action': 'created', - 'source_event_key': f"issue_comment:{os.environ['COMMENT_ID']}", - 'pr_number': int(os.environ['PR_NUMBER']), - 'comment_id': int(os.environ['COMMENT_ID']), - 'comment_class': comment_class, - 'has_non_command_text': has_non_command_text, - 'source_body_digest': hashlib.sha256(normalized.encode('utf-8')).hexdigest(), - 'source_created_at': os.environ['COMMENT_CREATED_AT'], - 'actor_login': author, - 'actor_id': int(os.environ['COMMENT_AUTHOR_ID']), - 'actor_class': actor_class, - 'source_artifact_name': f"reviewer-bot-comment-context-{os.environ['GITHUB_RUN_ID']}-attempt-{os.environ['GITHUB_RUN_ATTEMPT']}", - } - with open(os.environ['PAYLOAD_PATH'], 'w', encoding='utf-8') as handle: - json.dump(payload, handle) - PY - - name: Upload deferred comment artifact - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 - with: - name: reviewer-bot-comment-context-${{ github.run_id }}-attempt-${{ github.run_attempt }} - path: ${{ runner.temp }}/deferred-comment.json - retention-days: 7 - if-no-files-found: ignore diff --git a/.github/workflows/reviewer-bot-pr-comment-router.yml b/.github/workflows/reviewer-bot-pr-comment-router.yml new file mode 100644 index 000000000..06e3b15fa --- /dev/null +++ b/.github/workflows/reviewer-bot-pr-comment-router.yml @@ -0,0 +1,202 @@ +name: Reviewer Bot PR Comment Router + +on: + issue_comment: + types: [created] + +permissions: + contents: read + +env: + STATE_ISSUE_NUMBER: '314' + +jobs: + route-pr-comment: + if: ${{ github.event.issue.pull_request != null }} + runs-on: ubuntu-latest + permissions: + contents: read + actions: read + outputs: + route_outcome: ${{ steps.route.outputs.route_outcome }} + pr_head_full_name: ${{ steps.route.outputs.pr_head_full_name }} + pr_author: ${{ steps.route.outputs.pr_author }} + issue_state: ${{ steps.route.outputs.issue_state }} + issue_labels: ${{ steps.route.outputs.issue_labels }} + comment_author_id: ${{ steps.route.outputs.comment_author_id }} + reviewer_bot_trust_class: ${{ steps.route.outputs.reviewer_bot_trust_class }} + steps: + - name: Route PR comment + id: route + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PAYLOAD_PATH: ${{ runner.temp }}/deferred-comment.json + run: | + python - <<'PY' + import json + import os + import urllib.error + import urllib.request + from pathlib import Path + + event_path = Path(os.environ['GITHUB_EVENT_PATH']) + event = json.loads(event_path.read_text(encoding='utf-8')) + repo = os.environ['GITHUB_REPOSITORY'] + issue = event['issue'] + comment = event['comment'] + sender = event.get('sender') or {} + installation = event.get('installation') or {} + labels = [str(item.get('name') or '') for item in issue.get('labels') or []] + issue_labels_json = json.dumps([label for label in labels if label]) + issue_state = str(issue.get('state') or '').strip() + comment_author = str((comment.get('user') or {}).get('login') or '').strip() + comment_user_type = str((comment.get('user') or {}).get('type') or '').strip() + comment_sender_type = str(sender.get('type') or '').strip() + comment_author_association = str(comment.get('author_association') or '').strip() + performed_via_github_app = bool(comment.get('performed_via_github_app')) + installation_id = installation.get('id') + route_outcome = 'trusted_direct' + pr_head_full_name = '' + pr_author = '' + + if ( + comment_user_type != 'User' + or comment_author.endswith('[bot]') + or performed_via_github_app + or str(installation_id or '').strip() + or not comment_author + ): + route_outcome = 'safe_noop' + else: + pr_number = issue['number'] + req = urllib.request.Request( + f"https://api.github.com/repos/{repo}/pulls/{pr_number}", + headers={ + 'Authorization': f"Bearer {os.environ['GITHUB_TOKEN']}", + 'Accept': 'application/vnd.github+json', + }, + ) + try: + with urllib.request.urlopen(req) as response: + pull_request = json.load(response) + except urllib.error.URLError: + route_outcome = 'deferred_reconcile' + else: + head_repo = pull_request.get('head', {}).get('repo') or {} + pr_head_full_name = str(head_repo.get('full_name') or '').strip() + pr_author = str((pull_request.get('user') or {}).get('login') or '').strip() + if ( + not pr_head_full_name + or not pr_author + or pr_head_full_name != repo + or pr_author == 'dependabot[bot]' + or comment_author_association not in {'OWNER', 'MEMBER', 'COLLABORATOR'} + ): + route_outcome = 'deferred_reconcile' + + with open(os.environ['GITHUB_OUTPUT'], 'a', encoding='utf-8') as handle: + print(f'route_outcome={route_outcome}', file=handle) + print(f'pr_head_full_name={pr_head_full_name}', file=handle) + print(f'pr_author={pr_author}', file=handle) + print(f'issue_state={issue_state}', file=handle) + print(f'issue_labels={issue_labels_json}', file=handle) + print(f'comment_author_id={int((comment.get("user") or {}).get("id") or 0)}', file=handle) + print('reviewer_bot_trust_class=pr_trusted_direct', file=handle) + + if route_outcome == 'deferred_reconcile': + payload = { + 'payload_kind': 'deferred_comment', + 'schema_version': 3, + 'source_workflow_name': 'Reviewer Bot PR Comment Router', + 'source_workflow_file': '.github/workflows/reviewer-bot-pr-comment-router.yml', + 'source_run_id': int(os.environ['GITHUB_RUN_ID']), + 'source_run_attempt': int(os.environ['GITHUB_RUN_ATTEMPT']), + 'source_event_name': 'issue_comment', + 'source_event_action': 'created', + 'source_event_key': f"issue_comment:{comment['id']}", + 'pr_number': int(issue['number']), + 'comment_id': int(comment['id']), + 'comment_body': str(comment.get('body') or ''), + 'comment_created_at': str(comment.get('created_at') or ''), + 'comment_author': comment_author, + 'comment_author_id': int((comment.get('user') or {}).get('id') or 0), + 'comment_user_type': comment_user_type, + 'comment_sender_type': comment_sender_type, + 'comment_installation_id': str(installation_id) if installation_id is not None else None, + 'comment_performed_via_github_app': performed_via_github_app, + 'issue_author': str((issue.get('user') or {}).get('login') or ''), + 'issue_state': issue_state, + 'issue_labels': [label for label in labels if label], + } + Path(os.environ['PAYLOAD_PATH']).write_text(json.dumps(payload), encoding='utf-8') + PY + - name: Upload deferred comment artifact + if: ${{ steps.route.outputs.route_outcome == 'deferred_reconcile' }} + uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 + with: + name: reviewer-bot-comment-context-${{ github.run_id }}-attempt-${{ github.run_attempt }} + path: ${{ runner.temp }}/deferred-comment.json + retention-days: 7 + if-no-files-found: error + + trusted-direct: + if: ${{ needs.route-pr-comment.outputs.route_outcome == 'trusted_direct' }} + needs: [route-pr-comment] + runs-on: ubuntu-latest + permissions: + contents: write + issues: write + pull-requests: write + actions: read + steps: + - name: Install uv + run: python -m pip install uv + - name: Fetch trusted bot source tarball + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + python - <<'PY' + import io, os, tarfile, urllib.request + from pathlib import Path + req = urllib.request.Request( + f"https://api.github.com/repos/{os.environ['GITHUB_REPOSITORY']}/tarball/{os.environ['GITHUB_SHA']}", + headers={'Authorization': f"Bearer {os.environ['GITHUB_TOKEN']}", 'Accept': 'application/vnd.github+json'}, + ) + target = Path(os.environ['RUNNER_TEMP']) / 'reviewer-bot-src' + target.mkdir(parents=True, exist_ok=True) + with urllib.request.urlopen(req) as response: + data = response.read() + with tarfile.open(fileobj=io.BytesIO(data), mode='r:gz') as archive: + archive.extractall(target) + roots = list(target.iterdir()) + print(f'BOT_SRC_ROOT={roots[0]}', file=open(os.environ['GITHUB_ENV'], 'a', encoding='utf-8')) + PY + - name: Run reviewer bot + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + EVENT_NAME: issue_comment + EVENT_ACTION: created + ISSUE_NUMBER: ${{ github.event.issue.number }} + ISSUE_AUTHOR: ${{ github.event.issue.user.login }} + ISSUE_STATE: ${{ needs.route-pr-comment.outputs.issue_state }} + ISSUE_LABELS: ${{ needs.route-pr-comment.outputs.issue_labels }} + IS_PULL_REQUEST: 'true' + COMMENT_BODY: ${{ github.event.comment.body }} + COMMENT_AUTHOR: ${{ github.event.comment.user.login }} + COMMENT_AUTHOR_ID: ${{ needs.route-pr-comment.outputs.comment_author_id }} + COMMENT_ID: ${{ github.event.comment.id }} + COMMENT_CREATED_AT: ${{ github.event.comment.created_at }} + COMMENT_USER_TYPE: ${{ github.event.comment.user.type }} + COMMENT_SENDER_TYPE: ${{ github.event.sender.type }} + COMMENT_INSTALLATION_ID: ${{ github.event.installation.id }} + COMMENT_PERFORMED_VIA_GITHUB_APP: ${{ github.event.comment.performed_via_github_app != null }} + GITHUB_REPOSITORY: ${{ github.repository }} + PR_HEAD_FULL_NAME: ${{ needs.route-pr-comment.outputs.pr_head_full_name }} + PR_AUTHOR: ${{ needs.route-pr-comment.outputs.pr_author }} + REVIEWER_BOT_ROUTE_OUTCOME: ${{ needs.route-pr-comment.outputs.route_outcome }} + REVIEWER_BOT_TRUST_CLASS: ${{ needs.route-pr-comment.outputs.reviewer_bot_trust_class }} + GITHUB_RUN_ID: ${{ github.run_id }} + GITHUB_RUN_ATTEMPT: ${{ github.run_attempt }} + WORKFLOW_NAME: ${{ github.workflow }} + WORKFLOW_JOB_NAME: ${{ github.job }} + run: uv run --project "$BOT_SRC_ROOT" reviewer-bot diff --git a/.github/workflows/reviewer-bot-pr-comment-trusted.yml b/.github/workflows/reviewer-bot-pr-comment-trusted.yml deleted file mode 100644 index 077ee2b83..000000000 --- a/.github/workflows/reviewer-bot-pr-comment-trusted.yml +++ /dev/null @@ -1,120 +0,0 @@ -name: Reviewer Bot PR Comment Trusted - -on: - issue_comment: - types: [created] - -permissions: - contents: read - -env: - STATE_ISSUE_NUMBER: '314' - -jobs: - reviewer-bot-pr-comment-trusted: - if: > - github.event.issue.pull_request != null && - github.event.comment.user.type == 'User' && - contains('OWNER,MEMBER,COLLABORATOR', github.event.comment.author_association) - runs-on: ubuntu-latest - permissions: - # Temporary lock debt: contents:write is allowed only for the existing lock-ref API operations. - contents: write - issues: write - pull-requests: write - actions: read - steps: - - name: Decide whether same-repo trusted path applies - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - PR_NUMBER: ${{ github.event.issue.number }} - run: | - python - <<'PY' - import json - import os - import urllib.request - - pr_number = os.environ['PR_NUMBER'] - repo = os.environ['GITHUB_REPOSITORY'] - req = urllib.request.Request( - f"https://api.github.com/repos/{repo}/pulls/{pr_number}", - headers={ - 'Authorization': f"Bearer {os.environ['GITHUB_TOKEN']}", - 'Accept': 'application/vnd.github+json', - }, - ) - with urllib.request.urlopen(req) as response: - pull_request = json.load(response) - - head_repo = pull_request.get('head', {}).get('repo') or {} - head_full_name = str(head_repo.get('full_name') or '').strip() - pr_author = str(pull_request.get('user', {}).get('login') or '').strip() - is_same_repo = head_full_name == repo - is_dependabot_restricted = pr_author == 'dependabot[bot]' - run_trusted_path = is_same_repo and not is_dependabot_restricted - with open(os.environ['GITHUB_ENV'], 'a', encoding='utf-8') as handle: - print(f"RUN_TRUSTED_PR_COMMENT={str(run_trusted_path).lower()}", file=handle) - if run_trusted_path: - print(f"Trusted same-repo PR comment path enabled for PR #{pr_number}.") - else: - reason = 'cross-repo' if not is_same_repo else 'restricted-author' - print( - f"Skipping trusted same-repo PR comment path for PR #{pr_number}: {reason}." - ) - PY - - name: Install uv - if: env.RUN_TRUSTED_PR_COMMENT == 'true' - run: python -m pip install uv - - name: Fetch trusted bot source tarball - if: env.RUN_TRUSTED_PR_COMMENT == 'true' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - python - <<'PY' - import io, os, tarfile, urllib.request - from pathlib import Path - req = urllib.request.Request( - f"https://api.github.com/repos/{os.environ['GITHUB_REPOSITORY']}/tarball/{os.environ['GITHUB_SHA']}", - headers={'Authorization': f"Bearer {os.environ['GITHUB_TOKEN']}", 'Accept': 'application/vnd.github+json'}, - ) - target = Path(os.environ['RUNNER_TEMP']) / 'reviewer-bot-src' - target.mkdir(parents=True, exist_ok=True) - with urllib.request.urlopen(req) as response: - data = response.read() - with tarfile.open(fileobj=io.BytesIO(data), mode='r:gz') as archive: - archive.extractall(target) - roots = list(target.iterdir()) - print(f'BOT_SRC_ROOT={roots[0]}', file=open(os.environ['GITHUB_ENV'], 'a', encoding='utf-8')) - PY - - name: Run reviewer bot - if: env.RUN_TRUSTED_PR_COMMENT == 'true' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - EVENT_NAME: issue_comment - EVENT_ACTION: created - ISSUE_NUMBER: ${{ github.event.issue.number }} - ISSUE_AUTHOR: ${{ github.event.issue.user.login }} - IS_PULL_REQUEST: 'true' - COMMENT_BODY: ${{ github.event.comment.body }} - COMMENT_AUTHOR: ${{ github.event.comment.user.login }} - COMMENT_ID: ${{ github.event.comment.id }} - COMMENT_CREATED_AT: ${{ github.event.comment.created_at }} - COMMENT_USER_TYPE: ${{ github.event.comment.user.type }} - COMMENT_AUTHOR_ASSOCIATION: ${{ github.event.comment.author_association }} - COMMENT_SENDER_TYPE: ${{ github.event.sender.type }} - COMMENT_INSTALLATION_ID: ${{ github.event.installation.id }} - COMMENT_PERFORMED_VIA_GITHUB_APP: ${{ github.event.comment.performed_via_github_app != null }} - REPO_OWNER: ${{ github.repository_owner }} - REPO_NAME: ${{ github.event.repository.name }} - GITHUB_REPOSITORY: ${{ github.repository }} - GITHUB_REF: ${{ github.ref }} - WORKFLOW_RUN_ID: ${{ github.run_id }} - WORKFLOW_NAME: ${{ github.workflow }} - WORKFLOW_JOB_NAME: ${{ github.job }} - CURRENT_WORKFLOW_FILE: .github/workflows/reviewer-bot-pr-comment-trusted.yml - REVIEWER_BOT_TRUST_CLASS: pr_trusted_direct - run: uv run --project "$BOT_SRC_ROOT" reviewer-bot - - name: Trusted path skipped - if: env.RUN_TRUSTED_PR_COMMENT != 'true' - run: | - echo 'Trusted same-repo PR comment workflow skipped after PR metadata preflight.' diff --git a/.github/workflows/reviewer-bot-pr-metadata.yml b/.github/workflows/reviewer-bot-pr-metadata.yml index bd672fb58..6386af45d 100644 --- a/.github/workflows/reviewer-bot-pr-metadata.yml +++ b/.github/workflows/reviewer-bot-pr-metadata.yml @@ -52,11 +52,11 @@ jobs: ISSUE_LABELS: ${{ toJson(github.event.pull_request.labels.*.name) }} IS_PULL_REQUEST: 'true' PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }} + EVENT_CREATED_AT: ${{ github.event.pull_request.updated_at }} REPO_OWNER: ${{ github.repository_owner }} REPO_NAME: ${{ github.event.repository.name }} GITHUB_REPOSITORY: ${{ github.repository }} WORKFLOW_RUN_ID: ${{ github.run_id }} WORKFLOW_NAME: ${{ github.workflow }} WORKFLOW_JOB_NAME: ${{ github.job }} - CURRENT_WORKFLOW_FILE: .github/workflows/reviewer-bot-pr-metadata.yml run: uv run --project "$BOT_SRC_ROOT" reviewer-bot diff --git a/.github/workflows/reviewer-bot-pr-review-comment-observer.yml b/.github/workflows/reviewer-bot-pr-review-comment-observer.yml index 5d547fc18..9ebe8d203 100644 --- a/.github/workflows/reviewer-bot-pr-review-comment-observer.yml +++ b/.github/workflows/reviewer-bot-pr-review-comment-observer.yml @@ -18,49 +18,26 @@ jobs: PAYLOAD_PATH: ${{ runner.temp }}/deferred-review-comment.json COMMENT_BODY: ${{ github.event.comment.body }} PR_NUMBER: ${{ github.event.pull_request.number }} + ISSUE_AUTHOR: ${{ github.event.pull_request.user.login }} + ISSUE_STATE: ${{ github.event.pull_request.state }} + ISSUE_LABELS: ${{ toJson(github.event.pull_request.labels.*.name) }} COMMENT_ID: ${{ github.event.comment.id }} COMMENT_CREATED_AT: ${{ github.event.comment.created_at }} COMMENT_AUTHOR: ${{ github.event.comment.user.login }} COMMENT_AUTHOR_ID: ${{ github.event.comment.user.id }} COMMENT_USER_TYPE: ${{ github.event.comment.user.type }} - COMMENT_AUTHOR_ASSOCIATION: ${{ github.event.comment.author_association }} COMMENT_SENDER_TYPE: ${{ github.event.sender.type }} COMMENT_INSTALLATION_ID: ${{ github.event.installation.id }} COMMENT_PERFORMED_VIA_GITHUB_APP: ${{ github.event.comment.performed_via_github_app != null }} - REVIEW_ID: ${{ github.event.comment.pull_request_review_id }} - IN_REPLY_TO_ID: ${{ github.event.comment.in_reply_to_id }} run: | python - <<'PY' - import hashlib import json import os - body = os.environ['COMMENT_BODY'] - normalized = "\n".join(line.rstrip() for line in body.replace("\r\n", "\n").split("\n")).strip() - has_non_command_text = any( - line.strip() and not line.strip().startswith('@guidelines-bot /') - for line in normalized.split("\n") - ) - command_lines = [line for line in normalized.split("\n") if line.strip().startswith('@guidelines-bot /')] - if len(command_lines) == 0: - comment_class = 'plain_text' - elif len(command_lines) == 1 and has_non_command_text: - comment_class = 'command_plus_text' - elif len(command_lines) == 1: - comment_class = 'command_only' - else: - comment_class = 'command_plus_text' - actor_class = 'repo_user_principal' - user_type = os.environ.get('COMMENT_USER_TYPE', '') - author = os.environ.get('COMMENT_AUTHOR', '') - if user_type == 'Bot' or author.endswith('[bot]'): - actor_class = 'bot_account' - elif os.environ.get('COMMENT_INSTALLATION_ID', '').strip(): - actor_class = 'github_app_or_other_automation' - elif not user_type: - actor_class = 'unknown_actor' + issue_labels = json.loads(os.environ.get('ISSUE_LABELS', '[]') or '[]') payload = { - 'schema_version': 2, + 'payload_kind': 'deferred_review_comment', + 'schema_version': 3, 'source_workflow_name': 'Reviewer Bot PR Review Comment Observer', 'source_workflow_file': '.github/workflows/reviewer-bot-pr-review-comment-observer.yml', 'source_run_id': int(os.environ['GITHUB_RUN_ID']), @@ -70,16 +47,17 @@ jobs: 'source_event_key': f"pull_request_review_comment:{os.environ['COMMENT_ID']}", 'pr_number': int(os.environ['PR_NUMBER']), 'comment_id': int(os.environ['COMMENT_ID']), - 'comment_class': comment_class, - 'has_non_command_text': has_non_command_text, - 'source_body_digest': hashlib.sha256(normalized.encode('utf-8')).hexdigest(), - 'source_created_at': os.environ['COMMENT_CREATED_AT'], - 'actor_login': author, - 'actor_id': int(os.environ['COMMENT_AUTHOR_ID']), - 'actor_class': actor_class, - 'pull_request_review_id': int(os.environ['REVIEW_ID']) if os.environ.get('REVIEW_ID', '').strip() else None, - 'in_reply_to_id': int(os.environ['IN_REPLY_TO_ID']) if os.environ.get('IN_REPLY_TO_ID', '').strip() else None, - 'source_artifact_name': f"reviewer-bot-review-comment-context-{os.environ['GITHUB_RUN_ID']}-attempt-{os.environ['GITHUB_RUN_ATTEMPT']}", + 'comment_body': os.environ['COMMENT_BODY'], + 'comment_created_at': os.environ['COMMENT_CREATED_AT'], + 'comment_author': os.environ['COMMENT_AUTHOR'], + 'comment_author_id': int(os.environ['COMMENT_AUTHOR_ID']), + 'comment_user_type': os.environ['COMMENT_USER_TYPE'], + 'comment_sender_type': os.environ['COMMENT_SENDER_TYPE'], + 'comment_installation_id': os.environ.get('COMMENT_INSTALLATION_ID', '').strip() or None, + 'comment_performed_via_github_app': os.environ['COMMENT_PERFORMED_VIA_GITHUB_APP'] == 'true', + 'issue_author': os.environ['ISSUE_AUTHOR'], + 'issue_state': os.environ['ISSUE_STATE'], + 'issue_labels': [str(label) for label in issue_labels if str(label)], } with open(os.environ['PAYLOAD_PATH'], 'w', encoding='utf-8') as handle: json.dump(payload, handle) diff --git a/.github/workflows/reviewer-bot-pr-review-dismissed-observer.yml b/.github/workflows/reviewer-bot-pr-review-dismissed-observer.yml index 30bcb6865..5af241d48 100644 --- a/.github/workflows/reviewer-bot-pr-review-dismissed-observer.yml +++ b/.github/workflows/reviewer-bot-pr-review-dismissed-observer.yml @@ -25,7 +25,8 @@ jobs: python - <<'PY' import json, os payload = { - 'schema_version': 2, + 'payload_kind': 'deferred_review_dismissed', + 'schema_version': 3, 'source_workflow_name': 'Reviewer Bot PR Review Dismissed Observer', 'source_workflow_file': '.github/workflows/reviewer-bot-pr-review-dismissed-observer.yml', 'source_run_id': int(os.environ['GITHUB_RUN_ID']), diff --git a/.github/workflows/reviewer-bot-pr-review-submitted-observer.yml b/.github/workflows/reviewer-bot-pr-review-submitted-observer.yml index 8e06cc52f..96ae3d2c5 100644 --- a/.github/workflows/reviewer-bot-pr-review-submitted-observer.yml +++ b/.github/workflows/reviewer-bot-pr-review-submitted-observer.yml @@ -27,7 +27,8 @@ jobs: python - <<'PY' import json, os payload = { - 'schema_version': 2, + 'payload_kind': 'deferred_review_submitted', + 'schema_version': 3, 'source_workflow_name': 'Reviewer Bot PR Review Submitted Observer', 'source_workflow_file': '.github/workflows/reviewer-bot-pr-review-submitted-observer.yml', 'source_run_id': int(os.environ['GITHUB_RUN_ID']), diff --git a/.github/workflows/reviewer-bot-privileged-commands.yml b/.github/workflows/reviewer-bot-privileged-commands.yml index a92b2ccec..61485a29f 100644 --- a/.github/workflows/reviewer-bot-privileged-commands.yml +++ b/.github/workflows/reviewer-bot-privileged-commands.yml @@ -69,6 +69,5 @@ jobs: WORKFLOW_RUN_ID: ${{ github.run_id }} WORKFLOW_NAME: ${{ github.workflow }} WORKFLOW_JOB_NAME: ${{ github.job }} - CURRENT_WORKFLOW_FILE: .github/workflows/reviewer-bot-privileged-commands.yml REVIEWER_BOT_TARGET_REPO_ROOT: ${{ github.workspace }} run: uv run --project "$BOT_SRC_ROOT" python "$BOT_SRC_ROOT/scripts/reviewer_bot.py" diff --git a/.github/workflows/reviewer-bot-reconcile.yml b/.github/workflows/reviewer-bot-reconcile.yml index 5f61c60c6..74a2176e6 100644 --- a/.github/workflows/reviewer-bot-reconcile.yml +++ b/.github/workflows/reviewer-bot-reconcile.yml @@ -3,7 +3,7 @@ name: Reviewer Bot Reconcile on: workflow_run: workflows: - - Reviewer Bot PR Comment Observer + - Reviewer Bot PR Comment Router - Reviewer Bot PR Review Submitted Observer - Reviewer Bot PR Review Dismissed Observer - Reviewer Bot PR Review Comment Observer @@ -29,6 +29,7 @@ jobs: - name: Install uv run: python -m pip install uv - name: Download deferred artifact + continue-on-error: true uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 with: path: ${{ runner.temp }}/observer-artifact @@ -61,17 +62,17 @@ jobs: import os from pathlib import Path files = sorted(Path(os.environ['RUNNER_TEMP']).joinpath('observer-artifact').rglob('*.json')) - if len(files) != 1: - raise SystemExit(f'Expected exactly one deferred payload, found {len(files)}') - print(f'DEFERRED_CONTEXT_PATH={files[0]}', file=open(os.environ['GITHUB_ENV'], 'a', encoding='utf-8')) + if len(files) > 1: + raise SystemExit(f'Expected at most one deferred payload, found {len(files)}') + if len(files) == 1: + print(f'DEFERRED_CONTEXT_PATH={files[0]}', file=open(os.environ['GITHUB_ENV'], 'a', encoding='utf-8')) PY - name: Run trusted reconcile env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} EVENT_NAME: workflow_run EVENT_ACTION: completed - WORKFLOW_RUN_EVENT: ${{ github.event.workflow_run.event }} - WORKFLOW_RUN_TRIGGERING_NAME: ${{ github.event.workflow_run.name }} + REVIEWER_BOT_WORKFLOW_KIND: reconcile WORKFLOW_RUN_TRIGGERING_ID: ${{ github.event.workflow_run.id }} WORKFLOW_RUN_TRIGGERING_ATTEMPT: ${{ github.event.workflow_run.run_attempt }} WORKFLOW_RUN_TRIGGERING_CONCLUSION: ${{ github.event.workflow_run.conclusion }} @@ -82,7 +83,6 @@ jobs: WORKFLOW_RUN_ID: ${{ github.run_id }} WORKFLOW_NAME: ${{ github.workflow }} WORKFLOW_JOB_NAME: ${{ github.job }} - CURRENT_WORKFLOW_FILE: .github/workflows/reviewer-bot-reconcile.yml run: uv run --project "$BOT_SRC_ROOT" reviewer-bot - name: Workflow summary run: | diff --git a/.github/workflows/reviewer-bot-sweeper-repair.yml b/.github/workflows/reviewer-bot-sweeper-repair.yml index 37febf673..ff2a468b5 100644 --- a/.github/workflows/reviewer-bot-sweeper-repair.yml +++ b/.github/workflows/reviewer-bot-sweeper-repair.yml @@ -70,7 +70,6 @@ jobs: WORKFLOW_RUN_ID: ${{ github.run_id }} WORKFLOW_NAME: ${{ github.workflow }} WORKFLOW_JOB_NAME: ${{ github.job }} - CURRENT_WORKFLOW_FILE: .github/workflows/reviewer-bot-sweeper-repair.yml REVIEWER_BOARD_ENABLED: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.action == 'preview-reviewer-board' && 'true' || 'false' }} REVIEWER_BOARD_TOKEN: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.action == 'preview-reviewer-board' && secrets.REVIEWER_BOARD_TOKEN || '' }} run: uv run --project "$BOT_SRC_ROOT" reviewer-bot diff --git a/.github/workflows/reviewer-bot-tests.yml b/.github/workflows/reviewer-bot-tests.yml index eac04986f..fc00adb04 100644 --- a/.github/workflows/reviewer-bot-tests.yml +++ b/.github/workflows/reviewer-bot-tests.yml @@ -4,21 +4,23 @@ on: push: paths: - 'scripts/reviewer_bot.py' + - 'scripts/reviewer_bot_lib/**' - 'scripts/*.py' + - 'tests/**' - '.github/workflows/reviewer-bot-*.yml' - - '.github/reviewer-bot-tests/**' - 'pyproject.toml' pull_request: paths: - 'scripts/reviewer_bot.py' + - 'scripts/reviewer_bot_lib/**' - 'scripts/*.py' + - 'tests/**' - '.github/workflows/reviewer-bot-*.yml' - - '.github/reviewer-bot-tests/**' - 'pyproject.toml' workflow_dispatch: jobs: - reviewer-bot-tests: + reviewer-bot-unit: runs-on: ubuntu-latest steps: - name: Checkout repository @@ -30,5 +32,82 @@ jobs: - name: Install dependencies run: uv sync --dev - - name: Run reviewer bot tests - run: uv run pytest .github/reviewer-bot-tests + - name: Run reviewer bot unit tests + run: uv run pytest tests/unit/reviewer_bot + + reviewer-bot-integration: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install uv + uses: astral-sh/setup-uv@v6 + + - name: Install dependencies + run: uv sync --dev + + - name: Run reviewer bot integration tests + run: uv run pytest tests/integration/reviewer_bot -m integration + + reviewer-bot-contract: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install uv + uses: astral-sh/setup-uv@v6 + + - name: Install dependencies + run: uv sync --dev + + - name: Run reviewer bot contract tests + run: uv run pytest tests/contract/reviewer_bot -m contract + + reviewer-bot-coverage: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install uv + uses: astral-sh/setup-uv@v6 + + - name: Install dependencies + run: uv sync --dev + + - name: Run reviewer bot coverage suite + run: >- + mkdir -p artifacts/coverage && + COVERAGE_FILE=artifacts/coverage/.coverage + uv run pytest tests/unit/reviewer_bot tests/integration/reviewer_bot + --cov=scripts.reviewer_bot + --cov=scripts.reviewer_bot_lib + --cov-branch + --cov-report=term-missing + --cov-report=json:artifacts/coverage/reviewer-bot.json + + - name: Enforce reviewer bot statement and branch floors + run: | + uv run python - <<'PY' + import json + + with open("artifacts/coverage/reviewer-bot.json", encoding="utf-8") as handle: + totals = json.load(handle)["totals"] + + statements = totals["percent_statements_covered"] + branches = totals["percent_branches_covered"] + + print(f"reviewer-bot statements: {statements:.2f}%") + print(f"reviewer-bot branches: {branches:.2f}%") + + if statements < 66: + raise SystemExit( + f"reviewer-bot statement coverage below floor: {statements:.2f}% < 66%" + ) + if branches < 48: + raise SystemExit( + f"reviewer-bot branch coverage below floor: {branches:.2f}% < 48%" + ) + PY diff --git a/.gitignore b/.gitignore index 9328f2b33..f157798ff 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,8 @@ build/ *.egg-info/ .cache/ .venv/ +.coverage +coverage*.json +coverage.xml +htmlcov/ +artifacts/coverage/ diff --git a/docs/reviewer-bot-review-freshness-operator-runbook.md b/docs/reviewer-bot-review-freshness-operator-runbook.md index 4ed7abce4..1d68ad467 100644 --- a/docs/reviewer-bot-review-freshness-operator-runbook.md +++ b/docs/reviewer-bot-review-freshness-operator-runbook.md @@ -74,7 +74,7 @@ Populate this section before production rollout: - environment: - event type: - exact endpoint used: `GET /repos/{owner}/{repo}/actions/runs/{run_id}` -- exact accepted field/value signature: +- exact accepted field/value signature: `{"status": "waiting", "conclusion": null, "name": "approval_pending"}` - negative near-miss examples rejected by tests: - validation run URL or run id: diff --git a/pyproject.toml b/pyproject.toml index 03cc22b67..c0a7317c5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -57,6 +57,30 @@ lint.ignore = [ [tool.ruff.lint.flake8-comprehensions] allow-dict-calls-with-keyword-arguments = true +[tool.pytest.ini_options] +testpaths = ["tests"] +addopts = [ + "--import-mode=importlib", + "-ra", + "--strict-config", + "--strict-markers", +] +markers = [ + "integration: filesystem, subprocess, multi-module, or boundary tests", + "contract: workflow, CLI, adapter, or artifact contract tests", +] +filterwarnings = ["error"] +xfail_strict = true + +[tool.coverage.run] +branch = true +relative_files = true +source = ["scripts.reviewer_bot", "scripts.reviewer_bot_lib"] + +[tool.coverage.report] +show_missing = true +skip_empty = true + [dependency-groups] dev = [ "pytest>=8.3.0", diff --git a/scripts/reviewer_bot.py b/scripts/reviewer_bot.py index 61a54c3b5..343c66ea3 100644 --- a/scripts/reviewer_bot.py +++ b/scripts/reviewer_bot.py @@ -1,902 +1,57 @@ #!/usr/bin/env python3 -""" -Reviewer Bot for Safety-Critical Rust Coding Guidelines - -This bot manages round-robin assignment of reviewers for coding guideline and -FLS audit issues and PRs. It supports commands for passing reviews, vacations, -and label management. - -All commands must be prefixed with @guidelines-bot /: - - @guidelines-bot /pass [reason] - - Skip the assigned reviewer for this issue/PR and assign the next person - - The skipped reviewer stays in queue position for future assignments - - @guidelines-bot /away YYYY-MM-DD [reason] - - Remove yourself from the queue until the specified date - - Automatically assigns the next available reviewer - - @guidelines-bot /claim - - Assign yourself as the reviewer for this issue/PR - - Removes any existing reviewer assignment - - @guidelines-bot /release [@username] [reason] - - Release your assignment from this issue/PR (or someone else's with triage+ permission) - - Does NOT auto-assign the next reviewer (use /pass for that) - - @guidelines-bot /rectify - - Reconcile this issue/PR's review state from GitHub review history - - Useful when cross-repo review events cannot persist state immediately - - @guidelines-bot /r? @username - - Assign a specific reviewer - - @guidelines-bot /r? producers - - Assign the next reviewer from the round-robin queue - - Useful for requesting a reviewer on an already-open issue/PR - - @guidelines-bot /label +label-name - - Add a label to the issue/PR - - @guidelines-bot /label -label-name - - Remove a label from the issue/PR - - @guidelines-bot /accept-no-fls-changes - - Update spec.lock and open a PR when the audit reports no guideline impact - - @guidelines-bot /sync-members - - Manually trigger sync of the queue with members.md - - @guidelines-bot /queue - - Show current queue status and who's next up - - @guidelines-bot /commands - - Show all available commands -""" +"""Bootstrap entrypoint for reviewer-bot.""" +import random import sys -from collections.abc import Iterable -from datetime import datetime -from datetime import timezone as _timezone -from pathlib import Path -from typing import Any - -# GitHub API interaction - -timezone = _timezone - -import scripts.reviewer_bot_lib.automation as automation_module -import scripts.reviewer_bot_lib.commands as commands_module -import scripts.reviewer_bot_lib.comment_routing as comment_routing_module -import scripts.reviewer_bot_lib.events as events_module -import scripts.reviewer_bot_lib.github_api as github_api_module -import scripts.reviewer_bot_lib.lease_lock as lease_lock_module -import scripts.reviewer_bot_lib.lifecycle as lifecycle_module -import scripts.reviewer_bot_lib.maintenance as maintenance_module -import scripts.reviewer_bot_lib.project_board as project_board_module -import scripts.reviewer_bot_lib.reconcile as reconcile_module -import scripts.reviewer_bot_lib.reviews as reviews_module -import scripts.reviewer_bot_lib.state_store as state_store_module -from scripts.reviewer_bot_lib import app as app_module - -# Exported runtime config surface used by extracted modules and tests. -# Exported runtime types used by extracted modules and tests. -# Internal-only config consumed directly by the entrypoint adapter. -from scripts.reviewer_bot_lib.config import ( # noqa: F401 # noqa: F401 - AUTHOR_ASSOCIATION_TRUST_ALLOWLIST, - BOT_MENTION, - BOT_NAME, - COMMANDS, - DEFERRED_ARTIFACT_RETENTION_DAYS, - DEFERRED_DISCOVERY_BOOTSTRAP_WINDOW_SECONDS, - DEFERRED_DISCOVERY_OVERLAP_SECONDS, - DEFERRED_MISSING_RUN_WINDOW_SECONDS, - EVENT_INTENT_MUTATING, - EVENT_INTENT_NON_MUTATING_DEFER, - EVENT_INTENT_NON_MUTATING_READONLY, - FLS_AUDIT_LABEL, - FRESHNESS_RUNTIME_EPOCH_LEGACY, - FRESHNESS_RUNTIME_EPOCH_V18, - LOCK_API_RETRY_LIMIT, - LOCK_BLOCK_END_MARKER, - LOCK_BLOCK_START_MARKER, - LOCK_COMMIT_MARKER, - LOCK_LEASE_TTL_SECONDS, - LOCK_MAX_WAIT_SECONDS, - LOCK_METADATA_KEYS, - LOCK_REF_BOOTSTRAP_BRANCH, - LOCK_REF_NAME, - LOCK_RENEWAL_WINDOW_SECONDS, - LOCK_RETRY_BASE_SECONDS, - LOCK_SCHEMA_VERSION, - MANDATORY_TRIAGE_APPROVER_LABEL, - MANDATORY_TRIAGE_ESCALATION_TEMPLATE, - MANDATORY_TRIAGE_SATISFIED_TEMPLATE, - MAX_RECENT_ASSIGNMENTS, - REVIEW_DEADLINE_DAYS, - REVIEW_FRESHNESS_RUNBOOK_PATH, - REVIEW_LABELS, - REVIEWER_BOARD_ENABLED_ENV, - REVIEWER_BOARD_FIELD_ASSIGNED_AT, - REVIEWER_BOARD_FIELD_NEEDS_ATTENTION, - REVIEWER_BOARD_FIELD_REVIEW_STATE, - REVIEWER_BOARD_FIELD_REVIEWER, - REVIEWER_BOARD_FIELD_WAITING_SINCE, - REVIEWER_BOARD_OPTION_ATTENTION_NO, - REVIEWER_BOARD_OPTION_ATTENTION_PROJECTION_REPAIR_REQUIRED, - REVIEWER_BOARD_OPTION_ATTENTION_TRANSITION_NOTICE_SENT, - REVIEWER_BOARD_OPTION_ATTENTION_TRIAGE_APPROVAL_REQUIRED, - REVIEWER_BOARD_OPTION_ATTENTION_WARNING_SENT, - REVIEWER_BOARD_OPTION_AWAITING_CONTRIBUTOR, - REVIEWER_BOARD_OPTION_AWAITING_REVIEWER, - REVIEWER_BOARD_OPTION_AWAITING_WRITE_APPROVAL, - REVIEWER_BOARD_OPTION_DONE, - REVIEWER_BOARD_OPTION_UNASSIGNED, - REVIEWER_BOARD_ORG, - REVIEWER_BOARD_PROJECT_MANIFEST, - REVIEWER_BOARD_PROJECT_NUMBER, - REVIEWER_BOARD_TOKEN_ENV, - REVIEWER_REQUEST_422_TEMPLATE, - STATE_BLOCK_END_MARKER, - STATE_BLOCK_START_MARKER, - STATE_ISSUE_NUMBER, - STATE_READ_RETRY_BASE_SECONDS, - STATE_READ_RETRY_LIMIT, - STATE_SCHEMA_VERSION, - STATUS_AWAITING_CONTRIBUTOR_RESPONSE_LABEL, - STATUS_AWAITING_REVIEW_COMPLETION_LABEL, - STATUS_AWAITING_REVIEWER_RESPONSE_LABEL, - STATUS_AWAITING_WRITE_APPROVAL_LABEL, - STATUS_LABEL_CONFIG, - STATUS_LABELS, - STATUS_PROJECTION_EPOCH, - TRANSITION_PERIOD_DAYS, - AssignmentAttempt, - GitHubApiResult, - LeaseContext, - StateIssueBodyParts, - StateIssueSnapshot, - get_commands_help, +import time + +from scripts.reviewer_bot_lib import github_api +from scripts.reviewer_bot_lib.app import build_event_context as build_app_event_context +from scripts.reviewer_bot_lib.app import execute_run as execute_app_run +from scripts.reviewer_bot_lib.app import main as run_app_main +from scripts.reviewer_bot_lib.bootstrap_runtime import ( + build_runtime as build_bootstrap_runtime, ) -from scripts.reviewer_bot_lib.guidance import ( # noqa: F401 - get_fls_audit_guidance, - get_issue_guidance, - get_pr_guidance, -) -from scripts.reviewer_bot_lib.members import fetch_members # noqa: F401 -from scripts.reviewer_bot_lib.queue import ( - get_next_reviewer as queue_get_next_reviewer, -) -from scripts.reviewer_bot_lib.queue import ( - process_pass_until_expirations as queue_process_pass_until_expirations, -) -from scripts.reviewer_bot_lib.queue import ( - record_assignment as queue_record_assignment, -) -from scripts.reviewer_bot_lib.queue import ( - reposition_member_as_next as queue_reposition_member_as_next, -) -from scripts.reviewer_bot_lib.queue import ( - sync_members_with_queue as queue_sync_members_with_queue, -) - -requests = github_api_module.requests -random = lease_lock_module.random -time = lease_lock_module.time - -# ============================================================================== -# GitHub API Helpers -# ============================================================================== +from scripts.reviewer_bot_lib.context import EventContext, ExecutionResult +from scripts.reviewer_bot_lib.runtime import ReviewerBotRuntime -ACTIVE_LEASE_CONTEXT: LeaseContext | None = None -TOUCHED_ISSUE_NUMBERS: set[int] = set() +def _runtime_bot(runtime: ReviewerBotRuntime | None = None) -> ReviewerBotRuntime: + if runtime is not None: + return runtime + return build_runtime() -def _runtime_bot() -> Any: - return sys.modules[__name__] +def build_event_context(runtime: ReviewerBotRuntime | None = None) -> EventContext: + return build_app_event_context(_runtime_bot(runtime)) -def get_github_token() -> str: - return github_api_module.get_github_token() +def execute_run(context: EventContext, runtime: ReviewerBotRuntime | None = None) -> ExecutionResult: + return execute_app_run(_runtime_bot(runtime), context) -def get_github_graphql_token(*, prefer_board_token: bool = False) -> str: - return github_api_module.get_github_graphql_token( - _runtime_bot(), prefer_board_token=prefer_board_token - ) - - -def github_api_request( - method: str, - endpoint: str, - data: dict | None = None, - extra_headers: dict[str, str] | None = None, - *, - suppress_error_log: bool = False, -) -> GitHubApiResult: - return github_api_module.github_api_request( - _runtime_bot(), - method, - endpoint, - data, - extra_headers, - suppress_error_log=suppress_error_log, - ) - - -def github_api(method: str, endpoint: str, data: dict | None = None) -> Any | None: - return github_api_module.github_api(_runtime_bot(), method, endpoint, data) - - -def github_graphql_request( - query: str, - variables: dict | None = None, - *, - token: str | None = None, - suppress_error_log: bool = False, -) -> GitHubApiResult: - return github_api_module.github_graphql_request( - _runtime_bot(), - query, - variables, - token=token, - suppress_error_log=suppress_error_log, - ) +def build_runtime() -> ReviewerBotRuntime: + return _build_runtime() -def github_graphql( - query: str, - variables: dict | None = None, +def main( + runtime: ReviewerBotRuntime | None = None, *, - token: str | None = None, -) -> Any | None: - return github_api_module.github_graphql(_runtime_bot(), query, variables, token=token) + runtime_factory=build_runtime, +) -> None: + resolved_runtime = runtime or runtime_factory() + run_app_main(_runtime_bot(resolved_runtime)) -def post_comment(issue_number: int, body: str) -> bool: - return github_api_module.post_comment(_runtime_bot(), issue_number, body) - - -def get_repo_labels() -> set[str]: - return github_api_module.get_repo_labels(_runtime_bot()) - - -def add_label(issue_number: int, label: str) -> bool: - return github_api_module.add_label(_runtime_bot(), issue_number, label) - - -def remove_label(issue_number: int, label: str) -> bool: - return github_api_module.remove_label(_runtime_bot(), issue_number, label) - - -def add_label_with_status(issue_number: int, label: str) -> bool: - return github_api_module.add_label_with_status(_runtime_bot(), issue_number, label) - - -def remove_label_with_status(issue_number: int, label: str) -> bool: - return github_api_module.remove_label_with_status(_runtime_bot(), issue_number, label) - - -def ensure_label_exists( - label: str, - *, - color: str | None = None, - description: str | None = None, -) -> bool: - return github_api_module.ensure_label_exists( - _runtime_bot(), - label, - color=color, - description=description, +def _build_runtime() -> ReviewerBotRuntime: + return build_bootstrap_runtime( + requests=github_api.requests, + sys=sys, + random=random, + time=time, + active_lease_context=None, ) -def collect_touched_item(issue_number: int | None) -> None: - """Record an issue/PR number for centralized status-label sync.""" - if isinstance(issue_number, int) and issue_number > 0: - TOUCHED_ISSUE_NUMBERS.add(issue_number) - - -def drain_touched_items() -> list[int]: - """Return touched issue numbers and clear the collector.""" - touched = sorted(TOUCHED_ISSUE_NUMBERS) - TOUCHED_ISSUE_NUMBERS.clear() - return touched - - -def get_issue_or_pr_snapshot(issue_number: int) -> dict | None: - """Fetch issue metadata used for derived status labels.""" - result = github_api("GET", f"issues/{issue_number}") - if isinstance(result, dict): - return result - return None - - -def get_issue_or_pr_labels(issue_number: int) -> set[str] | None: - """Fetch the current label set for an issue or PR.""" - item = get_issue_or_pr_snapshot(issue_number) - if not isinstance(item, dict): - return None - - labels = item.get("labels", []) - if not isinstance(labels, list): - return set() - - result = set() - for label in labels: - if isinstance(label, dict): - name = label.get("name") - if isinstance(name, str): - result.add(name) - elif isinstance(label, str): - result.add(label) - return result - - -def request_reviewer_assignment(issue_number: int, username: str) -> AssignmentAttempt: - return github_api_module.request_reviewer_assignment(_runtime_bot(), issue_number, username) - - -def assign_reviewer(issue_number: int, username: str) -> bool: - return github_api_module.assign_reviewer(_runtime_bot(), issue_number, username) - - -def get_assignment_failure_comment(reviewer: str, attempt: AssignmentAttempt) -> str | None: - return github_api_module.get_assignment_failure_comment(_runtime_bot(), reviewer, attempt) - - -def get_issue_assignees(issue_number: int) -> list[str]: - return github_api_module.get_issue_assignees(_runtime_bot(), issue_number) - - -def add_reaction(comment_id: int, reaction: str) -> bool: - return github_api_module.add_reaction(_runtime_bot(), comment_id, reaction) - - -def remove_assignee(issue_number: int, username: str) -> bool: - return github_api_module.remove_assignee(_runtime_bot(), issue_number, username) - - -def remove_pr_reviewer(issue_number: int, username: str) -> bool: - return github_api_module.remove_pr_reviewer(_runtime_bot(), issue_number, username) - - -def unassign_reviewer(issue_number: int, username: str) -> bool: - return github_api_module.unassign_reviewer(_runtime_bot(), issue_number, username) - - -def check_user_permission(username: str, required_permission: str = "triage") -> bool: - return github_api_module.check_user_permission(_runtime_bot(), username, required_permission) - - -# ============================================================================== -# State Management -# ============================================================================== - - -def get_state_issue() -> dict | None: - return state_store_module.get_state_issue(_runtime_bot()) - - -def default_state_issue_prefix() -> str: - return state_store_module.default_state_issue_prefix() - - -def split_state_issue_body(body: str) -> StateIssueBodyParts: - return state_store_module.split_state_issue_body(body) - - -def extract_fenced_block(inner_block: str, language_pattern: str) -> str | None: - return state_store_module.extract_fenced_block(inner_block, language_pattern) - - -def normalize_lock_metadata(lock_meta: dict | None) -> dict: - return state_store_module.normalize_lock_metadata(lock_meta) - - -def parse_state_yaml_from_issue_body(body: str) -> dict: - return state_store_module.parse_state_yaml_from_issue_body(body) - - -def parse_lock_metadata_from_issue_body(body: str) -> dict: - return state_store_module.parse_lock_metadata_from_issue_body(body) - - -def render_marked_fenced_block( - start_marker: str, - end_marker: str, - language: str, - content: str, -) -> str: - return state_store_module.render_marked_fenced_block(start_marker, end_marker, language, content) - - -def render_state_issue_body( - state: dict, - lock_meta: dict, - base_body: str | None = None, - *, - preserve_state_block: bool = False, -) -> str: - return state_store_module.render_state_issue_body( - state, - lock_meta, - base_body, - preserve_state_block=preserve_state_block, - ) - - -def parse_state_from_issue(issue: dict) -> dict: - return state_store_module.parse_state_from_issue(issue) - - -def get_state_issue_snapshot() -> StateIssueSnapshot | None: - return state_store_module.get_state_issue_snapshot(_runtime_bot()) - - -def conditional_patch_state_issue(body: str, etag: str | None = None) -> GitHubApiResult: - return state_store_module.conditional_patch_state_issue(_runtime_bot(), body, etag) - - -def assert_lock_held(operation: str) -> None: - state_store_module.assert_lock_held(_runtime_bot(), operation) - - -def load_state(*, fail_on_unavailable: bool = False) -> dict: - return state_store_module.load_state(_runtime_bot(), fail_on_unavailable=fail_on_unavailable) - - -def save_state(state: dict) -> bool: - return state_store_module.save_state(_runtime_bot(), state) - - -def parse_iso8601_timestamp(value: Any) -> datetime | None: - return state_store_module.parse_iso8601_timestamp(value) - - -def lock_is_currently_valid(lock_meta: dict, now: datetime | None = None) -> bool: - return lease_lock_module.lock_is_currently_valid(_runtime_bot(), lock_meta, now) - - -def get_lock_owner_context() -> tuple[str, str, str]: - return lease_lock_module.get_lock_owner_context() - - -def build_lock_metadata( - lock_token: str, - lock_owner_run_id: str, - lock_owner_workflow: str, - lock_owner_job: str, -) -> dict: - return lease_lock_module.build_lock_metadata( - _runtime_bot(), lock_token, lock_owner_run_id, lock_owner_workflow, lock_owner_job - ) - - -def clear_lock_metadata() -> dict: - return lease_lock_module.clear_lock_metadata(_runtime_bot()) - - -def normalize_lock_ref_name(ref_name: str) -> str: - return lease_lock_module.normalize_lock_ref_name(ref_name) - - -def get_lock_ref_name() -> str: - return lease_lock_module.get_lock_ref_name(_runtime_bot()) - - -def get_lock_ref_display() -> str: - return lease_lock_module.get_lock_ref_display(_runtime_bot()) - - -def get_state_issue_html_url() -> str: - return lease_lock_module.get_state_issue_html_url(_runtime_bot()) - - -def extract_ref_sha(payload: Any) -> str | None: - return lease_lock_module.extract_ref_sha(payload) - - -def extract_commit_tree_sha(payload: Any) -> str | None: - return lease_lock_module.extract_commit_tree_sha(payload) - - -def extract_commit_sha(payload: Any) -> str | None: - return lease_lock_module.extract_commit_sha(payload) - - -def render_lock_commit_message(lock_meta: dict) -> str: - return lease_lock_module.render_lock_commit_message(_runtime_bot(), lock_meta) - - -def parse_lock_metadata_from_lock_commit_message(message: str) -> dict: - return lease_lock_module.parse_lock_metadata_from_lock_commit_message(_runtime_bot(), message) - - -def ensure_lock_ref_exists() -> str: - return lease_lock_module.ensure_lock_ref_exists(_runtime_bot()) - - -def get_lock_ref_snapshot() -> tuple[str, str, dict]: - return lease_lock_module.get_lock_ref_snapshot(_runtime_bot()) - - -def create_lock_commit(parent_sha: str, tree_sha: str, lock_meta: dict) -> GitHubApiResult: - return lease_lock_module.create_lock_commit(_runtime_bot(), parent_sha, tree_sha, lock_meta) - - -def cas_update_lock_ref(new_sha: str) -> GitHubApiResult: - return lease_lock_module.cas_update_lock_ref(_runtime_bot(), new_sha) - - -def ensure_state_issue_lease_lock_fresh() -> bool: - return lease_lock_module.ensure_state_issue_lease_lock_fresh(_runtime_bot()) - - -def renew_state_issue_lease_lock(context: LeaseContext) -> bool: - return lease_lock_module.renew_state_issue_lease_lock(_runtime_bot(), context) - - -def acquire_state_issue_lease_lock() -> LeaseContext: - return lease_lock_module.acquire_state_issue_lease_lock(_runtime_bot()) - - -def release_state_issue_lease_lock() -> bool: - return lease_lock_module.release_state_issue_lease_lock(_runtime_bot()) - - -def sync_members_with_queue(state: dict) -> tuple[dict, list[str]]: - return queue_sync_members_with_queue(_runtime_bot(), state) - - -def reposition_member_as_next(state: dict, username: str) -> bool: - return queue_reposition_member_as_next(state, username) - - -def process_pass_until_expirations(state: dict) -> tuple[dict, list[str]]: - return queue_process_pass_until_expirations(state) - - -# ============================================================================== -# Reviewer Assignment -# ============================================================================== - - -def get_next_reviewer(state: dict, skip_usernames: set[str] | None = None) -> str | None: - return queue_get_next_reviewer(state, skip_usernames) - - -def record_assignment(state: dict, github: str, issue_number: int, - issue_type: str) -> None: - queue_record_assignment( - state, - github, - issue_number, - issue_type, - max_recent_assignments=MAX_RECENT_ASSIGNMENTS, - ) - - -# ============================================================================== -# Command Parsing & Handling -# ============================================================================== - - -def strip_code_blocks(comment_body: str) -> str: - return commands_module.strip_code_blocks(comment_body) - - -def parse_command(comment_body: str) -> tuple[str, list[str]] | None: - return commands_module.parse_command(_runtime_bot(), comment_body) - - -def handle_pass_command(state: dict, issue_number: int, comment_author: str, - reason: str | None) -> tuple[str, bool]: - return commands_module.handle_pass_command(_runtime_bot(), state, issue_number, comment_author, reason) - - -def handle_pass_until_command(state: dict, issue_number: int, comment_author: str, - return_date: str, reason: str | None) -> tuple[str, bool]: - return commands_module.handle_pass_until_command(_runtime_bot(), state, issue_number, comment_author, return_date, reason) - - -def handle_label_command(state: dict, issue_number: int, label_string: str) -> tuple[str, bool, bool]: - return commands_module.handle_label_command(_runtime_bot(), state, issue_number, label_string) - - -def parse_issue_labels() -> list[str]: - return commands_module.parse_issue_labels() - - -def run_command(command: list[str], cwd: Path, check: bool = True) -> Any: - return automation_module.run_command(command, cwd, check=check) - - -def summarize_output(result: Any, limit: int = 20) -> str: - return automation_module.summarize_output(result, limit=limit) - - -def list_changed_files(repo_root: Path) -> list[str]: - return automation_module.list_changed_files(repo_root) - - -def get_default_branch() -> str: - return automation_module.get_default_branch(_runtime_bot()) - - -def find_open_pr_for_branch(branch: str) -> dict | None: - return automation_module.find_open_pr_for_branch(_runtime_bot(), branch) - - -def resolve_workflow_run_pr_number() -> int: - return commands_module.resolve_workflow_run_pr_number(_runtime_bot()) - - -def create_pull_request(branch: str, base: str, issue_number: int) -> dict | None: - return automation_module.create_pull_request(_runtime_bot(), branch, base, issue_number) - - -def handle_accept_no_fls_changes_command(issue_number: int, comment_author: str) -> tuple[str, bool]: - return automation_module.handle_accept_no_fls_changes_command(_runtime_bot(), issue_number, comment_author) - - -def handle_sync_members_command(state: dict) -> tuple[str, bool]: - return commands_module.handle_sync_members_command(_runtime_bot(), state) - - -def handle_queue_command(state: dict) -> tuple[str, bool]: - return commands_module.handle_queue_command(_runtime_bot(), state) - - -def handle_commands_command() -> tuple[str, bool]: - return commands_module.handle_commands_command(_runtime_bot()) - - -def handle_claim_command(state: dict, issue_number: int, - comment_author: str) -> tuple[str, bool]: - return commands_module.handle_claim_command(_runtime_bot(), state, issue_number, comment_author) - - -def handle_release_command(state: dict, issue_number: int, - comment_author: str, args: list | None = None) -> tuple[str, bool]: - return commands_module.handle_release_command(_runtime_bot(), state, issue_number, comment_author, args) - - -def handle_assign_command(state: dict, issue_number: int, - username: str) -> tuple[str, bool]: - return commands_module.handle_assign_command(_runtime_bot(), state, issue_number, username) - - -def handle_assign_from_queue_command(state: dict, issue_number: int) -> tuple[str, bool]: - return commands_module.handle_assign_from_queue_command(_runtime_bot(), state, issue_number) - - -# ============================================================================== -# Event Handlers -# ============================================================================== - - -def ensure_review_entry(state: dict, issue_number: int, create: bool = False) -> dict | None: - return reviews_module.ensure_review_entry(state, issue_number, create=create) - - -def set_current_reviewer(state: dict, issue_number: int, reviewer: str, - assignment_method: str = "round-robin") -> None: - reviews_module.set_current_reviewer(state, issue_number, reviewer, assignment_method=assignment_method) - - -def update_reviewer_activity(state: dict, issue_number: int, reviewer: str) -> bool: - return reviews_module.update_reviewer_activity(state, issue_number, reviewer) - - -def mark_review_complete( - state: dict, - issue_number: int, - reviewer: str | None, - source: str, -) -> bool: - return reviews_module.mark_review_complete(state, issue_number, reviewer, source) - - -def is_triage_or_higher(username: str) -> bool: - return reviews_module.is_triage_or_higher(_runtime_bot(), username) - - -def trigger_mandatory_approver_escalation(state: dict, issue_number: int) -> bool: - return reviews_module.trigger_mandatory_approver_escalation(_runtime_bot(), state, issue_number) - - -def satisfy_mandatory_approver_requirement( - state: dict, - issue_number: int, - approver: str, -) -> bool: - return reviews_module.satisfy_mandatory_approver_requirement( - _runtime_bot(), state, issue_number, approver - ) - - -def handle_pr_approved_review( - state: dict, - issue_number: int, - review_author: str, - completion_source: str, -) -> bool: - return reviews_module.handle_pr_approved_review( - _runtime_bot(), state, issue_number, review_author, completion_source - ) - - -def parse_github_timestamp(value: str | None) -> datetime | None: - return reviews_module.parse_github_timestamp(value) - - -def get_pull_request_reviews(issue_number: int) -> list[dict] | None: - return reviews_module.get_pull_request_reviews(_runtime_bot(), issue_number) - - -def compute_reviewer_response_state( - issue_number: int, - review_data: dict, - *, - issue_snapshot: dict | None = None, - pull_request: dict | None = None, - reviews: list[dict] | None = None, -) -> dict[str, object]: - return reviews_module.compute_reviewer_response_state( - _runtime_bot(), - issue_number, - review_data, - issue_snapshot=issue_snapshot, - pull_request=pull_request, - reviews=reviews, - ) - - -def project_status_labels_for_item( - issue_number: int, - state: dict, - *, - issue_snapshot: dict | None = None, -) -> tuple[set[str] | None, dict[str, str | None]]: - return reviews_module.project_status_labels_for_item( - _runtime_bot(), issue_number, state, issue_snapshot=issue_snapshot - ) - - -def sync_status_labels(issue_number: int, desired_labels: set[str], actual_labels: Iterable[str]) -> bool: - return reviews_module.sync_status_labels(_runtime_bot(), issue_number, desired_labels, actual_labels) - - -def sync_status_labels_for_items(state: dict, issue_numbers: Iterable[int]) -> bool: - return reviews_module.sync_status_labels_for_items(_runtime_bot(), state, issue_numbers) - - -def list_open_items_with_status_labels() -> list[int]: - return reviews_module.list_open_items_with_status_labels(_runtime_bot()) - - -def handle_rectify_command(state: dict, issue_number: int, comment_author: str) -> tuple[str, bool, bool]: - """Handle /rectify for the current issue/PR only. - - Permission model: - - Allowed for the currently assigned reviewer. - - Allowed for users with triage+ permissions. - - Returns (message, success, state_changed). - """ - review_data = ensure_review_entry(state, issue_number) - current_reviewer = review_data.get("current_reviewer") if review_data else None - - is_current_reviewer = ( - isinstance(current_reviewer, str) - and current_reviewer.lower() == comment_author.lower() - ) - - has_triage = False - if not is_current_reviewer: - has_triage = check_user_permission(comment_author, "triage") - - if not is_current_reviewer and not has_triage: - if current_reviewer: - return ( - f"❌ Only the assigned reviewer (@{current_reviewer}) or a maintainer with triage+ " - "permission can run `/rectify`.", - False, - False, - ) - return ( - "❌ Only maintainers with triage+ permission can run `/rectify` when no assigned " - "reviewer is tracked.", - False, - False, - ) - - return reconcile_module.reconcile_active_review_entry( - _runtime_bot(), - state, - issue_number, - ) - - -def handle_transition_notice(state: dict, issue_number: int, reviewer: str) -> bool: - return lifecycle_module.handle_transition_notice(_runtime_bot(), state, issue_number, reviewer) - - -def handle_issue_or_pr_opened(state: dict) -> bool: - return lifecycle_module.handle_issue_or_pr_opened(_runtime_bot(), state) - - -def handle_labeled_event(state: dict) -> bool: - return lifecycle_module.handle_labeled_event(_runtime_bot(), state) - - -def handle_issue_edited_event(state: dict) -> bool: - return lifecycle_module.handle_issue_edited_event(_runtime_bot(), state) - - -def handle_pull_request_target_synchronize(state: dict) -> bool: - return lifecycle_module.handle_pull_request_target_synchronize(_runtime_bot(), state) - - -def maybe_record_head_observation_repair(issue_number: int, review_data: dict) -> bool: - return lifecycle_module.maybe_record_head_observation_repair(_runtime_bot(), issue_number, review_data) - - -def handle_pull_request_review_event(state: dict) -> bool: - return events_module.handle_pull_request_review_event(_runtime_bot(), state) - - -def handle_workflow_run_event(state: dict) -> bool: - return reconcile_module.handle_workflow_run_event(_runtime_bot(), state) - - -def handle_closed_event(state: dict) -> bool: - return lifecycle_module.handle_closed_event(_runtime_bot(), state) - - -def handle_comment_event(state: dict) -> bool: - return comment_routing_module.handle_comment_event(_runtime_bot(), state) - - -def build_pr_comment_observer_payload(issue_number: int) -> dict: - return comment_routing_module.build_pr_comment_observer_payload(_runtime_bot(), issue_number) - - -def handle_manual_dispatch(state: dict) -> bool: - return maintenance_module.handle_manual_dispatch(_runtime_bot(), state) - - -def handle_scheduled_check(state: dict) -> bool: - return maintenance_module.handle_scheduled_check(_runtime_bot(), state) - - -def reviewer_board_preflight(): - return project_board_module.reviewer_board_preflight(_runtime_bot()) - - -def preview_board_projection_for_item(state: dict, issue_number: int): - return project_board_module.preview_board_projection_for_item(_runtime_bot(), state, issue_number) - - -# ============================================================================== -# Main -# ============================================================================== - - -def classify_event_intent(event_name: str, event_action: str) -> str: - return app_module.classify_event_intent(_runtime_bot(), event_name, event_action) - - -def event_requires_lease_lock(event_name: str, event_action: str) -> bool: - """Backwards-compatible helper for tests and call sites.""" - return app_module.event_requires_lease_lock(_runtime_bot(), event_name, event_action) - - -def main(): - app_module.main(_runtime_bot()) - - if __name__ == "__main__": main() diff --git a/scripts/reviewer_bot_core/__init__.py b/scripts/reviewer_bot_core/__init__.py new file mode 100644 index 000000000..64778f2af --- /dev/null +++ b/scripts/reviewer_bot_core/__init__.py @@ -0,0 +1 @@ +"""Minimal reviewer bot core package.""" diff --git a/scripts/reviewer_bot_core/approval_policy.py b/scripts/reviewer_bot_core/approval_policy.py new file mode 100644 index 000000000..0e4ca1d74 --- /dev/null +++ b/scripts/reviewer_bot_core/approval_policy.py @@ -0,0 +1,134 @@ +"""Approval and completion derivation owner. + +Future changes that belong here: +- approval/completion derivation from pull-request snapshots, review snapshots, and + permission observations +- triage-approval derivation from already-fetched review inputs + +Future changes that do not belong here: +- reviewer-response derivation +- state mutation, label writes, escalation, or other side effects +- pure projection helper implementations that remain in `reviews_projection.py` + +Old module no longer preferred for these derivation changes: +- `scripts/reviewer_bot_lib/reviews.py` +""" + +from __future__ import annotations + +from . import live_review_support + + +def compute_pr_approval_state_from_reviews( + survivors: dict[str, dict], + *, + current_head: str, + permission_statuses: dict[str, str], +) -> dict[str, object]: + approvals = [review for review in survivors.values() if str(review.get("state", "")).upper() == "APPROVED"] + completion = { + "completed": bool(approvals), + "current_head_sha": current_head, + "qualifying_review_ids": [review.get("id") for review in approvals], + } + + has_write_approval = False + write_approvers: list[str] = [] + for review in approvals: + author = review.get("user", {}).get("login") + if not isinstance(author, str) or not author.strip(): + continue + status = permission_statuses.get(author.lower(), "unavailable") + if status == "unavailable": + return {"ok": False, "reason": "permission_unavailable"} + if status == "granted": + has_write_approval = True + write_approvers.append(author) + + write_approval = { + "has_write_approval": has_write_approval, + "write_approvers": write_approvers, + "current_head_sha": current_head, + } + return { + "ok": True, + "completion": completion, + "write_approval": write_approval, + "current_head_sha": current_head, + } + + +def compute_pr_approval_state_result( + bot, + issue_number: int, + review_data: dict, + *, + pull_request: dict | None = None, + reviews: list[dict] | None = None, +) -> dict[str, object]: + boundary = live_review_support.get_current_cycle_boundary( + review_data, + parse_timestamp=bot.parse_iso8601_timestamp, + ) + if boundary is None: + return live_review_support.projection_failure_result("pull_request_unavailable") + pull_request_result = live_review_support.read_pull_request_result(bot, issue_number, pull_request) + if not pull_request_result.get("ok"): + return pull_request_result + pull_request = pull_request_result["pull_request"] + head = pull_request.get("head") + current_head = head.get("sha") if isinstance(head, dict) else None + if not isinstance(current_head, str) or not current_head.strip(): + return live_review_support.projection_failure_result("pull_request_head_unavailable", "invalid_payload") + reviews_result = live_review_support.read_pull_request_reviews_result(bot, issue_number, reviews) + if not reviews_result.get("ok"): + return reviews_result + reviews = reviews_result["reviews"] + + normalized_reviews = live_review_support.normalize_reviews_with_parsed_timestamps( + reviews, + parse_timestamp=live_review_support.parse_github_timestamp, + ) + survivors = live_review_support.filter_current_head_reviews_for_cycle( + normalized_reviews, + boundary=boundary, + current_head=current_head, + ) + permission_cache = live_review_support.collect_permission_statuses( + survivors, + permission_status=lambda author: live_review_support.permission_status(bot, author, "push"), + ) + result = compute_pr_approval_state_from_reviews( + survivors, + current_head=current_head, + permission_statuses=permission_cache, + ) + if not result.get("ok"): + return live_review_support.projection_failure_result(str(result.get("reason"))) + return result + + +def find_triage_approval_after(bot, reviews: list[dict], since) -> tuple[str, object] | None: + permission_cache: dict[str, bool] = {} + approvals: list[tuple[object, str, str]] = [] + for review in reviews: + state = str(review.get("state", "")).upper() + if state != "APPROVED": + continue + author = review.get("user", {}).get("login") + if not isinstance(author, str) or not author: + continue + submitted_at = live_review_support.parse_github_timestamp(review.get("submitted_at")) + if submitted_at is None: + continue + if since is not None and submitted_at <= since: + continue + approvals.append((submitted_at, str(review.get("id", "")), author)) + approvals.sort(key=lambda item: (item[0], item[1])) + for submitted_at, _, author in approvals: + cache_key = author.lower() + if cache_key not in permission_cache: + permission_cache[cache_key] = live_review_support.permission_status(bot, author, "triage") == "granted" + if permission_cache[cache_key]: + return author, submitted_at + return None diff --git a/scripts/reviewer_bot_core/comment_command_policy.py b/scripts/reviewer_bot_core/comment_command_policy.py new file mode 100644 index 000000000..a52e4e4f9 --- /dev/null +++ b/scripts/reviewer_bot_core/comment_command_policy.py @@ -0,0 +1,207 @@ +"""Command decision owner for already-routed comment events.""" + +from __future__ import annotations + +from dataclasses import dataclass +from enum import StrEnum + +from . import privileged_command_policy + + +class OrdinaryCommandId(StrEnum): + PASS = "pass" + AWAY = "away" + LABEL = "label" + SYNC_MEMBERS = "sync-members" + QUEUE = "queue" + COMMANDS = "commands" + CLAIM = "claim" + RELEASE = "release" + RECTIFY = "rectify" + ASSIGN_SPECIFIC = "r?-user" + ASSIGN_FROM_QUEUE = "assign-from-queue" + + +@dataclass(frozen=True) +class IgnoreDecision: + pass + + +@dataclass(frozen=True) +class InlineResponseDecision: + response: str + success: bool + react: bool + + +@dataclass(frozen=True) +class DeferPrivilegedHandoffDecision: + command_id: privileged_command_policy.PrivilegedCommandId + + +@dataclass(frozen=True) +class ExecuteOrdinaryCommandDecision: + command_id: OrdinaryCommandId + issue_number: int + actor: str + raw_args: tuple[str, ...] + needs_assignment_request: bool + + +CommentCommandDecision = ( + IgnoreDecision + | InlineResponseDecision + | DeferPrivilegedHandoffDecision + | ExecuteOrdinaryCommandDecision +) + + +def decide_comment_command(bot, request, classified, *, actor_class: str, commands_help: str) -> CommentCommandDecision: + if not isinstance(classified, dict): + return IgnoreDecision() + command = classified.get("command") + args = tuple(classified.get("args") or []) + if not isinstance(command, str): + return IgnoreDecision() + if actor_class in {"unknown_actor", "bot_account", "github_app_or_other_automation"}: + return IgnoreDecision() + + issue_number = request.issue_number + comment_author = request.comment_author + + if command == privileged_command_policy.PrivilegedCommandId.ACCEPT_NO_FLS_CHANGES.value: + return DeferPrivilegedHandoffDecision(privileged_command_policy.PrivilegedCommandId.ACCEPT_NO_FLS_CHANGES) + if command == "_multiple_commands": + return InlineResponseDecision( + response=( + f"⚠️ Multiple bot commands in one comment are ignored. Please post a single command per comment. " + f"For a list of commands, use `{bot.BOT_MENTION} /commands`." + ), + success=False, + react=False, + ) + if command == OrdinaryCommandId.PASS.value: + return ExecuteOrdinaryCommandDecision( + command_id=OrdinaryCommandId.PASS, + issue_number=issue_number, + actor=comment_author, + raw_args=args, + needs_assignment_request=True, + ) + if command == OrdinaryCommandId.AWAY.value: + if args: + return ExecuteOrdinaryCommandDecision( + command_id=OrdinaryCommandId.AWAY, + issue_number=issue_number, + actor=comment_author, + raw_args=args, + needs_assignment_request=True, + ) + return InlineResponseDecision( + response=f"❌ Missing date. Usage: `{bot.BOT_MENTION} /away YYYY-MM-DD [reason]`", + success=False, + react=True, + ) + if command == OrdinaryCommandId.LABEL.value: + return ExecuteOrdinaryCommandDecision( + command_id=OrdinaryCommandId.LABEL, + issue_number=issue_number, + actor=comment_author, + raw_args=args, + needs_assignment_request=True, + ) + if command == OrdinaryCommandId.SYNC_MEMBERS.value: + return ExecuteOrdinaryCommandDecision( + command_id=OrdinaryCommandId.SYNC_MEMBERS, + issue_number=issue_number, + actor=comment_author, + raw_args=args, + needs_assignment_request=False, + ) + if command == OrdinaryCommandId.QUEUE.value: + return ExecuteOrdinaryCommandDecision( + command_id=OrdinaryCommandId.QUEUE, + issue_number=issue_number, + actor=comment_author, + raw_args=args, + needs_assignment_request=False, + ) + if command == OrdinaryCommandId.COMMANDS.value: + return ExecuteOrdinaryCommandDecision( + command_id=OrdinaryCommandId.COMMANDS, + issue_number=issue_number, + actor=comment_author, + raw_args=args, + needs_assignment_request=False, + ) + if command == OrdinaryCommandId.CLAIM.value: + return ExecuteOrdinaryCommandDecision( + command_id=OrdinaryCommandId.CLAIM, + issue_number=issue_number, + actor=comment_author, + raw_args=args, + needs_assignment_request=True, + ) + if command == OrdinaryCommandId.RELEASE.value: + return ExecuteOrdinaryCommandDecision( + command_id=OrdinaryCommandId.RELEASE, + issue_number=issue_number, + actor=comment_author, + raw_args=args, + needs_assignment_request=True, + ) + if command == OrdinaryCommandId.RECTIFY.value: + return ExecuteOrdinaryCommandDecision( + command_id=OrdinaryCommandId.RECTIFY, + issue_number=issue_number, + actor=comment_author, + raw_args=args, + needs_assignment_request=False, + ) + if command == OrdinaryCommandId.ASSIGN_SPECIFIC.value: + return ExecuteOrdinaryCommandDecision( + command_id=OrdinaryCommandId.ASSIGN_SPECIFIC, + issue_number=issue_number, + actor=comment_author, + raw_args=args, + needs_assignment_request=True, + ) + if command == OrdinaryCommandId.ASSIGN_FROM_QUEUE.value: + return ExecuteOrdinaryCommandDecision( + command_id=OrdinaryCommandId.ASSIGN_FROM_QUEUE, + issue_number=issue_number, + actor=comment_author, + raw_args=args, + needs_assignment_request=True, + ) + if command == "r?": + return InlineResponseDecision( + response=( + f"❌ Missing target. Usage:\n- `{bot.BOT_MENTION} /r? @username` - Assign a specific reviewer\n" + f"- `{bot.BOT_MENTION} /r? producers` - Assign next reviewer from queue" + ), + success=False, + react=True, + ) + if command == "_malformed_known": + attempted = args[0] if args else "command" + return InlineResponseDecision( + response=f"⚠️ Did you mean `{bot.BOT_MENTION} /{attempted}`?\n\nCommands require a `/` prefix.", + success=False, + react=True, + ) + if command == "_malformed_unknown": + attempted = args[0] if args else "" + return InlineResponseDecision( + response=( + f"⚠️ Unknown command `{attempted}`. Commands require a `/` prefix.\n\n" + f"Try `{bot.BOT_MENTION} /commands` to see available commands." + ), + success=False, + react=True, + ) + return InlineResponseDecision( + response=f"❌ Unknown command: `/{command}`\n\nAvailable commands:\n{commands_help}", + success=False, + react=True, + ) diff --git a/scripts/reviewer_bot_core/comment_freshness_policy.py b/scripts/reviewer_bot_core/comment_freshness_policy.py new file mode 100644 index 000000000..5f64bea73 --- /dev/null +++ b/scripts/reviewer_bot_core/comment_freshness_policy.py @@ -0,0 +1,45 @@ +"""Direct comment freshness decision policy. + +This module owns freshness application decisions only. It does not parse command +syntax, route trust, or perform GitHub writes. +""" + +from __future__ import annotations + +from dataclasses import dataclass + + +@dataclass(frozen=True) +class CommentFreshnessDecision: + kind: str + channel_name: str | None = None + semantic_key: str | None = None + timestamp: str | None = None + actor: str | None = None + update_reviewer_activity: bool = False + + +def decide_comment_freshness(review_data: dict, request) -> CommentFreshnessDecision: + comment_author = request.comment_author + created_at = request.comment_created_at + semantic_key = request.comment_source_event_key or f"issue_comment:{request.comment_id}" + if request.issue_author and request.issue_author.lower() == comment_author.lower(): + return CommentFreshnessDecision( + kind="accept_channel_event", + channel_name="contributor_comment", + semantic_key=semantic_key, + timestamp=created_at, + actor=comment_author, + update_reviewer_activity=False, + ) + current_reviewer = review_data.get("current_reviewer") + if isinstance(current_reviewer, str) and current_reviewer.lower() == comment_author.lower(): + return CommentFreshnessDecision( + kind="accept_channel_event", + channel_name="reviewer_comment", + semantic_key=semantic_key, + timestamp=created_at, + actor=comment_author, + update_reviewer_activity=True, + ) + return CommentFreshnessDecision(kind="noop") diff --git a/scripts/reviewer_bot_core/comment_routing_policy.py b/scripts/reviewer_bot_core/comment_routing_policy.py new file mode 100644 index 000000000..fa15fdd6a --- /dev/null +++ b/scripts/reviewer_bot_core/comment_routing_policy.py @@ -0,0 +1,129 @@ +"""Trust-routing and comment-payload classification owner.""" + +from __future__ import annotations + +import re +from enum import StrEnum + +from scripts.reviewer_bot_lib.context import PrCommentAdmission + + +class PrCommentRouterOutcome(StrEnum): + TRUSTED_DIRECT = "trusted_direct" + DEFERRED_RECONCILE = "deferred_reconcile" + SAFE_NOOP = "safe_noop" + + +class ActorClass(StrEnum): + BOT_ACCOUNT = "bot_account" + GITHUB_APP_OR_OTHER_AUTOMATION = "github_app_or_other_automation" + REPO_USER_PRINCIPAL = "repo_user_principal" + UNKNOWN_ACTOR = "unknown_actor" + + +class ProcessingTarget(StrEnum): + ISSUE_DIRECT = "issue_direct" + PR_TRUSTED_DIRECT = "pr_trusted_direct" + + +class ObserverCommentClassification(StrEnum): + EMPTY_OR_WHITESPACE = "empty_or_whitespace" + PLAIN_TEXT = "plain_text" + COMMAND_ONLY = "command_only" + COMMAND_PLUS_TEXT = "command_plus_text" + + +class DirectCommentClassification(StrEnum): + NOOP = "noop" + FRESHNESS_ONLY = "freshness_only" + COMMAND_ONLY = "command_only" + BOTH = "both" + + +def classify_issue_comment_actor(request) -> ActorClass: + comment_user_type = request.comment_user_type + comment_author = request.comment_author.strip() + sender_type = request.comment_sender_type + installation_id = request.comment_installation_id + via_github_app = request.comment_performed_via_github_app + if comment_user_type == "Bot" or comment_author.endswith("[bot]"): + return ActorClass.BOT_ACCOUNT + if installation_id or via_github_app or (sender_type and sender_type not in {"User", "Bot"}): + return ActorClass.GITHUB_APP_OR_OTHER_AUTOMATION + if comment_user_type == "User" and comment_author and not comment_author.endswith("[bot]") and not installation_id and not via_github_app: + return ActorClass.REPO_USER_PRINCIPAL + return ActorClass.UNKNOWN_ACTOR + + +def comment_line_is_command(bot_mention: str, line: str) -> bool: + stripped = line.strip() + if not stripped: + return False + pattern = rf"^{re.escape(bot_mention)}\s+/[A-Za-z0-9?_-]+(?:\s+.*)?$" + return re.match(pattern, stripped) is not None + + +def classify_comment_payload(bot_mention: str, normalized_body: str, parsed_command) -> dict: + if not normalized_body: + return { + "comment_class": ObserverCommentClassification.EMPTY_OR_WHITESPACE, + "has_non_command_text": False, + "command_count": 0, + "command": None, + "args": [], + "normalized_body": normalized_body, + } + lines = [line for line in normalized_body.splitlines() if line.strip()] + command_lines = [line for line in lines if comment_line_is_command(bot_mention, line)] + non_command_lines = [line for line in lines if not comment_line_is_command(bot_mention, line)] + command = None + args: list[str] = [] + if parsed_command: + command, args = parsed_command + if command_lines and not non_command_lines: + comment_class = ObserverCommentClassification.COMMAND_ONLY + elif command_lines and non_command_lines: + comment_class = ObserverCommentClassification.COMMAND_PLUS_TEXT + else: + comment_class = ObserverCommentClassification.PLAIN_TEXT + return { + "comment_class": comment_class, + "has_non_command_text": bool(non_command_lines), + "command_count": len(command_lines), + "command": command, + "args": args, + "normalized_body": normalized_body, + } + + +def classify_pr_comment_processing_target( + request, + pr_admission: PrCommentAdmission, + *, + actor_class: ActorClass, + is_self_comment: bool, +) -> ProcessingTarget | PrCommentRouterOutcome: + if actor_class in {ActorClass.BOT_ACCOUNT, ActorClass.GITHUB_APP_OR_OTHER_AUTOMATION} or is_self_comment: + return PrCommentRouterOutcome.SAFE_NOOP + if pr_admission.route_outcome is not PrCommentRouterOutcome.TRUSTED_DIRECT: + return pr_admission.route_outcome + if pr_admission.pr_head_full_name != pr_admission.github_repository: + return PrCommentRouterOutcome.DEFERRED_RECONCILE + if pr_admission.pr_author == "dependabot[bot]": + return PrCommentRouterOutcome.DEFERRED_RECONCILE + if actor_class is ActorClass.REPO_USER_PRINCIPAL and pr_admission.declared_trust_class == "pr_trusted_direct": + return ProcessingTarget.PR_TRUSTED_DIRECT + raise RuntimeError("Ambiguous same-repo PR comment trust posture; failing closed") + + +def route_issue_comment_trust(request, pr_admission: PrCommentAdmission | None, *, processing_target=None): + if not request.is_pull_request: + return ProcessingTarget.ISSUE_DIRECT + target = processing_target + if target == ProcessingTarget.PR_TRUSTED_DIRECT: + return PrCommentRouterOutcome.TRUSTED_DIRECT + if isinstance(target, PrCommentRouterOutcome): + return target + if pr_admission is None: + raise RuntimeError("Trusted direct PR comment handling requires pr_admission") + return pr_admission.route_outcome diff --git a/scripts/reviewer_bot_core/deferred_gap_diagnosis.py b/scripts/reviewer_bot_core/deferred_gap_diagnosis.py new file mode 100644 index 000000000..e756afb9e --- /dev/null +++ b/scripts/reviewer_bot_core/deferred_gap_diagnosis.py @@ -0,0 +1,316 @@ +"""Deferred gap diagnosis and narrow visible-review recommendation helpers. + +This module owns diagnosis vocabulary selection and the current narrow visible-review +repair recommendation seam. It does not fetch evidence or mutate review state. +""" + +from __future__ import annotations + +from datetime import datetime, timedelta, timezone +from typing import Any + + +def _now() -> datetime: + return datetime.now(timezone.utc) + + +def parse_timestamp(value: Any) -> datetime | None: + if not isinstance(value, str) or not value: + return None + try: + return datetime.fromisoformat(value.replace("Z", "+00:00")) + except ValueError: + return None + + +def observer_run_reason_from_details(run_details: dict, runbook_signature: dict | None) -> str: + status = str(run_details.get("status", "")).strip() + conclusion = run_details.get("conclusion") + if runbook_signature and all(run_details.get(key) == value for key, value in runbook_signature.items()): + return "awaiting_observer_approval" + if status in {"queued", "in_progress"}: + return "observer_in_progress" + if status == "completed": + if conclusion == "success": + return "completed_success" + if conclusion in {"failure", "timed_out", "action_required", "stale"}: + return "observer_failed" + if conclusion == "cancelled": + return "observer_cancelled" + return "observer_state_unknown" + + +def can_mark_observer_run_missing(gap: dict, now: datetime | None = None) -> bool: + now = now or _now() + created_at = gap.get("source_event_created_at") + created_dt = parse_timestamp(created_at) + if created_dt is None or now < created_dt + timedelta(hours=24): + return False + return bool( + gap.get("full_scan_complete") + and gap.get("later_recheck_complete") + and not gap.get("correlated_run_found") + and not gap.get("approval_pending_evidence_retained") + ) + + +def classify_artifact_gap_reason(gap: dict, now: datetime | None = None, *, retention_days: int = 7) -> str: + now = now or _now() + run_created_at = parse_timestamp(gap.get("run_created_at")) + if gap.get("artifact_seen_at") or gap.get("artifact_last_downloadable_at"): + return "artifact_expired" + if run_created_at is not None and gap.get("retention_window_documented") and now >= run_created_at + timedelta(days=retention_days): + return "artifact_expired" + if gap.get("artifact_inspection_complete"): + return "artifact_missing" + return "observer_state_unknown" + + +def correlate_candidate_observer_runs( + source_event_key: str, + *, + source_event_kind: str, + source_event_created_at: str, + pr_number: int, + workflow_file: str, + workflow_runs: list[dict] | None, + github_repository: str, +) -> dict: + created_at = parse_timestamp(source_event_created_at) + if created_at is None: + return { + "status": "observer_state_unknown", + "reason": "invalid_source_event_created_at", + "candidate_run_ids": [], + "full_scan_complete": False, + "later_recheck_complete": False, + "correlated_run": None, + } + if workflow_runs is None: + return { + "status": "observer_state_unknown", + "reason": "workflow_run_scan_unavailable", + "candidate_run_ids": [], + "full_scan_complete": False, + "later_recheck_complete": False, + "correlated_run": None, + } + expected_event_map = { + "issue_comment:created": "issue_comment", + "pull_request_review:submitted": "pull_request_review", + "pull_request_review:dismissed": "pull_request_review", + "pull_request_review_comment:created": "pull_request_review_comment", + } + expected_event = expected_event_map.get(source_event_kind) + if expected_event is None: + return { + "status": "observer_state_unknown", + "reason": "unsupported_source_event_kind", + "candidate_run_ids": [], + "full_scan_complete": False, + "later_recheck_complete": False, + "correlated_run": None, + } + window_start = created_at - timedelta(minutes=2) + window_end = created_at + timedelta(minutes=30) + candidates: list[dict] = [] + for run in workflow_runs: + if not isinstance(run, dict): + continue + if str(run.get("event", "")) != expected_event: + continue + created = parse_timestamp(run.get("created_at")) + if created is None or created < window_start or created > window_end: + continue + if str(run.get("path", "")) != workflow_file: + continue + repo = run.get("repository") + if isinstance(repo, dict): + full_name = repo.get("full_name") + if isinstance(full_name, str) and full_name != github_repository: + continue + prs = run.get("pull_requests") + if isinstance(prs, list) and prs: + if not any(isinstance(pr, dict) and pr.get("number") == pr_number for pr in prs): + continue + candidates.append(run) + candidate_run_ids = [run.get("id") for run in candidates if isinstance(run.get("id"), int)] + return { + "status": "candidate_runs_found" if candidates else "no_candidate_runs", + "reason": None, + "candidate_runs": candidates, + "candidate_run_ids": candidate_run_ids, + "full_scan_complete": True, + "later_recheck_complete": False, + "correlated_run": None, + } + + +def correlate_run_artifacts_exact( + payloads_by_run: dict[int, list[dict]] | None, + source_event_key: str, + *, + pr_number: int, +) -> dict: + if payloads_by_run is None: + return {"status": "observer_state_unknown", "reason": "artifact_scan_unavailable", "correlated_run": None} + matches: list[tuple[int, dict]] = [] + candidate_run_ids: list[int] = [] + for run_id, payloads in payloads_by_run.items(): + if not isinstance(run_id, int): + continue + candidate_run_ids.append(run_id) + latest_by_attempt: dict[int, dict] = {} + for artifact_payload in payloads: + if not isinstance(artifact_payload, dict): + continue + attempt = artifact_payload.get("source_run_attempt") + if not isinstance(attempt, int): + continue + previous = latest_by_attempt.get(attempt) + if previous is None or artifact_payload.get("source_event_key") == source_event_key: + latest_by_attempt[attempt] = artifact_payload + for artifact_payload in latest_by_attempt.values(): + if artifact_payload.get("source_event_key") != source_event_key: + continue + if artifact_payload.get("source_run_id") != run_id: + continue + if artifact_payload.get("pr_number") != pr_number: + continue + matches.append((run_id, artifact_payload)) + if not matches: + return { + "status": "no_exact_artifact_match", + "reason": "no_exact_source_event_key_match", + "correlated_run": None, + "candidate_run_ids": sorted(candidate_run_ids), + } + distinct_run_ids = sorted({run_id for run_id, _ in matches}) + if len(distinct_run_ids) > 1: + return { + "status": "observer_state_unknown", + "reason": "ambiguous_exact_artifact_matches", + "correlated_run": None, + "candidate_run_ids": distinct_run_ids, + } + run_id, matched_payload = matches[-1] + return { + "status": "exact_artifact_match", + "reason": None, + "correlated_run": run_id, + "artifact_payload": matched_payload, + "candidate_run_ids": distinct_run_ids, + } + + +def evaluate_deferred_gap_state( + existing_gap: dict, + run_correlation: dict, + run_detail: dict | None, + artifact_correlation: dict | None, + *, + runbook_signature: dict | None = None, +) -> tuple[str, str]: + if run_correlation.get("status") == "observer_state_unknown": + return "observer_state_unknown", str(run_correlation.get("reason") or "run_scan_unknown") + if run_correlation.get("status") == "no_candidate_runs": + gap = dict(existing_gap) + gap["full_scan_complete"] = bool(run_correlation.get("full_scan_complete")) + gap["later_recheck_complete"] = bool(run_correlation.get("later_recheck_complete")) + gap["correlated_run_found"] = False + gap["approval_pending_evidence_retained"] = False + if can_mark_observer_run_missing(gap): + return "observer_run_missing", "negative_inference_satisfied" + created_at = parse_timestamp(existing_gap.get("source_event_created_at")) + if created_at is not None and _now() < created_at + timedelta(hours=24): + return "awaiting_observer_run", "missing_run_window_open" + return "observer_state_unknown", "missing_run_window_not_proven" + if run_detail is None: + return "observer_state_unknown", "run_detail_unavailable" + run_state = observer_run_reason_from_details(run_detail, runbook_signature) + if run_state in { + "awaiting_observer_approval", + "observer_in_progress", + "observer_failed", + "observer_cancelled", + "observer_state_unknown", + }: + return run_state, f"run_detail:{run_state}" + if run_state != "completed_success": + return "observer_state_unknown", "unmapped_run_state" + if artifact_correlation is None: + return "artifact_missing", "artifact_correlation_unavailable" + artifact_status = artifact_correlation.get("status") + if artifact_status == "exact_artifact_match": + return "observer_state_unknown", "successful_artifact_present_without_reconcile_marker" + if artifact_status == "no_exact_artifact_match": + scan_outcomes = artifact_correlation.get("artifact_scan_outcomes") + if isinstance(scan_outcomes, dict): + if any(outcome == "expired" for outcome in scan_outcomes.values()): + return "artifact_expired", "prior_visibility_or_retention_proof_required" + if any(outcome == "download_unavailable" for outcome in scan_outcomes.values()): + return "observer_state_unknown", "artifact_download_unavailable" + invalid_outcomes = {"missing_download_url", "download_failed", "invalid_payload_layout", "invalid_payload_format"} + if any(outcome in invalid_outcomes for outcome in scan_outcomes.values()): + return "artifact_invalid", "artifact_download_or_payload_invalid" + return "artifact_missing", str(artifact_correlation.get("reason") or "exact_artifact_missing") + if artifact_status == "observer_state_unknown": + return "observer_state_unknown", str(artifact_correlation.get("reason") or "artifact_ambiguity") + return "artifact_invalid", str(artifact_correlation.get("reason") or "artifact_invalid") + + +def recommend_visible_review_repair( + review_data: dict, + review: dict, + source_event_key: str, + *, + current_cycle_boundary, +) -> tuple[str, str, str] | None: + current_reviewer = review_data.get("current_reviewer") + if not isinstance(current_reviewer, str) or not current_reviewer.strip(): + return None + author = review.get("user", {}).get("login") if isinstance(review, dict) else None + commit_id = review.get("commit_id") if isinstance(review, dict) else None + submitted_at = review.get("submitted_at") if isinstance(review, dict) else None + review_id = review.get("id") if isinstance(review, dict) else None + if not isinstance(author, str) or author.lower() != current_reviewer.lower(): + return None + if not isinstance(commit_id, str) or not commit_id.strip(): + return None + if not isinstance(submitted_at, str): + return None + submitted_dt = parse_timestamp(submitted_at) + if current_cycle_boundary is None or submitted_dt is None or submitted_dt < current_cycle_boundary: + return None + if source_event_key != f"pull_request_review:{review_id}": + return None + return author, submitted_at, commit_id + + +def recommend_review_submission_gap_repair( + review_data: dict, + review: dict | None, + source_event_key: str, + *, + artifact_status: str | None, + current_cycle_boundary, +) -> dict[str, object] | None: + if review is None or artifact_status == "exact_artifact_match": + return None + repair = recommend_visible_review_repair( + review_data, + review, + source_event_key, + current_cycle_boundary=current_cycle_boundary, + ) + if repair is None: + return None + author, submitted_at, commit_id = repair + return { + "category": "review_submission_repair", + "payload": { + "author": author, + "submitted_at": submitted_at, + "commit_id": commit_id, + }, + } diff --git a/scripts/reviewer_bot_core/live_review_support.py b/scripts/reviewer_bot_core/live_review_support.py new file mode 100644 index 000000000..7897c977f --- /dev/null +++ b/scripts/reviewer_bot_core/live_review_support.py @@ -0,0 +1,165 @@ +"""Core-local live-review support helpers.""" + +from __future__ import annotations + +from datetime import datetime, timezone +from typing import Any + + +def get_current_cycle_boundary(review_data: dict, *, parse_timestamp) -> datetime | None: + for field in ("active_cycle_started_at", "cycle_started_at", "assigned_at"): + boundary = parse_timestamp(review_data.get(field)) + if boundary is not None: + return boundary + return None + + +def parse_github_timestamp(value: Any) -> datetime | None: + if not isinstance(value, str) or not value: + return None + try: + return datetime.fromisoformat(value.replace("Z", "+00:00")) + except ValueError: + return None + + +def projection_failure_result(reason: str, failure_kind: str | None = None) -> dict[str, object]: + return {"ok": False, "reason": reason, "failure_kind": failure_kind} + + +def _fallback_pull_request_payload(bot, issue_number: int) -> dict[str, object]: + payload = bot.github_api("GET", f"pulls/{issue_number}") + if isinstance(payload, dict): + return {"ok": True, "pull_request": payload} + return projection_failure_result("pull_request_unavailable") + + +def read_pull_request_result(bot, issue_number: int, pull_request: dict | None = None) -> dict[str, object]: + if pull_request is not None: + if isinstance(pull_request, dict): + return {"ok": True, "pull_request": pull_request} + return projection_failure_result("pull_request_unavailable", "invalid_payload") + try: + response = bot.github_api_request("GET", f"pulls/{issue_number}", retry_policy="idempotent_read") + except SystemExit: + return _fallback_pull_request_payload(bot, issue_number) + if not response.ok: + if response.failure_kind == "not_found": + return projection_failure_result("pull_request_not_found", response.failure_kind) + return projection_failure_result("pull_request_unavailable", response.failure_kind) + if not isinstance(response.payload, dict): + return projection_failure_result("pull_request_unavailable", "invalid_payload") + return {"ok": True, "pull_request": response.payload} + + +def _fallback_pull_request_reviews_result(bot, issue_number: int) -> dict[str, object]: + fallback_reviews = bot.get_pull_request_reviews(issue_number) + if isinstance(fallback_reviews, list): + return {"ok": True, "reviews": fallback_reviews} + + collected_reviews: list[dict] = [] + page = 1 + while True: + payload = bot.github_api("GET", f"pulls/{issue_number}/reviews?per_page=100&page={page}") + if not isinstance(payload, list): + return projection_failure_result("reviews_unavailable") + page_reviews = [review for review in payload if isinstance(review, dict)] + collected_reviews.extend(page_reviews) + if len(payload) < 100: + return {"ok": True, "reviews": collected_reviews} + page += 1 + + +def read_pull_request_reviews_result(bot, issue_number: int, reviews: list[dict] | None = None) -> dict[str, object]: + if reviews is not None: + return {"ok": True, "reviews": reviews} + collected_reviews: list[dict] = [] + page = 1 + while True: + try: + response = bot.github_api_request( + "GET", + f"pulls/{issue_number}/reviews?per_page=100&page={page}", + retry_policy="idempotent_read", + ) + except SystemExit: + return _fallback_pull_request_reviews_result(bot, issue_number) + if not response.ok: + return projection_failure_result("reviews_unavailable", response.failure_kind) + payload = response.payload + if not isinstance(payload, list): + return projection_failure_result("reviews_unavailable", "invalid_payload") + page_reviews = [review for review in payload if isinstance(review, dict)] + collected_reviews.extend(page_reviews) + if len(payload) < 100: + return {"ok": True, "reviews": collected_reviews} + page += 1 + + +def permission_status(bot, username: str, permission: str) -> str: + status = bot.github.get_user_permission_status(username, permission) + if status not in {"granted", "denied", "unavailable"}: + return "unavailable" + return status + + +def normalize_reviews_with_parsed_timestamps(reviews: list[dict], *, parse_timestamp) -> list[dict]: + normalized_reviews = [] + for review in reviews: + if not isinstance(review, dict): + normalized_reviews.append(review) + continue + normalized = dict(review) + normalized["submitted_at"] = parse_timestamp(review.get("submitted_at")) + normalized_reviews.append(normalized) + return normalized_reviews + + +def filter_current_head_reviews_for_cycle( + reviews: list[dict], + *, + boundary: datetime, + current_head: str, +) -> dict[str, dict]: + survivors: dict[str, dict] = {} + for review in reviews: + if not isinstance(review, dict): + continue + state = str(review.get("state", "")).upper() + if state == "DISMISSED": + continue + submitted_at = review.get("submitted_at") + if not isinstance(submitted_at, datetime) or submitted_at < boundary: + continue + commit_id = review.get("commit_id") + if not isinstance(commit_id, str) or not commit_id.strip() or commit_id.strip() != current_head: + continue + author = review.get("user", {}).get("login") + if not isinstance(author, str) or not author.strip(): + continue + review_id = str(review.get("id", "")) + key = author.lower() + candidate_key = (submitted_at, review_id) + current = survivors.get(key) + if current is None: + survivors[key] = review + continue + current_key = ( + current.get("submitted_at") or datetime.min.replace(tzinfo=timezone.utc), + str(current.get("id", "")), + ) + if candidate_key >= current_key: + survivors[key] = review + return survivors + + +def collect_permission_statuses(survivors: dict[str, dict], *, permission_status) -> dict[str, str]: + statuses: dict[str, str] = {} + for review in survivors.values(): + author = review.get("user", {}).get("login") + if not isinstance(author, str) or not author.strip(): + continue + cache_key = author.lower() + if cache_key not in statuses: + statuses[cache_key] = permission_status(author) + return statuses diff --git a/scripts/reviewer_bot_core/mandatory_approver_policy.py b/scripts/reviewer_bot_core/mandatory_approver_policy.py new file mode 100644 index 000000000..df3990794 --- /dev/null +++ b/scripts/reviewer_bot_core/mandatory_approver_policy.py @@ -0,0 +1,42 @@ +"""Mandatory-approver decision owner. + +Future changes that belong here: +- mandatory-approver escalation decisions from stored review state and already-read collaborator outcomes +- mandatory-approver satisfaction decisions from stored review state and caller-supplied approver identity + +Future changes that do not belong here: +- label writes, comment posts, or direct state mutation +- reviewer-response derivation + +Old module no longer preferred for these mandatory-approver decision changes: +- scripts/reviewer_bot_lib/reviews.py +""" + +from __future__ import annotations + + +def decide_mandatory_approver_escalation(review_data: dict, *, now: str, label_exists: bool) -> dict[str, object]: + require_escalation = not review_data.get("mandatory_approver_required") + return { + "allow": True, + "require_escalation": require_escalation, + "clear_satisfaction": require_escalation, + "attempt_label_apply": bool(label_exists), + "record_label_applied_at": bool(label_exists) and review_data.get("mandatory_approver_label_applied_at") is None, + "post_ping": review_data.get("mandatory_approver_pinged_at") is None, + "now": now, + } + + +def decide_mandatory_approver_satisfaction(review_data: dict, *, approver: str, now: str) -> dict[str, object]: + if not review_data.get("mandatory_approver_required"): + return {"allow": False} + if review_data.get("mandatory_approver_satisfied_at"): + return {"allow": False} + return { + "allow": True, + "approver": approver, + "now": now, + "attempt_label_remove": True, + "post_comment": True, + } diff --git a/scripts/reviewer_bot_core/privileged_command_policy.py b/scripts/reviewer_bot_core/privileged_command_policy.py new file mode 100644 index 000000000..6d7033167 --- /dev/null +++ b/scripts/reviewer_bot_core/privileged_command_policy.py @@ -0,0 +1,546 @@ +"""Privileged command validation and planning policy.""" + +from __future__ import annotations + +from copy import deepcopy +from dataclasses import asdict, dataclass +from enum import StrEnum + +ORDERED_EXECUTION_STEPS = [ + "working_tree_clean_check", + "audit_no_impact_check", + "spec_lock_update", + "changed_files_validation", + "branch_name_derivation", + "branch_existence_check", + "branch_creation", + "git_add_spec_lock", + "git_commit", + "git_push", + "pull_request_create", +] + +REVALIDATION_CHECKPOINTS = [ + "issue_not_pull_request", + "fls_audit_label_present", + "triage_permission_granted", + "working_tree_clean_before_update", + "audit_no_impact_before_update", + "changed_files_exact_after_update", +] + +COMMIT_MESSAGE = "chore: update spec.lock; no affected guidelines" +PR_TITLE = "chore: update spec.lock (no guideline impact)" + + +class PrivilegedCommandId(StrEnum): + ACCEPT_NO_FLS_CHANGES = "accept-no-fls-changes" + + +@dataclass(frozen=True) +class PendingAcceptNoFlsChangesRecord: + source_event_key: str + command_name: str + issue_number: int + actor: str + authorization_required_permission: str + authorization_authorized: bool + target_kind: str + target_number: int + target_labels_snapshot: tuple[str, ...] + status: str + created_at: str + completed_at: str | None = None + result_code: str | None = None + result_message: str | None = None + opened_pr_url: str | None = None + + +@dataclass(frozen=True) +class PrivilegedExecutionContext: + target_repo_root: str + base_branch: str + branch_probe_name: str + branch_name: str + expected_changed_files: tuple[str, ...] + existing_open_pr_url: str | None = None + + +@dataclass(frozen=True) +class BlockedPrivilegedHandoff: + reason: str + response: str + + +@dataclass(frozen=True) +class AllowedPrivilegedHandoff: + source_event_key: str + command_name: str + issue_number: int + actor: str + authorization_required_permission: str + authorization_authorized: bool + target_kind: str + target_number: int + target_labels_snapshot: tuple[str, ...] + + +@dataclass(frozen=True) +class BlockedPrivilegedExecution: + status: str = "failed_closed" + result_code: str = "authorization_failed" + result_message: str | None = None + opened_pr_url: None = None + + +@dataclass(frozen=True) +class CompletePrivilegedExecution: + status: str + result_code: str + result_message: str | None + opened_pr_url: str | None = None + + +@dataclass(frozen=True) +class ExecutePrivilegedPlan: + record: PendingAcceptNoFlsChangesRecord + execution_context: PrivilegedExecutionContext + + +def _blocked_privileged_execution(*, result_code: str, result_message: str | None = None) -> BlockedPrivilegedExecution: + return BlockedPrivilegedExecution(result_code=result_code, result_message=result_message) + + +def _pending_accept_no_fls_changes_record_from_request(request) -> PendingAcceptNoFlsChangesRecord: + labels = tuple(sorted(request.issue_labels)) + return PendingAcceptNoFlsChangesRecord( + source_event_key="", + command_name=PrivilegedCommandId.ACCEPT_NO_FLS_CHANGES.value, + issue_number=request.issue_number, + actor=request.actor, + authorization_required_permission="triage", + authorization_authorized=True, + target_kind="issue", + target_number=request.issue_number, + target_labels_snapshot=labels, + status="pending", + created_at="", + ) + + +def _accept_no_fls_changes_execution_context(*, target_repo_root: str) -> PrivilegedExecutionContext: + return PrivilegedExecutionContext( + target_repo_root=target_repo_root, + base_branch="", + branch_probe_name="", + branch_name="", + expected_changed_files=("src/spec.lock",), + ) + + +def _snapshot_issue_labels(issue_snapshot: dict) -> tuple[str, ...]: + labels: list[str] = [] + for label in issue_snapshot.get("labels", []): + if not isinstance(label, dict): + continue + name = label.get("name") + if isinstance(name, str): + labels.append(name) + return tuple(sorted(labels)) + + +def _sidecars(review_data: dict) -> dict: + sidecars = review_data.get("sidecars") + if not isinstance(sidecars, dict): + sidecars = {} + review_data["sidecars"] = sidecars + return sidecars + + +def get_pending_privileged_commands(review_data: dict) -> dict: + pending = _sidecars(review_data).get("pending_privileged_commands") + if not isinstance(pending, dict): + pending = {} + _sidecars(review_data)["pending_privileged_commands"] = pending + return pending + + +def put_pending_accept_no_fls_changes(review_data: dict, record: PendingAcceptNoFlsChangesRecord) -> None: + get_pending_privileged_commands(review_data)[record.source_event_key] = deepcopy(asdict(record)) + + +def load_pending_accept_no_fls_changes(review_data: dict, source_event_key: str) -> PendingAcceptNoFlsChangesRecord | None: + record = get_pending_privileged_commands(review_data).get(source_event_key) + return _coerce_pending_accept_no_fls_changes_record(record) + + +def _coerce_pending_accept_no_fls_changes_record(record: dict | None) -> PendingAcceptNoFlsChangesRecord | None: + if not isinstance(record, dict): + return None + try: + return PendingAcceptNoFlsChangesRecord( + source_event_key=str(record["source_event_key"]), + command_name=str(record["command_name"]), + issue_number=int(record["issue_number"]), + actor=str(record["actor"]), + authorization_required_permission=str(record["authorization_required_permission"]), + authorization_authorized=bool(record["authorization_authorized"]), + target_kind=str(record["target_kind"]), + target_number=int(record["target_number"]), + target_labels_snapshot=tuple(record["target_labels_snapshot"]), + status=str(record["status"]), + created_at=str(record["created_at"]), + completed_at=(str(record["completed_at"]) if record.get("completed_at") is not None else None), + result_code=(str(record["result_code"]) if record.get("result_code") is not None else None), + result_message=(str(record["result_message"]) if record.get("result_message") is not None else None), + opened_pr_url=(str(record["opened_pr_url"]) if record.get("opened_pr_url") is not None else None), + ) + except (KeyError, TypeError, ValueError): + return None + + +def find_pending_accept_no_fls_changes(state: dict, source_event_key: str) -> tuple[int, dict, PendingAcceptNoFlsChangesRecord] | None: + active_reviews = state.get("active_reviews") + if not isinstance(active_reviews, dict): + return None + for issue_key, review_data in active_reviews.items(): + if not isinstance(review_data, dict): + continue + record = _coerce_pending_accept_no_fls_changes_record( + get_pending_privileged_commands(review_data).get(source_event_key) + ) + if record is None: + continue + try: + issue_number = int(issue_key) + except (TypeError, ValueError): + issue_number = record.issue_number + return issue_number, review_data, record + return None + + +def mark_pending_accept_no_fls_changes_executed( + review_data: dict, + source_event_key: str, + *, + completed_at: str, + result: str, + result_message: str, + opened_pr_url: str | None = None, +) -> bool: + record = get_pending_privileged_commands(review_data).get(source_event_key) + if not isinstance(record, dict): + return False + record["status"] = "executed" + record["completed_at"] = completed_at + record["result_code"] = result + record["result_message"] = result_message + if opened_pr_url is not None: + record["opened_pr_url"] = opened_pr_url + return True + + +def mark_pending_accept_no_fls_changes_failed_closed( + review_data: dict, + source_event_key: str, + *, + completed_at: str, + result: str, + result_message: str | None = None, +) -> bool: + record = get_pending_privileged_commands(review_data).get(source_event_key) + if not isinstance(record, dict): + return False + record["status"] = "failed_closed" + record["completed_at"] = completed_at + record["result_code"] = result + if result_message is not None: + record["result_message"] = result_message + return True + + +@dataclass(frozen=True) +class AcceptNoFlsChangesPlan: + ordered_steps: list[str] + revalidation_checkpoints: list[str] + expected_changed_files: list[str] + branch_probe_name: str + branch_name: str + base_branch: str + add_paths: list[str] + git_checkout_args: list[str] + git_commit_args: list[str] + git_push_args: list[str] + commit_message: str + pull_request_title: str + pull_request_body: str + + def build_execution_context(self, *, target_repo_root: str, existing_open_pr_url: str | None = None) -> PrivilegedExecutionContext: + return PrivilegedExecutionContext( + target_repo_root=target_repo_root, + base_branch=self.base_branch, + branch_probe_name=self.branch_probe_name, + branch_name=self.branch_name, + expected_changed_files=tuple(self.expected_changed_files), + existing_open_pr_url=existing_open_pr_url, + ) + + +def validate_accept_no_fls_changes_handoff( + request, + permission_status: str, + *, + source_event_key: str = "", +) -> BlockedPrivilegedHandoff | AllowedPrivilegedHandoff: + if request.is_pull_request: + return BlockedPrivilegedHandoff( + reason="pull_request_target_not_allowed", + response="❌ This command can only be used on issues, not PRs.", + ) + labels = tuple(sorted(request.issue_labels)) + if "fls-audit" not in labels: + return BlockedPrivilegedHandoff( + reason="missing_fls_audit_label", + response="❌ This command is only available on issues labeled `fls-audit`.", + ) + if permission_status == "unavailable": + return BlockedPrivilegedHandoff( + reason="authorization_unavailable", + response="❌ Unable to verify triage permissions right now; refusing to run this command.", + ) + if permission_status != "granted": + return BlockedPrivilegedHandoff( + reason="authorization_failed", + response="❌ You must have triage permissions to run this command.", + ) + return AllowedPrivilegedHandoff( + source_event_key=source_event_key, + command_name=PrivilegedCommandId.ACCEPT_NO_FLS_CHANGES.value, + issue_number=request.issue_number, + actor=request.comment_author, + authorization_required_permission="triage", + authorization_authorized=True, + target_kind="issue", + target_number=request.issue_number, + target_labels_snapshot=labels, + ) + + +def build_pending_privileged_command(*, created_at: str, handoff: AllowedPrivilegedHandoff) -> PendingAcceptNoFlsChangesRecord: + return PendingAcceptNoFlsChangesRecord( + source_event_key=handoff.source_event_key, + command_name=handoff.command_name, + issue_number=handoff.issue_number, + actor=handoff.actor, + authorization_required_permission=handoff.authorization_required_permission, + authorization_authorized=handoff.authorization_authorized, + target_kind=handoff.target_kind, + target_number=handoff.target_number, + target_labels_snapshot=handoff.target_labels_snapshot, + status="pending", + created_at=created_at, + ) + + +def prevalidate_accept_no_fls_changes_request( + request, + permission_status: str, + changed_files_before: list[str], +) -> BlockedPrivilegedExecution | ExecutePrivilegedPlan: + if request.is_pull_request: + return _blocked_privileged_execution( + result_code="pull_request_target_not_allowed", + result_message="❌ This command can only be used on issues, not PRs.", + ) + labels = tuple(sorted(request.issue_labels)) + if "fls-audit" not in labels: + return _blocked_privileged_execution( + result_code="missing_fls_audit_label", + result_message="❌ This command is only available on issues labeled `fls-audit`.", + ) + if permission_status == "unavailable": + return _blocked_privileged_execution( + result_code="authorization_unavailable", + result_message="❌ Unable to verify triage permissions right now; refusing to run this command.", + ) + if permission_status != "granted": + return _blocked_privileged_execution( + result_code="authorization_failed", + result_message="❌ You must have triage permissions to run this command.", + ) + if changed_files_before: + return _blocked_privileged_execution( + result_code="working_tree_not_clean", + result_message="❌ Working tree is not clean; refusing to update spec.lock.", + ) + return ExecutePrivilegedPlan( + record=_pending_accept_no_fls_changes_record_from_request(request), + execution_context=_accept_no_fls_changes_execution_context(target_repo_root=""), + ) + + +def revalidate_pending_accept_no_fls_changes( + record: PendingAcceptNoFlsChangesRecord, + issue_snapshot: dict, + permission_status: str, + *, + target_repo_root: str, +) -> BlockedPrivilegedExecution | ExecutePrivilegedPlan: + if record.command_name != PrivilegedCommandId.ACCEPT_NO_FLS_CHANGES.value: + return _blocked_privileged_execution(result_code="unsupported_command") + if not isinstance(issue_snapshot, dict): + return _blocked_privileged_execution(result_code="unsupported_command") + if isinstance(issue_snapshot.get("pull_request"), dict): + return _blocked_privileged_execution( + result_code="pull_request_target_not_allowed", + result_message="❌ This command can only be used on issues, not PRs.", + ) + labels = _snapshot_issue_labels(issue_snapshot) + if "fls-audit" not in labels: + return _blocked_privileged_execution( + result_code="missing_fls_audit_label", + result_message="❌ This command is only available on issues labeled `fls-audit`.", + ) + if permission_status == "unavailable": + return _blocked_privileged_execution( + result_code="authorization_unavailable", + result_message="❌ Unable to verify triage permissions right now; refusing to run this command.", + ) + if permission_status != "granted": + return _blocked_privileged_execution( + result_code="authorization_failed", + result_message="❌ You must have triage permissions to run this command.", + ) + revalidated_record = PendingAcceptNoFlsChangesRecord( + source_event_key=record.source_event_key, + command_name=record.command_name, + issue_number=record.issue_number, + actor=record.actor, + authorization_required_permission=record.authorization_required_permission, + authorization_authorized=record.authorization_authorized, + target_kind=record.target_kind, + target_number=record.target_number, + target_labels_snapshot=labels, + status=record.status, + created_at=record.created_at, + completed_at=record.completed_at, + result_code=record.result_code, + result_message=record.result_message, + opened_pr_url=record.opened_pr_url, + ) + return ExecutePrivilegedPlan( + record=revalidated_record, + execution_context=_accept_no_fls_changes_execution_context(target_repo_root=target_repo_root), + ) + + +def derive_accept_no_fls_changes_branch_name( + *, + issue_number: int, + branch_date: str, + branch_suffix: str | None = None, +) -> str: + branch_name = f"chore/spec-lock-{branch_date}-issue-{issue_number}" + if branch_suffix: + branch_name = f"{branch_name}-{branch_suffix}" + return branch_name + + +def assess_accept_no_fls_changes_post_update( + *, + audit_returncode: int, + audit_details: str, + update_returncode: int, + update_details: str, + changed_files_after: list[str], +) -> BlockedPrivilegedExecution | CompletePrivilegedExecution | None: + if audit_returncode == 2: + return CompletePrivilegedExecution( + status="failed_closed", + result_code="audit_reported_guideline_impact", + result_message=( + "❌ The audit reports affected guidelines. Please review and open a PR with " + "the necessary guideline updates instead." + ), + ) + if audit_returncode != 0: + detail_text = f"\n\nDetails:\n```\n{audit_details}\n```" if audit_details else "" + return CompletePrivilegedExecution(status="failed_closed", result_code="audit_failed", result_message=f"❌ Audit command failed.{detail_text}") + if update_returncode != 0: + detail_text = f"\n\nDetails:\n```\n{update_details}\n```" if update_details else "" + return CompletePrivilegedExecution(status="failed_closed", result_code="update_failed", result_message=f"❌ Failed to update spec.lock.{detail_text}") + if not changed_files_after: + return CompletePrivilegedExecution(status="executed", result_code="already_up_to_date", result_message="✅ `src/spec.lock` is already up to date; no PR needed.") + unexpected = sorted(path for path in changed_files_after if path != "src/spec.lock") + if unexpected: + return CompletePrivilegedExecution( + status="failed_closed", + result_code="unexpected_changed_files", + result_message=( + "❌ Unexpected tracked file changes detected; refusing to open a PR. " + f"Please review: {', '.join(unexpected)}" + ), + ) + return None + + +def plan_accept_no_fls_changes_execution( + *, + issue_number: int, + audit_returncode: int, + audit_details: str, + update_returncode: int, + update_details: str, + changed_files_after: list[str], + branch_date: str, + base_branch: str, + branch_exists: bool, + branch_suffix: str | None = None, +) -> CompletePrivilegedExecution | AcceptNoFlsChangesPlan: + assessment = assess_accept_no_fls_changes_post_update( + audit_returncode=audit_returncode, + audit_details=audit_details, + update_returncode=update_returncode, + update_details=update_details, + changed_files_after=changed_files_after, + ) + if assessment is not None: + return assessment + branch_probe_name = derive_accept_no_fls_changes_branch_name( + issue_number=issue_number, + branch_date=branch_date, + ) + effective_suffix = branch_suffix if branch_exists else None + branch_name = derive_accept_no_fls_changes_branch_name( + issue_number=issue_number, + branch_date=branch_date, + branch_suffix=effective_suffix, + ) + plan = AcceptNoFlsChangesPlan( + ordered_steps=list(ORDERED_EXECUTION_STEPS), + revalidation_checkpoints=list(REVALIDATION_CHECKPOINTS), + expected_changed_files=["src/spec.lock"], + branch_probe_name=branch_probe_name, + branch_name=branch_name, + base_branch=base_branch, + add_paths=["src/spec.lock"], + git_checkout_args=["git", "checkout", "-b", branch_name], + git_commit_args=[ + "git", + "-c", + "user.name=guidelines-bot", + "-c", + "user.email=guidelines-bot@users.noreply.github.com", + "commit", + "-m", + COMMIT_MESSAGE, + ], + git_push_args=["git", "push", "origin", branch_name], + commit_message=COMMIT_MESSAGE, + pull_request_title=PR_TITLE, + pull_request_body=( + "Updates `src/spec.lock` after confirming the audit reported no affected guidelines.\n\n" + f"Closes #{issue_number}" + ), + ) + return plan diff --git a/scripts/reviewer_bot_core/reconcile_replay_policy.py b/scripts/reviewer_bot_core/reconcile_replay_policy.py new file mode 100644 index 000000000..4e33ff855 --- /dev/null +++ b/scripts/reviewer_bot_core/reconcile_replay_policy.py @@ -0,0 +1,167 @@ +"""Replay decision policy for deferred workflow_run reconcile. + +This module accepts normalized parsed payloads and normalized live-read results. +It does not fetch GitHub objects, inspect workflow files, or mutate review state. +""" + +from __future__ import annotations + +from dataclasses import dataclass + + +@dataclass(frozen=True) +class ObserverNoopDecision: + source_event_key: str + reason: str + + +@dataclass(frozen=True) +class CommentReplayDecision: + record_source_freshness: bool + replay_comment_command: bool + mark_reconciled: bool + clear_gap: bool + failed_closed_reason: str | None = None + diagnostic_summary: str | None = None + failure_kind: str | None = None + live_classified: dict | None = None + + +@dataclass(frozen=True) +class ReviewReplayDecision: + accept_reviewer_review: bool + accept_review_dismissal: bool + replay_timestamp: str | None + reviewed_head_sha: str | None + actor_login: str | None + mark_reconciled: bool + clear_gap: bool + + +def decide_observer_noop(*, source_event_key: str, reason: str) -> ObserverNoopDecision: + return ObserverNoopDecision(source_event_key=source_event_key, reason=reason) + + +def decide_comment_replay( + *, + comment_id: int, + source_comment_class: str, + source_has_non_command_text: bool, + source_freshness_eligible: bool, + live_comment_found: bool, + live_body_digest_matches: bool, + live_classified: dict | None, + live_failure_kind: str | None, + runbook_path: str, +) -> CommentReplayDecision: + if not live_comment_found: + if live_failure_kind == "not_found": + summary = ( + f"Deferred comment {comment_id} is no longer visible; source-time freshness only may be preserved. " + f"See {runbook_path}." + ) + else: + summary = ( + f"Deferred comment {comment_id} could not be validated from live GitHub data " + f"({live_failure_kind or 'unavailable'}); replay suppressed. See {runbook_path}." + ) + return CommentReplayDecision( + record_source_freshness=source_freshness_eligible, + replay_comment_command=False, + mark_reconciled=False, + clear_gap=False, + failed_closed_reason="reconcile_failed_closed", + diagnostic_summary=summary, + failure_kind=live_failure_kind, + ) + + if not live_body_digest_matches: + return CommentReplayDecision( + record_source_freshness=source_freshness_eligible, + replay_comment_command=False, + mark_reconciled=False, + clear_gap=False, + failed_closed_reason="reconcile_failed_closed", + diagnostic_summary=( + f"Deferred comment {comment_id} body digest changed; command execution suppressed. See {runbook_path}." + ), + ) + + if live_classified is None: + raise RuntimeError("Comment replay policy requires live classification when live comment exists") + live_comment_class = str(live_classified.get("comment_class", "")) + live_has_non_command_text = bool(live_classified.get("has_non_command_text")) + + if live_comment_class != source_comment_class: + return CommentReplayDecision( + record_source_freshness=source_freshness_eligible, + replay_comment_command=False, + mark_reconciled=False, + clear_gap=False, + failed_closed_reason="reconcile_failed_closed", + diagnostic_summary=( + f"Deferred comment {comment_id} classification changed from {source_comment_class} to {live_comment_class}; replay suppressed. See {runbook_path}." + ), + ) + if live_has_non_command_text != source_has_non_command_text: + return CommentReplayDecision( + record_source_freshness=source_freshness_eligible, + replay_comment_command=False, + mark_reconciled=False, + clear_gap=False, + failed_closed_reason="reconcile_failed_closed", + diagnostic_summary=( + f"Deferred comment {comment_id} non-command text classification drifted; replay suppressed. See {runbook_path}." + ), + ) + if source_comment_class in {"command_only", "command_plus_text"} and int(live_classified.get("command_count", 0)) != 1: + return CommentReplayDecision( + record_source_freshness=source_freshness_eligible, + replay_comment_command=False, + mark_reconciled=False, + clear_gap=False, + failed_closed_reason="reconcile_failed_closed", + diagnostic_summary=( + f"Deferred comment {comment_id} no longer resolves to exactly one command; replay suppressed. See {runbook_path}." + ), + ) + return CommentReplayDecision( + record_source_freshness=source_freshness_eligible, + replay_comment_command=source_comment_class in {"command_only", "command_plus_text"}, + mark_reconciled=True, + clear_gap=True, + live_classified=live_classified, + ) + + +def decide_review_submitted_replay( + *, + source_event_key: str, + actor_login: str, + current_reviewer: str | None, + live_commit_id: str | None, + live_submitted_at: str | None, +) -> ReviewReplayDecision: + actor_matches = isinstance(current_reviewer, str) and current_reviewer.lower() == actor_login.lower() + accept_reviewer_review = actor_matches and isinstance(live_commit_id, str) and isinstance(live_submitted_at, str) + return ReviewReplayDecision( + accept_reviewer_review=accept_reviewer_review, + accept_review_dismissal=False, + replay_timestamp=live_submitted_at, + reviewed_head_sha=live_commit_id, + actor_login=actor_login, + mark_reconciled=True, + clear_gap=True, + ) + + +def decide_review_dismissed_replay(*, source_event_key: str, timestamp: str) -> ReviewReplayDecision: + return ReviewReplayDecision( + accept_reviewer_review=False, + accept_review_dismissal=True, + replay_timestamp=timestamp, + reviewed_head_sha=None, + actor_login=None, + mark_reconciled=True, + clear_gap=True, + ) diff --git a/scripts/reviewer_bot_core/review_state_live_repair.py b/scripts/reviewer_bot_core/review_state_live_repair.py new file mode 100644 index 000000000..d49f63837 --- /dev/null +++ b/scripts/reviewer_bot_core/review_state_live_repair.py @@ -0,0 +1,89 @@ +"""Live-read-assisted review repair owner. + +Future changes that belong here: +- applying already-selected live reviewer reviews into persisted reviewer_review state +- activity refresh and repair decisions once live review inputs are available + +Future changes that do not belong here: +- persisted entry defaulting or local-only state mutation rules +- raw GitHub runtime access or direct review fetching + +Old module no longer preferred for these live-read-assisted repair changes: +- scripts/reviewer_bot_core/review_state_machine.py +""" + +from __future__ import annotations + +from . import live_review_support, review_state_machine, reviewer_review_helpers + + +def accept_reviewer_review_from_live_review(review_data: dict, review: dict, *, actor: str | None = None) -> bool: + record = reviewer_review_helpers.build_reviewer_review_record_from_live_review(review, actor=actor) + if record is None: + return False + return review_state_machine.accept_channel_event( + review_data, + "reviewer_review", + semantic_key=record["semantic_key"], + timestamp=record["timestamp"], + actor=record["actor"], + reviewed_head_sha=record["reviewed_head_sha"], + source_precedence=record["source_precedence"], + payload=record["payload"], + ) + + +def refresh_reviewer_review_from_live_preferred_review( + bot, + issue_number: int, + review_data: dict, + *, + pull_request: dict | None = None, + reviews: list[dict] | None = None, + actor: str | None = None, +) -> tuple[bool, dict | None]: + if pull_request is None: + pull_request_result = live_review_support.read_pull_request_result(bot, issue_number) + if not pull_request_result.get("ok"): + return False, None + pull_request = pull_request_result["pull_request"] + preferred_review = reviewer_review_helpers.get_preferred_current_reviewer_review_for_cycle( + bot, + issue_number, + review_data, + pull_request=pull_request, + reviews=reviews, + ) + if preferred_review is None: + return False, None + record = reviewer_review_helpers.build_reviewer_review_record_from_live_review( + preferred_review, + actor=actor or review_data.get("current_reviewer"), + ) + if record is None: + return False, None + changed = review_state_machine.upsert_channel_accepted_record(review_data, "reviewer_review", record) + submitted_at = preferred_review.get("submitted_at") + if isinstance(submitted_at, str): + previous_activity = review_data.get("last_reviewer_activity") + previous_warning = review_data.get("transition_warning_sent") + previous_notice = review_data.get("transition_notice_sent_at") + review_state_machine.record_reviewer_activity(review_data, submitted_at) + activity_changed = ( + previous_activity != review_data.get("last_reviewer_activity") + or previous_warning != review_data.get("transition_warning_sent") + or previous_notice != review_data.get("transition_notice_sent_at") + ) + changed = changed or activity_changed + return changed, preferred_review + + +def repair_missing_reviewer_review_state(bot, issue_number: int, review_data: dict, *, reviews: list[dict] | None = None) -> bool: + changed, _ = refresh_reviewer_review_from_live_preferred_review( + bot, + issue_number, + review_data, + reviews=reviews, + actor=review_data.get("current_reviewer"), + ) + return changed diff --git a/scripts/reviewer_bot_core/review_state_machine.py b/scripts/reviewer_bot_core/review_state_machine.py new file mode 100644 index 000000000..106a2db5a --- /dev/null +++ b/scripts/reviewer_bot_core/review_state_machine.py @@ -0,0 +1,221 @@ +"""Core owner for local-state-only review mutation. + +Future changes that belong here: +- review-entry defaulting and compatibility upgrades for local mutation APIs +- channel-event acceptance and semantic-key tracking +- reviewer activity updates, completion marking, and cycle-local resets + +Future changes that do not belong here: +- live GitHub review reads or preferred-review selection +- replay policy, approval policy, sweeper diagnosis, or privileged planning + +Old module no longer preferred for these local-state-only behaviors: +- scripts/reviewer_bot_lib/review_state.py +""" + +from __future__ import annotations + +from datetime import datetime, timezone +from typing import Any + +from . import state_adapters +from .live_review_support import parse_github_timestamp + + +def _ensure_channel_map(review_entry: dict[str, Any], name: str) -> dict[str, Any]: + value = review_entry.get(name) + if not isinstance(value, dict): + value = {"accepted": None, "seen_keys": []} + review_entry[name] = value + if not isinstance(value.get("seen_keys"), list): + value["seen_keys"] = [] + return value + + +def ensure_review_entry(state: dict, issue_number: int, create: bool = False) -> dict | None: + issue_key = str(issue_number) + if "active_reviews" not in state or not isinstance(state.get("active_reviews"), dict): + state["active_reviews"] = {} + review_entry = state["active_reviews"].get(issue_key) + if review_entry is None: + if not create: + return None + review_entry = {} + state["active_reviews"][issue_key] = review_entry + elif isinstance(review_entry, list): + review_entry = {"skipped": review_entry} + state["active_reviews"][issue_key] = review_entry + if not isinstance(review_entry, dict): + return None + + core_entry = state_adapters.review_entry_from_persisted(review_entry) + if core_entry is None: + return None + state_adapters.apply_local_state_core_to_persisted(review_entry, core_entry) + state_adapters.ensure_sidecar_subtree(review_entry, state_last_updated=state.get("last_updated")) + return review_entry + + +def clear_transition_timers(review_data: dict) -> None: + review_data["transition_warning_sent"] = None + review_data["transition_notice_sent_at"] = None + + +def record_reviewer_activity(review_data: dict, timestamp: str) -> bool: + current = parse_github_timestamp(review_data.get("last_reviewer_activity")) + candidate = parse_github_timestamp(timestamp) + if candidate is None: + return False + if current is None or candidate > current: + review_data["last_reviewer_activity"] = timestamp + clear_transition_timers(review_data) + return True + return False + + +def record_transition_notice_sent(review_data: dict, timestamp: str) -> None: + review_data["transition_notice_sent_at"] = timestamp + + +def _compare_records(left: dict | None, right: dict | None) -> int: + if right is None: + return 1 + if left is None: + return -1 + left_time = parse_github_timestamp(left.get("timestamp")) or datetime.min.replace(tzinfo=timezone.utc) + right_time = parse_github_timestamp(right.get("timestamp")) or datetime.min.replace(tzinfo=timezone.utc) + left_rank = int(left.get("source_precedence", 0)) + right_rank = int(right.get("source_precedence", 0)) + left_key = str(left.get("semantic_key", "")) + right_key = str(right.get("semantic_key", "")) + left_tuple = (left_time, left_rank, left_key) + right_tuple = (right_time, right_rank, right_key) + if left_tuple > right_tuple: + return 1 + if left_tuple < right_tuple: + return -1 + return 0 + + +def semantic_key_seen(review_data: dict, channel_name: str, semantic_key: str) -> bool: + channel = _ensure_channel_map(review_data, channel_name) + return semantic_key in channel["seen_keys"] + + +def accept_channel_event( + review_data: dict, + channel_name: str, + *, + semantic_key: str, + timestamp: str, + actor: str | None = None, + reviewed_head_sha: str | None = None, + source_precedence: int = 0, + payload: dict | None = None, + dismissal_only: bool = False, +) -> bool: + channel = _ensure_channel_map(review_data, channel_name) + if semantic_key in channel["seen_keys"]: + return False + channel["seen_keys"].append(semantic_key) + if dismissal_only: + channel["accepted"] = channel.get("accepted") or { + "semantic_key": semantic_key, + "timestamp": timestamp, + } + return True + candidate = { + "semantic_key": semantic_key, + "timestamp": timestamp, + "actor": actor, + "reviewed_head_sha": reviewed_head_sha, + "source_precedence": source_precedence, + "payload": payload or {}, + } + current = channel.get("accepted") + if _compare_records(candidate, current) >= 0: + channel["accepted"] = candidate + return True + + +def upsert_channel_accepted_record(review_data: dict, channel_name: str, record: dict) -> bool: + channel = _ensure_channel_map(review_data, channel_name) + changed = False + if record["semantic_key"] not in channel["seen_keys"]: + channel["seen_keys"].append(record["semantic_key"]) + changed = True + if channel.get("accepted") != record: + channel["accepted"] = record + changed = True + return changed + + +def _reset_cycle_state(review_data: dict) -> None: + for channel in ( + "reviewer_comment", + "reviewer_review", + "contributor_comment", + "contributor_revision", + "review_dismissal", + ): + review_data[channel] = {"accepted": None, "seen_keys": []} + review_data["current_cycle_completion"] = {} + review_data["current_cycle_write_approval"] = {} + review_data["overdue_anchor"] = None + + +def set_current_reviewer( + state: dict, + issue_number: int, + reviewer: str, + *, + now: str, + assignment_method: str = "round-robin", +) -> None: + review_data = ensure_review_entry(state, issue_number, create=True) + if review_data is None: + return + review_data["current_reviewer"] = reviewer + review_data["cycle_started_at"] = now + review_data["active_cycle_started_at"] = now + review_data["assigned_at"] = now + record_reviewer_activity(review_data, now) + review_data["assignment_method"] = assignment_method + review_data["review_completed_at"] = None + review_data["review_completed_by"] = None + review_data["review_completion_source"] = None + review_data["mandatory_approver_required"] = False + review_data["mandatory_approver_label_applied_at"] = None + review_data["mandatory_approver_pinged_at"] = None + review_data["mandatory_approver_satisfied_by"] = None + review_data["mandatory_approver_satisfied_at"] = None + review_data["active_head_sha"] = None + _reset_cycle_state(review_data) + + +def update_reviewer_activity(state: dict, issue_number: int, reviewer: str, *, now: str) -> bool: + review_data = ensure_review_entry(state, issue_number) + if review_data is None: + return False + current_reviewer = review_data.get("current_reviewer") + if not isinstance(current_reviewer, str) or current_reviewer.lower() != reviewer.lower(): + return False + record_reviewer_activity(review_data, now) + return True + + +def mark_review_complete(state: dict, issue_number: int, reviewer: str | None, source: str, *, completed_at: str) -> bool: + review_data = ensure_review_entry(state, issue_number, create=True) + if review_data is None: + return False + review_data["review_completed_at"] = completed_at + review_data["review_completed_by"] = reviewer or None + review_data["review_completion_source"] = source + record_reviewer_activity(review_data, completed_at) + review_data["current_cycle_completion"] = { + "completed": True, + "completed_at": completed_at, + "source": source, + "reviewer": reviewer, + } + return True diff --git a/scripts/reviewer_bot_core/review_state_types.py b/scripts/reviewer_bot_core/review_state_types.py new file mode 100644 index 000000000..d5c13f5b9 --- /dev/null +++ b/scripts/reviewer_bot_core/review_state_types.py @@ -0,0 +1,81 @@ +"""Minimal typed review-state structures for the future C1 mutation cutover. + +These types cover only the persisted review-entry fields currently needed for: +- review-entry initialization and defaulting +- channel-event acceptance and semantic-key tracking +- reviewer activity updates +- completion marking +- cycle-boundary behavior + +They intentionally do not model deferred-gap diagnosis, replay bookkeeping, +approval policy, comment policy, or privileged-command planning. +""" + +from __future__ import annotations + +from dataclasses import dataclass, field +from typing import Any + + +@dataclass +class AcceptedChannelRecord: + """Maps to the persisted accepted channel record for non-dismissal events.""" + + semantic_key: str + timestamp: str + actor: str | None = None + reviewed_head_sha: str | None = None + source_precedence: int = 0 + payload: dict[str, Any] = field(default_factory=dict) + + +@dataclass +class DismissalAcceptedRecord: + """Maps to dismissal-only accepted channel records with the current sparse shape.""" + + semantic_key: str + timestamp: str + + +@dataclass +class ReviewChannelState: + """Maps to one persisted per-channel map with accepted and seen_keys entries.""" + + accepted: AcceptedChannelRecord | DismissalAcceptedRecord | None = None + seen_keys: list[str] = field(default_factory=list) + + +@dataclass +class ReviewEntryState: + """Maps to the persisted review entry fields used by the future C1 cutover. + + Included fields mirror current mutation semantics in `review_state.py` without + cleaning up persisted names or collapsing precedence behavior. + """ + + skipped: list[str] = field(default_factory=list) + current_reviewer: str | None = None + cycle_started_at: str | None = None + active_cycle_started_at: str | None = None + assigned_at: str | None = None + active_head_sha: str | None = None + last_reviewer_activity: str | None = None + transition_warning_sent: str | None = None + transition_notice_sent_at: str | None = None + assignment_method: str | None = None + review_completed_at: str | None = None + review_completed_by: str | None = None + review_completion_source: str | None = None + mandatory_approver_required: bool = False + mandatory_approver_label_applied_at: str | None = None + mandatory_approver_pinged_at: str | None = None + mandatory_approver_satisfied_by: str | None = None + mandatory_approver_satisfied_at: str | None = None + overdue_anchor: Any | None = None + reviewer_comment: ReviewChannelState = field(default_factory=ReviewChannelState) + reviewer_review: ReviewChannelState = field(default_factory=ReviewChannelState) + contributor_comment: ReviewChannelState = field(default_factory=ReviewChannelState) + contributor_revision: ReviewChannelState = field(default_factory=ReviewChannelState) + review_dismissal: ReviewChannelState = field(default_factory=ReviewChannelState) + current_cycle_completion: dict[str, Any] = field(default_factory=dict) + current_cycle_write_approval: dict[str, Any] = field(default_factory=dict) diff --git a/scripts/reviewer_bot_core/reviewer_response_policy.py b/scripts/reviewer_bot_core/reviewer_response_policy.py new file mode 100644 index 000000000..718455199 --- /dev/null +++ b/scripts/reviewer_bot_core/reviewer_response_policy.py @@ -0,0 +1,338 @@ +"""Reviewer-response derivation owner. + +Future changes that belong here: +- reviewer-response derivation from stored review state plus already-fetched live PR inputs +- contributor handoff and stale-review response decisions + +Future changes that do not belong here: +- label writes, issue writes, or projection application +- mandatory approver escalation + +Old module no longer preferred for these reviewer-response decision changes: +- scripts/reviewer_bot_lib/reviews.py +""" + +from __future__ import annotations + +from datetime import datetime, timezone + +from . import live_review_support, reviewer_review_helpers + +_UNSET = object() + + +def _record_timestamp(record: dict | None, *, parse_timestamp) -> datetime | None: + if not isinstance(record, dict): + return None + return parse_timestamp(record.get("timestamp")) + + +def _compare_cross_channel_conversation(contributor: dict | None, reviewer: dict | None, *, parse_timestamp) -> int: + contributor_time = _record_timestamp(contributor, parse_timestamp=parse_timestamp) or datetime.min.replace( + tzinfo=timezone.utc + ) + reviewer_time = _record_timestamp(reviewer, parse_timestamp=parse_timestamp) or datetime.min.replace( + tzinfo=timezone.utc + ) + contributor_key = str((contributor or {}).get("semantic_key", "")) + reviewer_key = str((reviewer or {}).get("semantic_key", "")) + if (contributor_time, contributor_key) == (reviewer_time, reviewer_key): + return 0 + if contributor_time > reviewer_time: + return 1 + if contributor_time < reviewer_time: + return -1 + if contributor_key >= reviewer_key: + return 1 + return -1 + + +def _initial_reviewer_anchor(review_data: dict) -> str | None: + for field in ("active_cycle_started_at", "cycle_started_at", "assigned_at"): + value = review_data.get(field) + if isinstance(value, str) and value: + return value + return None + + +def _contributor_revision_handoff_record(review_data: dict, current_head: str | None, reviewer_review: dict | None) -> dict | None: + contributor_revision = review_data.get("contributor_revision", {}).get("accepted") + if not isinstance(contributor_revision, dict): + return None + revision_head = contributor_revision.get("reviewed_head_sha") + if not isinstance(revision_head, str) or not isinstance(current_head, str): + return None + if revision_head != current_head: + return None + reviewer_head = reviewer_review.get("reviewed_head_sha") if isinstance(reviewer_review, dict) else None + if isinstance(reviewer_head, str) and reviewer_head == current_head: + return None + return contributor_revision + + +def derive_reviewer_response_state( + review_data: dict, + *, + issue_is_pull_request: bool, + current_head: str | None = None, + reviewer_comment: dict | None | object = _UNSET, + reviewer_review: dict | None | object = _UNSET, + contributor_comment: dict | None | object = _UNSET, + had_reviewer_review: bool = False, + approval_result: dict[str, object] | None = None, +) -> dict[str, object]: + current_reviewer = review_data.get("current_reviewer") + if not isinstance(current_reviewer, str) or not current_reviewer.strip(): + return {"state": "untracked", "reason": "no_current_reviewer"} + + if reviewer_comment is _UNSET: + reviewer_comment = review_data.get("reviewer_comment", {}).get("accepted") + if reviewer_review is _UNSET: + reviewer_review = review_data.get("reviewer_review", {}).get("accepted") + if contributor_comment is _UNSET: + contributor_comment = review_data.get("contributor_comment", {}).get("accepted") + + if not issue_is_pull_request: + if not reviewer_comment and not reviewer_review: + return { + "state": "awaiting_reviewer_response", + "reason": "no_reviewer_activity", + "anchor_timestamp": _initial_reviewer_anchor(review_data), + "reviewer_comment": reviewer_comment, + "reviewer_review": reviewer_review, + "contributor_comment": contributor_comment, + "contributor_handoff": None, + } + latest_reviewer_response = reviewer_comment + if reviewer_review_helpers.compare_records( + reviewer_review, + latest_reviewer_response, + parse_timestamp=live_review_support.parse_github_timestamp, + ) > 0: + latest_reviewer_response = reviewer_review + completion = review_data.get("current_cycle_completion") + if not isinstance(completion, dict) or not completion.get("completed"): + if review_data.get("review_completed_at"): + return {"state": "done", "reason": None} + return { + "state": "awaiting_contributor_response", + "reason": "completion_missing", + "anchor_timestamp": latest_reviewer_response.get("timestamp") if isinstance(latest_reviewer_response, dict) else None, + } + return {"state": "done", "reason": None} + + if not isinstance(current_head, str) or not current_head.strip(): + return {"state": "projection_failed", "reason": "pull_request_head_unavailable"} + + if not reviewer_comment and not reviewer_review: + if not had_reviewer_review: + return { + "state": "awaiting_reviewer_response", + "reason": "no_reviewer_activity", + "anchor_timestamp": _initial_reviewer_anchor(review_data), + "reviewer_comment": reviewer_comment, + "reviewer_review": reviewer_review, + "contributor_comment": contributor_comment, + "contributor_handoff": None, + } + + latest_reviewer_response = reviewer_comment + if reviewer_review_helpers.compare_records( + reviewer_review, + latest_reviewer_response, + parse_timestamp=live_review_support.parse_github_timestamp, + ) > 0: + latest_reviewer_response = reviewer_review + + contributor_handoff = contributor_comment + contributor_revision = _contributor_revision_handoff_record( + review_data, + current_head, + reviewer_review if isinstance(reviewer_review, dict) else None, + ) + if reviewer_review_helpers.compare_records( + contributor_revision, + contributor_handoff, + parse_timestamp=live_review_support.parse_github_timestamp, + ) > 0: + contributor_handoff = contributor_revision + + if _compare_cross_channel_conversation( + contributor_handoff, + latest_reviewer_response, + parse_timestamp=live_review_support.parse_github_timestamp, + ) > 0: + reason = "contributor_comment_newer" + if isinstance(contributor_handoff, dict) and str(contributor_handoff.get("semantic_key", "")).startswith( + "pull_request_" + ): + reason = "contributor_revision_newer" + return { + "state": "awaiting_reviewer_response", + "reason": reason, + "anchor_timestamp": contributor_handoff.get("timestamp") if isinstance(contributor_handoff, dict) else None, + "current_head_sha": current_head, + "reviewer_comment": reviewer_comment, + "reviewer_review": reviewer_review, + "contributor_comment": contributor_comment, + "contributor_handoff": contributor_handoff, + } + + latest_review_head = reviewer_review.get("reviewed_head_sha") if isinstance(reviewer_review, dict) else None + if not isinstance(latest_review_head, str) or latest_review_head != current_head: + return { + "state": "awaiting_reviewer_response", + "reason": "review_head_stale", + "anchor_timestamp": contributor_handoff.get("timestamp") if isinstance(contributor_handoff, dict) else _initial_reviewer_anchor(review_data), + "current_head_sha": current_head, + "reviewer_comment": reviewer_comment, + "reviewer_review": reviewer_review, + "contributor_comment": contributor_comment, + "contributor_handoff": contributor_handoff, + } + + if not isinstance(approval_result, dict) or not approval_result.get("ok"): + return {"state": "projection_failed", "reason": "live_review_state_unknown"} + + completion = approval_result["completion"] + write_approval = approval_result["write_approval"] + if not completion.get("completed"): + return { + "state": "awaiting_contributor_response", + "reason": "completion_missing", + "anchor_timestamp": latest_reviewer_response.get("timestamp") if isinstance(latest_reviewer_response, dict) else None, + "current_head_sha": current_head, + "reviewer_comment": reviewer_comment, + "reviewer_review": reviewer_review, + "contributor_comment": contributor_comment, + "contributor_handoff": contributor_handoff, + } + if not write_approval.get("has_write_approval"): + return { + "state": "awaiting_write_approval", + "reason": "write_approval_missing", + "anchor_timestamp": latest_reviewer_response.get("timestamp") if isinstance(latest_reviewer_response, dict) else None, + "current_head_sha": current_head, + "reviewer_comment": reviewer_comment, + "reviewer_review": reviewer_review, + "contributor_comment": contributor_comment, + "contributor_handoff": contributor_handoff, + } + return { + "state": "done", + "reason": "write_approval_present", + "anchor_timestamp": latest_reviewer_response.get("timestamp") if isinstance(latest_reviewer_response, dict) else None, + "current_head_sha": current_head, + "reviewer_comment": reviewer_comment, + "reviewer_review": reviewer_review, + "contributor_comment": contributor_comment, + "contributor_handoff": contributor_handoff, + } + + +def compute_reviewer_response_state( + bot, + issue_number: int, + review_data: dict, + *, + issue_snapshot: dict | None = None, + pull_request: dict | None = None, + reviews: list[dict] | None = None, +) -> dict[str, object]: + if issue_snapshot is None: + issue_snapshot = bot.github.get_issue_or_pr_snapshot(issue_number) + if not isinstance(issue_snapshot, dict): + return {"state": "projection_failed", "reason": "issue_snapshot_unavailable"} + is_pr = isinstance(issue_snapshot.get("pull_request"), dict) + + reviewer_comment = review_data.get("reviewer_comment", {}).get("accepted") + reviewer_review = review_data.get("reviewer_review", {}).get("accepted") + contributor_comment = review_data.get("contributor_comment", {}).get("accepted") + had_reviewer_review = isinstance(reviewer_review, dict) + + if not is_pr: + return derive_reviewer_response_state( + review_data, + issue_is_pull_request=False, + reviewer_comment=reviewer_comment, + reviewer_review=reviewer_review, + contributor_comment=contributor_comment, + had_reviewer_review=had_reviewer_review, + ) + + pull_request_result = live_review_support.read_pull_request_result(bot, issue_number, pull_request) + if not pull_request_result.get("ok"): + return {"state": "projection_failed", "reason": str(pull_request_result.get("reason"))} + pull_request = pull_request_result["pull_request"] + head = pull_request.get("head") + current_head = head.get("sha") if isinstance(head, dict) else None + if not isinstance(current_head, str) or not current_head.strip(): + return {"state": "projection_failed", "reason": "pull_request_head_unavailable"} + + if not reviewer_comment and not reviewer_review: + reviews_result = live_review_support.read_pull_request_reviews_result(bot, issue_number, reviews) + if not reviews_result.get("ok"): + return {"state": "projection_failed", "reason": str(reviews_result.get("reason"))} + reviews = reviews_result["reviews"] + preferred_live_review = reviewer_review_helpers.get_preferred_current_reviewer_review_for_cycle( + bot, + issue_number, + review_data, + pull_request=pull_request, + reviews=reviews, + ) + if preferred_live_review is not None: + reviewer_review = reviewer_review_helpers.build_reviewer_review_record_from_live_review( + preferred_live_review, + actor=review_data.get("current_reviewer"), + ) + + stored_review_head = reviewer_review.get("reviewed_head_sha") if isinstance(reviewer_review, dict) else None + refresh_live_review = reviews is not None or reviewer_review is None + if not refresh_live_review: + refresh_live_review = not isinstance(stored_review_head, str) or stored_review_head != current_head + + preferred_live_review = None + if refresh_live_review: + reviews_result = live_review_support.read_pull_request_reviews_result(bot, issue_number, reviews) + if not reviews_result.get("ok"): + return {"state": "projection_failed", "reason": str(reviews_result.get("reason"))} + reviews = reviews_result["reviews"] + preferred_live_review = reviewer_review_helpers.get_preferred_current_reviewer_review_for_cycle( + bot, + issue_number, + review_data, + pull_request=pull_request, + reviews=reviews, + ) + if preferred_live_review is not None: + reviewer_review = reviewer_review_helpers.build_reviewer_review_record_from_live_review( + preferred_live_review, + actor=review_data.get("current_reviewer"), + ) + elif refresh_live_review: + reviewer_review = None + + approval_result = None + reviewed_head_sha = reviewer_review.get("reviewed_head_sha") if isinstance(reviewer_review, dict) else None + if isinstance(reviewed_head_sha, str) and reviewed_head_sha == current_head: + from scripts.reviewer_bot_core import approval_policy + + approval_result = approval_policy.compute_pr_approval_state_result( + bot, + issue_number, + review_data, + pull_request=pull_request, + reviews=reviews, + ) + + return derive_reviewer_response_state( + review_data, + issue_is_pull_request=True, + current_head=current_head, + reviewer_comment=reviewer_comment, + reviewer_review=reviewer_review, + contributor_comment=contributor_comment, + had_reviewer_review=had_reviewer_review, + approval_result=approval_result, + ) diff --git a/scripts/reviewer_bot_core/reviewer_review_helpers.py b/scripts/reviewer_bot_core/reviewer_review_helpers.py new file mode 100644 index 000000000..ea5604794 --- /dev/null +++ b/scripts/reviewer_bot_core/reviewer_review_helpers.py @@ -0,0 +1,131 @@ +from __future__ import annotations + +from datetime import datetime, timezone + +from . import live_review_support + + +def compare_records( + left: dict | None, + right: dict | None, + *, + parse_timestamp, +) -> int: + if right is None: + return 1 + if left is None: + return -1 + left_time = parse_timestamp(left.get("timestamp")) or datetime.min.replace(tzinfo=timezone.utc) + right_time = parse_timestamp(right.get("timestamp")) or datetime.min.replace(tzinfo=timezone.utc) + left_rank = int(left.get("source_precedence", 0)) + right_rank = int(right.get("source_precedence", 0)) + left_key = str(left.get("semantic_key", "")) + right_key = str(right.get("semantic_key", "")) + left_tuple = (left_time, left_rank, left_key) + right_tuple = (right_time, right_rank, right_key) + if left_tuple > right_tuple: + return 1 + if left_tuple < right_tuple: + return -1 + return 0 + + +def _review_sort_key(bot, review: dict) -> tuple[datetime, str]: + return ( + live_review_support.parse_github_timestamp(review.get("submitted_at")) or datetime.min.replace(tzinfo=timezone.utc), + str(review.get("id", "")), + ) + + +def _review_matches_head(review: dict, current_head: str | None) -> bool: + commit_id = review.get("commit_id") if isinstance(review, dict) else None + return isinstance(commit_id, str) and isinstance(current_head, str) and commit_id.strip() == current_head.strip() + + +def get_valid_current_reviewer_reviews_for_cycle( + bot, + issue_number: int, + review_data: dict, + *, + current_cycle_boundary, + reviews: list[dict] | None = None, +) -> list[dict]: + current_reviewer = review_data.get("current_reviewer") + if not isinstance(current_reviewer, str) or not current_reviewer.strip(): + return [] + if current_cycle_boundary is None: + return [] + if reviews is None: + reviews = bot.github.get_pull_request_reviews(issue_number) + if reviews is None: + return [] + valid_reviews: list[dict] = [] + for review in reviews: + if not isinstance(review, dict): + continue + author = review.get("user", {}).get("login") + if not isinstance(author, str) or author.lower() != current_reviewer.lower(): + continue + state = str(review.get("state", "")).upper() + if state not in {"APPROVED", "COMMENTED", "CHANGES_REQUESTED"}: + continue + submitted_at = live_review_support.parse_github_timestamp(review.get("submitted_at")) + if submitted_at is None or submitted_at < current_cycle_boundary: + continue + commit_id = review.get("commit_id") + if not isinstance(commit_id, str) or not commit_id.strip(): + continue + valid_reviews.append(review) + return valid_reviews + + +def get_preferred_current_reviewer_review_for_cycle( + bot, + issue_number: int, + review_data: dict, + *, + pull_request: dict | None = None, + reviews: list[dict] | None = None, +) -> dict | None: + from . import live_review_support + + valid_reviews = get_valid_current_reviewer_reviews_for_cycle( + bot, + issue_number, + review_data, + current_cycle_boundary=live_review_support.get_current_cycle_boundary( + review_data, + parse_timestamp=bot.parse_iso8601_timestamp, + ), + reviews=reviews, + ) + if not valid_reviews: + return None + if len(valid_reviews) == 1: + return valid_reviews[0] + head = pull_request.get("head") if isinstance(pull_request, dict) else None + current_head = head.get("sha") if isinstance(head, dict) else None + current_head_reviews = [review for review in valid_reviews if _review_matches_head(review, current_head)] + candidates = current_head_reviews or valid_reviews + return max(candidates, key=lambda review: _review_sort_key(bot, review), default=None) + + +def build_reviewer_review_record_from_live_review(review: dict, *, actor: str | None = None) -> dict | None: + if not isinstance(review, dict): + return None + review_id = review.get("id") + submitted_at = review.get("submitted_at") + commit_id = review.get("commit_id") + author = actor if isinstance(actor, str) and actor.strip() else review.get("user", {}).get("login") + if not isinstance(review_id, int) or not isinstance(submitted_at, str) or not isinstance(commit_id, str): + return None + if not isinstance(author, str) or not author.strip(): + return None + return { + "semantic_key": f"pull_request_review:{review_id}", + "timestamp": submitted_at, + "actor": author, + "reviewed_head_sha": commit_id, + "source_precedence": 1, + "payload": {}, + } diff --git a/scripts/reviewer_bot_core/state_adapters.py b/scripts/reviewer_bot_core/state_adapters.py new file mode 100644 index 000000000..55ee13f5f --- /dev/null +++ b/scripts/reviewer_bot_core/state_adapters.py @@ -0,0 +1,326 @@ +"""Adapters between persisted review-entry dicts and minimal core review-state types. + +These adapters are limited to the mutable review-state fields needed for the C1 +cutover. They preserve current compatibility-upgrade behavior for sparse review +entries without introducing schema cleanup. +""" + +from __future__ import annotations + +from copy import deepcopy +from typing import Any + +from .review_state_types import ( + AcceptedChannelRecord, + DismissalAcceptedRecord, + ReviewChannelState, + ReviewEntryState, +) + +_CANONICAL_REPAIR_MARKERS = ( + "review_repair", + "head_observation_repair", + "status_label_projection", +) + +_DEFERRED_GAP_MIGRATION_DROP_KEYS = { + "source_run_id", + "source_run_attempt", + "source_workflow_file", + "source_artifact_name", +} + + +def _migrate_repair_marker(marker: Any) -> dict[str, Any] | None: + if not isinstance(marker, dict): + return None + return { + "kind": marker.get("kind"), + "reason": marker.get("reason"), + "failure_kind": marker.get("failure_kind"), + "recorded_at": marker.get("recorded_at"), + } + + +def _migrate_deferred_gaps(legacy: Any) -> dict[str, Any]: + if not isinstance(legacy, dict): + return {} + migrated: dict[str, Any] = {} + for source_event_key, payload in legacy.items(): + if not isinstance(source_event_key, str) or not isinstance(payload, dict): + continue + migrated[source_event_key] = { + key: deepcopy(value) + for key, value in payload.items() + if key not in _DEFERRED_GAP_MIGRATION_DROP_KEYS + } + return migrated + + +def _migrate_reconciled_source_events(legacy: Any) -> dict[str, Any]: + if isinstance(legacy, dict): + migrated: dict[str, Any] = {} + for source_event_key, record in legacy.items(): + if not isinstance(source_event_key, str): + continue + if isinstance(record, dict): + migrated[source_event_key] = { + "source_event_key": str(record.get("source_event_key") or source_event_key), + "reconciled_at": record.get("reconciled_at"), + } + else: + migrated[source_event_key] = { + "source_event_key": source_event_key, + "reconciled_at": None, + } + return migrated + if isinstance(legacy, list): + return { + source_event_key: {"source_event_key": source_event_key, "reconciled_at": None} + for source_event_key in legacy + if isinstance(source_event_key, str) + } + return {} + + +def ensure_sidecar_subtree(review_entry: dict[str, Any], *, state_last_updated: str | None = None) -> None: + del state_last_updated + sidecars = review_entry.get("sidecars") + if not isinstance(sidecars, dict): + sidecars = {} + review_entry["sidecars"] = sidecars + + sidecars["pending_privileged_commands"] = ( + deepcopy(sidecars.get("pending_privileged_commands")) + if isinstance(sidecars.get("pending_privileged_commands"), dict) + else deepcopy(review_entry.get("pending_privileged_commands")) + if isinstance(review_entry.get("pending_privileged_commands"), dict) + else {} + ) + sidecars["deferred_gaps"] = ( + deepcopy(sidecars.get("deferred_gaps")) + if isinstance(sidecars.get("deferred_gaps"), dict) + else _migrate_deferred_gaps(review_entry.get("deferred_gaps")) + ) + sidecars["observer_discovery_watermarks"] = ( + deepcopy(sidecars.get("observer_discovery_watermarks")) + if isinstance(sidecars.get("observer_discovery_watermarks"), dict) + else deepcopy(review_entry.get("observer_discovery_watermarks")) + if isinstance(review_entry.get("observer_discovery_watermarks"), dict) + else {} + ) + sidecars["reconciled_source_events"] = _migrate_reconciled_source_events( + sidecars.get("reconciled_source_events") + if sidecars.get("reconciled_source_events") is not None + else review_entry.get("reconciled_source_events") + ) + + repair_markers = sidecars.get("repair_markers") if isinstance(sidecars.get("repair_markers"), dict) else {} + canonical_repair_markers = dict.fromkeys(_CANONICAL_REPAIR_MARKERS) + for key in _CANONICAL_REPAIR_MARKERS: + if isinstance(repair_markers.get(key), dict): + canonical_repair_markers[key] = _migrate_repair_marker(repair_markers[key]) + + legacy_marker = review_entry.get("repair_needed") + if isinstance(legacy_marker, dict): + migrated = _migrate_repair_marker(legacy_marker) + if legacy_marker.get("kind") == "projection_failure": + canonical_repair_markers["status_label_projection"] = migrated + elif legacy_marker.get("phase") == "review_repair": + canonical_repair_markers["review_repair"] = migrated + elif legacy_marker.get("phase") == "head_observation_repair": + canonical_repair_markers["head_observation_repair"] = migrated + sidecars["repair_markers"] = canonical_repair_markers + + for legacy_key in ( + "repair_needed", + "pending_privileged_commands", + "deferred_gaps", + "observer_discovery_watermarks", + "reconciled_source_events", + ): + review_entry.pop(legacy_key, None) + + +def _channel_from_persisted(review_entry: dict[str, Any], name: str) -> ReviewChannelState: + raw_channel = review_entry.get(name) + if not isinstance(raw_channel, dict): + return ReviewChannelState() + + accepted = raw_channel.get("accepted") + typed_accepted: AcceptedChannelRecord | DismissalAcceptedRecord | None + if not isinstance(accepted, dict): + typed_accepted = None + elif set(accepted).issubset({"semantic_key", "timestamp"}): + typed_accepted = DismissalAcceptedRecord( + semantic_key=str(accepted.get("semantic_key", "")), + timestamp=str(accepted.get("timestamp", "")), + ) + else: + typed_accepted = AcceptedChannelRecord( + semantic_key=str(accepted.get("semantic_key", "")), + timestamp=str(accepted.get("timestamp", "")), + actor=(str(accepted["actor"]) if accepted.get("actor") is not None else None), + reviewed_head_sha=( + str(accepted["reviewed_head_sha"]) + if accepted.get("reviewed_head_sha") is not None + else None + ), + source_precedence=int(accepted.get("source_precedence", 0)), + payload=deepcopy(accepted.get("payload") or {}), + ) + + seen_keys = raw_channel.get("seen_keys") + return ReviewChannelState( + accepted=typed_accepted, + seen_keys=list(seen_keys) if isinstance(seen_keys, list) else [], + ) + + +def _channel_to_persisted(channel: ReviewChannelState) -> dict[str, Any]: + accepted = channel.accepted + if isinstance(accepted, AcceptedChannelRecord): + persisted_accepted: dict[str, Any] | None = { + "semantic_key": accepted.semantic_key, + "timestamp": accepted.timestamp, + "actor": accepted.actor, + "reviewed_head_sha": accepted.reviewed_head_sha, + "source_precedence": accepted.source_precedence, + "payload": deepcopy(accepted.payload), + } + elif isinstance(accepted, DismissalAcceptedRecord): + persisted_accepted = { + "semantic_key": accepted.semantic_key, + "timestamp": accepted.timestamp, + } + else: + persisted_accepted = None + return { + "accepted": persisted_accepted, + "seen_keys": list(channel.seen_keys), + } + + +def review_entry_from_persisted(review_entry: dict[str, Any] | list[Any] | None) -> ReviewEntryState | None: + if review_entry is None: + return None + if isinstance(review_entry, list): + review_entry = {"skipped": list(review_entry)} + if not isinstance(review_entry, dict): + return None + + skipped = review_entry.get("skipped") + return ReviewEntryState( + skipped=list(skipped) if isinstance(skipped, list) else [], + current_reviewer=(str(review_entry["current_reviewer"]) if review_entry.get("current_reviewer") is not None else None), + cycle_started_at=(str(review_entry["cycle_started_at"]) if review_entry.get("cycle_started_at") is not None else None), + active_cycle_started_at=( + str(review_entry["active_cycle_started_at"]) + if review_entry.get("active_cycle_started_at") is not None + else None + ), + assigned_at=(str(review_entry["assigned_at"]) if review_entry.get("assigned_at") is not None else None), + active_head_sha=(str(review_entry["active_head_sha"]) if review_entry.get("active_head_sha") is not None else None), + last_reviewer_activity=( + str(review_entry["last_reviewer_activity"]) + if review_entry.get("last_reviewer_activity") is not None + else None + ), + transition_warning_sent=( + str(review_entry["transition_warning_sent"]) + if review_entry.get("transition_warning_sent") is not None + else None + ), + transition_notice_sent_at=( + str(review_entry["transition_notice_sent_at"]) + if review_entry.get("transition_notice_sent_at") is not None + else None + ), + assignment_method=(str(review_entry["assignment_method"]) if review_entry.get("assignment_method") is not None else None), + review_completed_at=( + str(review_entry["review_completed_at"]) + if review_entry.get("review_completed_at") is not None + else None + ), + review_completed_by=( + str(review_entry["review_completed_by"]) + if review_entry.get("review_completed_by") is not None + else None + ), + review_completion_source=( + str(review_entry["review_completion_source"]) + if review_entry.get("review_completion_source") is not None + else None + ), + mandatory_approver_required=bool(review_entry.get("mandatory_approver_required", False)), + mandatory_approver_label_applied_at=( + str(review_entry["mandatory_approver_label_applied_at"]) + if review_entry.get("mandatory_approver_label_applied_at") is not None + else None + ), + mandatory_approver_pinged_at=( + str(review_entry["mandatory_approver_pinged_at"]) + if review_entry.get("mandatory_approver_pinged_at") is not None + else None + ), + mandatory_approver_satisfied_by=( + str(review_entry["mandatory_approver_satisfied_by"]) + if review_entry.get("mandatory_approver_satisfied_by") is not None + else None + ), + mandatory_approver_satisfied_at=( + str(review_entry["mandatory_approver_satisfied_at"]) + if review_entry.get("mandatory_approver_satisfied_at") is not None + else None + ), + overdue_anchor=deepcopy(review_entry.get("overdue_anchor")), + reviewer_comment=_channel_from_persisted(review_entry, "reviewer_comment"), + reviewer_review=_channel_from_persisted(review_entry, "reviewer_review"), + contributor_comment=_channel_from_persisted(review_entry, "contributor_comment"), + contributor_revision=_channel_from_persisted(review_entry, "contributor_revision"), + review_dismissal=_channel_from_persisted(review_entry, "review_dismissal"), + current_cycle_completion=deepcopy(review_entry.get("current_cycle_completion") or {}) + if isinstance(review_entry.get("current_cycle_completion"), dict) + else {}, + current_cycle_write_approval=deepcopy(review_entry.get("current_cycle_write_approval") or {}) + if isinstance(review_entry.get("current_cycle_write_approval"), dict) + else {}, + ) + + +def review_entry_to_persisted(review_entry: ReviewEntryState) -> dict[str, Any]: + return { + "skipped": list(review_entry.skipped), + "current_reviewer": review_entry.current_reviewer, + "cycle_started_at": review_entry.cycle_started_at, + "active_cycle_started_at": review_entry.active_cycle_started_at, + "assigned_at": review_entry.assigned_at, + "active_head_sha": review_entry.active_head_sha, + "last_reviewer_activity": review_entry.last_reviewer_activity, + "transition_warning_sent": review_entry.transition_warning_sent, + "transition_notice_sent_at": review_entry.transition_notice_sent_at, + "assignment_method": review_entry.assignment_method, + "review_completed_at": review_entry.review_completed_at, + "review_completed_by": review_entry.review_completed_by, + "review_completion_source": review_entry.review_completion_source, + "mandatory_approver_required": review_entry.mandatory_approver_required, + "mandatory_approver_label_applied_at": review_entry.mandatory_approver_label_applied_at, + "mandatory_approver_pinged_at": review_entry.mandatory_approver_pinged_at, + "mandatory_approver_satisfied_by": review_entry.mandatory_approver_satisfied_by, + "mandatory_approver_satisfied_at": review_entry.mandatory_approver_satisfied_at, + "overdue_anchor": deepcopy(review_entry.overdue_anchor), + "reviewer_comment": _channel_to_persisted(review_entry.reviewer_comment), + "reviewer_review": _channel_to_persisted(review_entry.reviewer_review), + "contributor_comment": _channel_to_persisted(review_entry.contributor_comment), + "contributor_revision": _channel_to_persisted(review_entry.contributor_revision), + "review_dismissal": _channel_to_persisted(review_entry.review_dismissal), + "current_cycle_completion": deepcopy(review_entry.current_cycle_completion), + "current_cycle_write_approval": deepcopy(review_entry.current_cycle_write_approval), + } + + +def apply_local_state_core_to_persisted(target: dict[str, Any], review_entry: ReviewEntryState) -> dict[str, Any]: + persisted = review_entry_to_persisted(review_entry) + for key, value in persisted.items(): + target[key] = value + return target diff --git a/scripts/reviewer_bot_lib/app.py b/scripts/reviewer_bot_lib/app.py index d93331d4b..944b1ed08 100644 --- a/scripts/reviewer_bot_lib/app.py +++ b/scripts/reviewer_bot_lib/app.py @@ -1,19 +1,26 @@ """Top-level reviewer-bot orchestration.""" -import os import sys -from .context import ReviewerBotContext +from . import maintenance, reconcile +from .context import EventContext, ExecutionResult +from .event_inputs import build_event_context as decode_event_context from .maintenance import ( collect_status_projection_repair_items, status_projection_repair_needed, ) +from .repair_records import projection_repair_marker, store_repair_marker +from .runtime_protocols import AppEventContextRuntime, AppExecutionRuntime -def _revalidate_epoch(bot: ReviewerBotContext, expected_epoch: str | None, phase: str) -> None: +def _log(bot: AppExecutionRuntime, level: str, message: str, **fields) -> None: + bot.logger.event(level, message, **fields) + + +def _revalidate_epoch(bot: AppExecutionRuntime, expected_epoch: str | None, phase: str) -> None: if expected_epoch is None: return - latest_state = bot.load_state(fail_on_unavailable=True) + latest_state = bot.state_store.load_state(fail_on_unavailable=True) latest_epoch = latest_state.get("freshness_runtime_epoch") if latest_epoch != expected_epoch: raise RuntimeError( @@ -21,7 +28,7 @@ def _revalidate_epoch(bot: ReviewerBotContext, expected_epoch: str | None, phase ) -def _mark_projection_repair_needed(bot: ReviewerBotContext, state: dict, issue_numbers: list[int], reason: str) -> bool: +def _mark_projection_repair_needed(bot: AppExecutionRuntime, state: dict, issue_numbers: list[int], reason: str) -> bool: changed = False active_reviews = state.get("active_reviews") if not isinstance(active_reviews, dict): @@ -30,17 +37,19 @@ def _mark_projection_repair_needed(bot: ReviewerBotContext, state: dict, issue_n review_data = active_reviews.get(str(issue_number)) if not isinstance(review_data, dict): continue - review_data["repair_needed"] = { - "kind": "projection_failure", - "reason": reason, - "recorded_at": bot.datetime.now(bot.timezone.utc).isoformat(), - } - changed = True + marker = projection_repair_marker(reason, bot.datetime.now(bot.timezone.utc).isoformat()) + changed = store_repair_marker(review_data, "status_label_projection", marker) or changed return changed -def classify_event_intent(bot: ReviewerBotContext, event_name: str, event_action: str) -> str: - """Classify whether a run can mutate reviewer-bot state.""" +def build_event_context(bot: AppEventContextRuntime) -> EventContext: + return decode_event_context(bot) + + +def _classify_event_intent_from_context(bot: AppEventContextRuntime, context: EventContext) -> str: + event_name = context.event_name + event_action = context.event_action + if event_name in {"issues", "pull_request_target"}: if event_action in {"opened", "labeled", "edited", "closed", "synchronize"}: return bot.EVENT_INTENT_MUTATING @@ -48,18 +57,13 @@ def classify_event_intent(bot: ReviewerBotContext, event_name: str, event_action if event_name == "issue_comment": if event_action == "created": - if os.environ.get("IS_PULL_REQUEST", "false").lower() == "true": - trust_class = os.environ.get("REVIEWER_BOT_TRUST_CLASS", "").strip() + if context.is_pull_request is True: + trust_class = bot.get_config_value("REVIEWER_BOT_TRUST_CLASS").strip() if trust_class in {"pr_deferred_reconcile", "safe_noop"}: return bot.EVENT_INTENT_NON_MUTATING_DEFER return bot.EVENT_INTENT_MUTATING return bot.EVENT_INTENT_NON_MUTATING_READONLY - if event_name == "pull_request_review": - if event_action in {"submitted", "dismissed"}: - return bot.EVENT_INTENT_NON_MUTATING_DEFER - return bot.EVENT_INTENT_NON_MUTATING_READONLY - if event_name == "pull_request_review_comment": if event_action == "created": return bot.EVENT_INTENT_NON_MUTATING_DEFER @@ -68,14 +72,12 @@ def classify_event_intent(bot: ReviewerBotContext, event_name: str, event_action if event_name == "workflow_run": if event_action != "completed": return bot.EVENT_INTENT_NON_MUTATING_READONLY - workflow_run_event = os.environ.get("WORKFLOW_RUN_EVENT", "").strip() - if workflow_run_event in {"pull_request_review", "issue_comment", "pull_request_review_comment"}: + if context.workflow_kind == "reconcile" and context.workflow_run_triggering_conclusion == "success": return bot.EVENT_INTENT_MUTATING return bot.EVENT_INTENT_NON_MUTATING_READONLY if event_name == "workflow_dispatch": - action = os.environ.get("MANUAL_ACTION", "").strip() - if action in {"show-state", "preview-reviewer-board"}: + if context.manual_action in {"show-state", "preview-reviewer-board"}: return bot.EVENT_INTENT_NON_MUTATING_READONLY return bot.EVENT_INTENT_MUTATING @@ -85,22 +87,52 @@ def classify_event_intent(bot: ReviewerBotContext, event_name: str, event_action return bot.EVENT_INTENT_NON_MUTATING_READONLY -def event_requires_lease_lock(bot: ReviewerBotContext, event_name: str, event_action: str) -> bool: +def classify_event_intent(bot: AppEventContextRuntime, event_name: str, event_action: str) -> str: + """Classify whether a run can mutate reviewer-bot state.""" + context = build_event_context(bot) + context = EventContext( + event_name=event_name, + event_action=event_action, + issue_number=context.issue_number, + is_pull_request=context.is_pull_request, + issue_author=context.issue_author, + issue_state=context.issue_state, + issue_labels=context.issue_labels, + comment_id=context.comment_id, + comment_author=context.comment_author, + comment_body=context.comment_body, + comment_source_event_key=context.comment_source_event_key, + pr_is_cross_repository=context.pr_is_cross_repository, + review_author=context.review_author, + review_state=context.review_state, + workflow_kind=context.workflow_kind, + workflow_run_triggering_conclusion=context.workflow_run_triggering_conclusion, + workflow_artifact_contract=context.workflow_artifact_contract, + manual_action=context.manual_action, + ) + return _classify_event_intent_from_context(bot, context) + + +def event_requires_lease_lock(bot: AppEventContextRuntime, event_name: str, event_action: str) -> bool: """Backwards-compatible helper for tests and call sites.""" return classify_event_intent(bot, event_name, event_action) == bot.EVENT_INTENT_MUTATING -def main(bot: ReviewerBotContext): - """Main entry point for the reviewer bot.""" - event_name = os.environ.get("EVENT_NAME", "") - event_action = os.environ.get("EVENT_ACTION", "") +def execute_run(bot: AppExecutionRuntime, context: EventContext) -> ExecutionResult: bot.drain_touched_items() - event_intent = classify_event_intent(bot, event_name, event_action) + event_name = context.event_name + event_action = context.event_action + event_intent = _classify_event_intent_from_context(bot, context) lock_required = event_intent == bot.EVENT_INTENT_MUTATING - print( - f"Event: {event_name}, Action: {event_action}, Intent: {event_intent}, " - f"Lock Required: {lock_required}" + _log( + bot, + "info", + f"Event: {event_name}, Action: {event_action}, Intent: {event_intent}, Lock Required: {lock_required}", + event_name=event_name, + event_action=event_action, + event_intent=event_intent, + lock_required=lock_required, ) lock_acquired = False @@ -116,72 +148,80 @@ def main(bot: ReviewerBotContext): projection_failure: RuntimeError | None = None loaded_epoch: str | None = None projection_epoch_repair = False + workflow_run_result: reconcile.WorkflowRunHandlerResult | None = None + schedule_result: maintenance.ScheduleHandlerResult | None = None try: if lock_required: - bot.acquire_state_issue_lease_lock() + bot.locks.acquire() lock_acquired = True - state = bot.load_state(fail_on_unavailable=lock_required) + state = bot.state_store.load_state(fail_on_unavailable=lock_required) active_reviews = state.get("active_reviews") if isinstance(active_reviews, dict): loaded_active_reviews_count = len(active_reviews) loaded_epoch = state.get("freshness_runtime_epoch") if isinstance(state.get("freshness_runtime_epoch"), str) else None if lock_required: - state, restored = bot.process_pass_until_expirations(state) + state, restored = bot.adapters.workflow.process_pass_until_expirations(state) if restored: - print(f"Restored from pass-until: {restored}") + _log(bot, "info", f"Restored from pass-until: {restored}", restored=restored) - state, sync_changes = bot.sync_members_with_queue(state) + state, sync_changes = bot.adapters.workflow.sync_members_with_queue(state) if sync_changes: - print(f"Members sync changes: {sync_changes}") + _log(bot, "info", f"Members sync changes: {sync_changes}", sync_changes=sync_changes) if event_name == "issues": if event_action == "opened": - state_changed = bot.handle_issue_or_pr_opened(state) + state_changed = bot.handlers.handle_issue_or_pr_opened(state) elif event_action == "labeled": - state_changed = bot.handle_labeled_event(state) + state_changed = bot.handlers.handle_labeled_event(state) elif event_action == "edited": - state_changed = bot.handle_issue_edited_event(state) + state_changed = bot.handlers.handle_issue_edited_event(state) elif event_action == "closed": - state_changed = bot.handle_closed_event(state) + state_changed = bot.handlers.handle_closed_event(state) elif event_name == "pull_request_target": if event_action == "opened": - state_changed = bot.handle_issue_or_pr_opened(state) + state_changed = bot.handlers.handle_issue_or_pr_opened(state) elif event_action == "labeled": - state_changed = bot.handle_labeled_event(state) + state_changed = bot.handlers.handle_labeled_event(state) elif event_action == "closed": - state_changed = bot.handle_closed_event(state) + state_changed = bot.handlers.handle_closed_event(state) elif event_action == "synchronize": - state_changed = bot.handle_pull_request_target_synchronize(state) - - elif event_name == "pull_request_review": - if event_action in {"submitted", "dismissed"}: - state_changed = bot.handle_pull_request_review_event(state) + state_changed = bot.handlers.handle_pull_request_target_synchronize(state) elif event_name == "issue_comment": if event_action == "created": - state_changed = bot.handle_comment_event(state) + state_changed = bot.handlers.handle_comment_event(state) elif event_name == "workflow_dispatch": - state_changed = bot.handle_manual_dispatch(state) + state_changed = bot.handlers.handle_manual_dispatch(state) elif event_name == "schedule": - state_changed = bot.handle_scheduled_check(state) + schedule_result = bot.handlers.handle_scheduled_check_result(state) + state_changed = schedule_result.state_changed elif event_name == "workflow_run": if event_action == "completed": - if os.environ.get("WORKFLOW_RUN_EVENT", "").strip() in {"pull_request_review", "issue_comment", "pull_request_review_comment"}: - state_changed = bot.handle_workflow_run_event(state) + if context.workflow_kind == "reconcile" and context.workflow_run_triggering_conclusion == "success": + workflow_run_result = reconcile.handle_workflow_run_event_result(bot, state) + state_changed = workflow_run_result.state_changed else: - print( - "Ignoring workflow_run event with unsupported source event: " - f"{os.environ.get('WORKFLOW_RUN_EVENT', '').strip() or ''}" + _log( + bot, + "info", + "Ignoring workflow_run event outside retained reconcile envelope.", + workflow_kind=context.workflow_kind or "", + workflow_conclusion=context.workflow_run_triggering_conclusion or "", ) - touched_items = bot.drain_touched_items() + if workflow_run_result is not None: + touched_items = workflow_run_result.touched_items + elif schedule_result is not None: + touched_items = schedule_result.touched_items + else: + touched_items = bot.drain_touched_items() if lock_required and event_name in {"schedule", "workflow_dispatch"} and status_projection_repair_needed(bot, state): touched_items = sorted( { @@ -204,7 +244,7 @@ def main(bot: ReviewerBotContext): len(current_active_reviews) if isinstance(current_active_reviews, dict) else 0 ) allow_empty_override = ( - os.environ.get("ALLOW_EMPTY_ACTIVE_REVIEWS_WRITE", "").strip().lower() == "true" + bot.get_config_value("ALLOW_EMPTY_ACTIVE_REVIEWS_WRITE").strip().lower() == "true" ) if ( loaded_active_reviews_count > 0 @@ -217,16 +257,16 @@ def main(bot: ReviewerBotContext): "to 0. Set ALLOW_EMPTY_ACTIVE_REVIEWS_WRITE=true to override." ) - print("State updates detected; attempting to persist reviewer-bot state.") + _log(bot, "info", "State updates detected; attempting to persist reviewer-bot state.") _revalidate_epoch(bot, loaded_epoch, "authoritative save") - if not bot.save_state(state): + if not bot.state_store.save_state(state): raise RuntimeError( "State updates were computed but could not be persisted. " "Failing this run to avoid silent success." ) if touched_items: - state = bot.load_state(fail_on_unavailable=True) + state = bot.state_store.load_state(fail_on_unavailable=True) if touched_items: if not lock_acquired: @@ -236,16 +276,18 @@ def main(bot: ReviewerBotContext): ) _revalidate_epoch(bot, loaded_epoch, "status-label projection") try: - status_labels_changed = bot.sync_status_labels_for_items(state, touched_items) + status_labels_changed = bot.adapters.workflow.sync_status_labels_for_items(state, touched_items) except RuntimeError as exc: projection_failure = exc - print( - f"WARNING: Authoritative state is persisted but status-label projection failed: {exc}", - file=sys.stderr, + _log( + bot, + "warning", + f"Authoritative state is persisted but status-label projection failed: {exc}", + projection_error=str(exc), ) if _mark_projection_repair_needed(bot, state, touched_items, str(exc)): _revalidate_epoch(bot, loaded_epoch, "projection-failure repair marker save") - if not bot.save_state(state): + if not bot.state_store.save_state(state): raise RuntimeError( "Projection failed and repair-needed metadata could not be persisted." ) @@ -253,40 +295,48 @@ def main(bot: ReviewerBotContext): if projection_epoch_repair: state["status_projection_epoch"] = bot.STATUS_PROJECTION_EPOCH _revalidate_epoch(bot, loaded_epoch, "status-projection epoch save") - if not bot.save_state(state): + if not bot.state_store.save_state(state): raise RuntimeError( "Status projection epoch repair succeeded but could not be persisted." ) - with open(os.environ.get("GITHUB_OUTPUT", "/dev/null"), "a") as output_file: - output_file.write( - "state_changed=true\n" - if (state_changed or bool(sync_changes) or bool(restored) or status_labels_changed) - else "state_changed=false\n" - ) + execution_state_changed = bool(state_changed or sync_changes or restored or status_labels_changed) + + bot.write_output( + "state_changed", + "true" if execution_state_changed else "false", + ) if projection_failure is not None: - print( + _log( + bot, + "warning", "PROJECTION_REPAIR_REQUIRED: labels remain unchanged until a trusted repair path succeeds.", - file=sys.stderr, ) except RuntimeError as exc: - print(f"ERROR: {exc}", file=sys.stderr) + _log(bot, "error", f"ERROR: {exc}", error=str(exc)) exit_code = 1 except Exception as exc: # pragma: no cover - defensive hard-fail path - print(f"ERROR: Unexpected reviewer-bot failure: {exc}", file=sys.stderr) + _log(bot, "error", f"ERROR: Unexpected reviewer-bot failure: {exc}", error=str(exc)) exit_code = 1 finally: if lock_acquired: - if not bot.release_state_issue_lease_lock(): + if not bot.locks.release(): release_failed = True if release_failed: - print( - "ERROR: Failed to release reviewer-bot lease lock after processing event.", - file=sys.stderr, - ) + _log(bot, "error", "ERROR: Failed to release reviewer-bot lease lock after processing event.") exit_code = 1 - if exit_code: - sys.exit(exit_code) + return ExecutionResult( + exit_code=exit_code, + state_changed=bool(state_changed or sync_changes or restored or status_labels_changed), + release_failed=release_failed, + ) + + +def main(bot: AppExecutionRuntime): + """Main entry point for the reviewer bot.""" + result = execute_run(bot, build_event_context(bot)) + if result.exit_code: + sys.exit(result.exit_code) diff --git a/scripts/reviewer_bot_lib/automation.py b/scripts/reviewer_bot_lib/automation.py index 95ba4872a..dd23b8e5d 100644 --- a/scripts/reviewer_bot_lib/automation.py +++ b/scripts/reviewer_bot_lib/automation.py @@ -1,10 +1,26 @@ """Automation-heavy reviewer-bot helpers.""" -import os import subprocess from datetime import datetime, timezone from pathlib import Path +from scripts.reviewer_bot_core import privileged_command_policy + +from .context import PrivilegedCommandRequest + +EXECUTOR_PHASE_CHECKLIST = [ + "audit_command_execution", + "spec_lock_update_command_execution", + "changed_file_validation_execution", + "branch_existence_check", + "branch_creation", + "git_add", + "git_commit", + "git_push", + "pull_request_create", + "execute_time_revalidation_checkpoints", +] + def run_command(command: list[str], cwd: Path, check: bool = True) -> subprocess.CompletedProcess: result = subprocess.run(command, cwd=cwd, capture_output=True, text=True) @@ -32,13 +48,11 @@ def list_changed_files(repo_root: Path) -> list[str]: return sorted(set(files)) -def get_target_repo_root() -> Path: - configured = os.environ.get("REVIEWER_BOT_TARGET_REPO_ROOT", "").strip() +def get_target_repo_root(bot) -> Path: + configured = bot.get_config_value("REVIEWER_BOT_TARGET_REPO_ROOT", "").strip() if configured: return Path(configured) return Path(__file__).resolve().parents[2] - - def get_default_branch(bot) -> str: repo_info = bot.github_api("GET", "") if isinstance(repo_info, dict): @@ -46,28 +60,43 @@ def get_default_branch(bot) -> str: return "main" -def find_open_pr_for_branch(bot, branch: str) -> dict | None: - owner = os.environ.get("REPO_OWNER", "").strip() +def find_open_pr_for_branch_status(bot, branch: str) -> tuple[str, dict | None]: + owner = bot.get_config_value("REPO_OWNER", "").strip() branch = branch.strip() if not owner or not branch: - return None - response = bot.github_api("GET", f"pulls?state=open&head={owner}:{branch}") - if isinstance(response, list) and response: - first = response[0] + return "not_found", None + response = bot.github_api_request( + "GET", + f"pulls?state=open&head={owner}:{branch}", + retry_policy="idempotent_read", + ) + if not response.ok: + return "unavailable", None + payload = response.payload + if not isinstance(payload, list): + return "unavailable", None + if payload: + first = payload[0] if isinstance(first, dict): - return first - return None + return "found", first + return "not_found", None -def create_pull_request(bot, branch: str, base: str, issue_number: int) -> dict | None: - existing = bot.find_open_pr_for_branch(branch) - if existing: +def find_open_pr_for_branch(bot, branch: str) -> dict | None: + status, pr = find_open_pr_for_branch_status(bot, branch) + if status != "found": + return None + return pr + + +def create_pull_request(bot, branch: str, base: str, issue_number: int, *, title: str | None = None, body: str | None = None) -> dict | None: + lookup_status, existing = find_open_pr_for_branch_status(bot, branch) + if lookup_status == "found": return existing - title = "chore: update spec.lock (no guideline impact)" - body = ( - "Updates `src/spec.lock` after confirming the audit reported no affected guidelines.\n\n" - f"Closes #{issue_number}" - ) + if lookup_status == "unavailable": + raise RuntimeError(f"Unable to determine whether branch '{branch}' already has an open PR") + if title is None or body is None: + raise RuntimeError("PR title/body must be provided by privileged command planning") response = bot.github_api( "POST", "pulls", @@ -78,87 +107,188 @@ def create_pull_request(bot, branch: str, base: str, issue_number: int) -> dict return None -def handle_accept_no_fls_changes_command(bot, issue_number: int, comment_author: str) -> tuple[str, bool]: - if os.environ.get("IS_PULL_REQUEST", "false").lower() == "true": - return "❌ This command can only be used on issues, not PRs.", False - labels = bot.parse_issue_labels() - if bot.FLS_AUDIT_LABEL not in labels: - return "❌ This command is only available on issues labeled `fls-audit`.", False - if not bot.check_user_permission(comment_author, "triage"): - return "❌ You must have triage permissions to run this command.", False +def _execute_accept_no_fls_changes_plan( + bot, + repo_root: Path, + issue_number: int, + plan: privileged_command_policy.AcceptNoFlsChangesPlan, +) -> privileged_command_policy.CompletePrivilegedExecution: + try: + bot.adapters.automation.run_command(plan.git_checkout_args, cwd=repo_root) + bot.adapters.automation.run_command(["git", "add", *plan.add_paths], cwd=repo_root) + bot.adapters.automation.run_command(plan.git_commit_args, cwd=repo_root) + bot.adapters.automation.run_command(plan.git_push_args, cwd=repo_root) + except RuntimeError as exc: + return privileged_command_policy.CompletePrivilegedExecution( + status="failed_closed", + result_code="branch_or_push_failed", + result_message=f"❌ Failed to create branch or push changes: {exc}", + ) + + pr = create_pull_request( + bot, + plan.branch_name, + plan.base_branch, + issue_number, + title=plan.pull_request_title, + body=plan.pull_request_body, + ) + if not pr or "html_url" not in pr: + return privileged_command_policy.CompletePrivilegedExecution( + status="failed_closed", + result_code="pull_request_creation_failed", + result_message="❌ Failed to open a pull request for the spec.lock update.", + ) + + return privileged_command_policy.CompletePrivilegedExecution( + status="executed", + result_code="opened_pull_request", + result_message=f"✅ Opened PR {pr['html_url']}", + opened_pr_url=pr["html_url"], + ) - repo_root = get_target_repo_root() - if bot.list_changed_files(repo_root): - return "❌ Working tree is not clean; refusing to update spec.lock.", False - audit_result = bot.run_command( +def _resolve_accept_no_fls_changes_plan( + bot, + repo_root: Path, + issue_number: int, + *, + audit_result: subprocess.CompletedProcess, + update_result: subprocess.CompletedProcess, + changed_files_after: list[str], + ) -> privileged_command_policy.CompletePrivilegedExecution | privileged_command_policy.AcceptNoFlsChangesPlan: + post_update = privileged_command_policy.assess_accept_no_fls_changes_post_update( + audit_returncode=audit_result.returncode, + audit_details=bot.adapters.automation.summarize_output(audit_result), + update_returncode=update_result.returncode, + update_details=bot.adapters.automation.summarize_output(update_result), + changed_files_after=changed_files_after, + ) + if post_update is not None: + return post_update + base_branch = bot.adapters.automation.get_default_branch() + branch_date = datetime.now(timezone.utc).strftime("%Y-%m-%d") + provisional = privileged_command_policy.plan_accept_no_fls_changes_execution( + issue_number=issue_number, + audit_returncode=0, + audit_details="", + update_returncode=0, + update_details="", + changed_files_after=changed_files_after, + branch_date=branch_date, + base_branch=base_branch, + branch_exists=False, + branch_suffix=None, + ) + if not isinstance(provisional, privileged_command_policy.AcceptNoFlsChangesPlan): + return provisional + branch_exists = bot.adapters.automation.run_command( + ["git", "rev-parse", "--verify", provisional.branch_probe_name], + cwd=repo_root, + check=False, + ).returncode == 0 + branch_suffix = datetime.now(timezone.utc).strftime("%H%M%S") if branch_exists else None + return privileged_command_policy.plan_accept_no_fls_changes_execution( + issue_number=issue_number, + audit_returncode=0, + audit_details="", + update_returncode=0, + update_details="", + changed_files_after=changed_files_after, + branch_date=branch_date, + base_branch=base_branch, + branch_exists=branch_exists, + branch_suffix=branch_suffix, + ) + + +def handle_accept_no_fls_changes_command( + bot, + issue_number: int, + comment_author: str, + request: PrivilegedCommandRequest | None = None, + execution_plan: privileged_command_policy.ExecutePrivilegedPlan | None = None, +) -> privileged_command_policy.CompletePrivilegedExecution: + if execution_plan is None: + if request is None: + raise RuntimeError("handle_accept_no_fls_changes_command requires a canonical privileged request") + permission_status = bot.github.get_user_permission_status(comment_author, "triage") + repo_root = get_target_repo_root(bot) + preflight = privileged_command_policy.prevalidate_accept_no_fls_changes_request( + request, + permission_status, + bot.adapters.automation.list_changed_files(repo_root), + ) + if isinstance(preflight, privileged_command_policy.BlockedPrivilegedExecution): + return privileged_command_policy.CompletePrivilegedExecution( + status="failed_closed", + result_code=preflight.result_code, + result_message=preflight.result_message, + ) + execution_plan = privileged_command_policy.ExecutePrivilegedPlan( + record=preflight.record, + execution_context=privileged_command_policy.PrivilegedExecutionContext( + target_repo_root=str(repo_root), + base_branch=preflight.execution_context.base_branch, + branch_probe_name=preflight.execution_context.branch_probe_name, + branch_name=preflight.execution_context.branch_name, + expected_changed_files=preflight.execution_context.expected_changed_files, + existing_open_pr_url=preflight.execution_context.existing_open_pr_url, + ), + ) + if execution_plan.record.command_name != privileged_command_policy.PrivilegedCommandId.ACCEPT_NO_FLS_CHANGES.value: + return privileged_command_policy.CompletePrivilegedExecution( + status="failed_closed", + result_code="unsupported_command", + ) + repo_root = Path(execution_plan.execution_context.target_repo_root) + + audit_result = bot.adapters.automation.run_command( ["uv", "run", "--locked", "python", "scripts/fls_audit.py", "--summary-only", "--fail-on-impact"], cwd=repo_root, check=False, ) if audit_result.returncode == 2: - return ( - "❌ The audit reports affected guidelines. Please review and open a PR with " - "the necessary guideline updates instead.", - False, + return privileged_command_policy.CompletePrivilegedExecution( + status="failed_closed", + result_code="audit_reported_guideline_impact", + result_message=( + "❌ The audit reports affected guidelines. Please review and open a PR with " + "the necessary guideline updates instead." + ), ) if audit_result.returncode != 0: - details = bot.summarize_output(audit_result) + details = bot.adapters.automation.summarize_output(audit_result) detail_text = f"\n\nDetails:\n```\n{details}\n```" if details else "" - return f"❌ Audit command failed.{detail_text}", False + return privileged_command_policy.CompletePrivilegedExecution( + status="failed_closed", + result_code="audit_failed", + result_message=f"❌ Audit command failed.{detail_text}", + ) - update_result = bot.run_command( + update_result = bot.adapters.automation.run_command( ["uv", "run", "--locked", "python", "./make.py", "--update-spec-lock-file"], cwd=repo_root, check=False, ) if update_result.returncode != 0: - details = bot.summarize_output(update_result) + details = bot.adapters.automation.summarize_output(update_result) detail_text = f"\n\nDetails:\n```\n{details}\n```" if details else "" - return f"❌ Failed to update spec.lock.{detail_text}", False - - changed_files = bot.list_changed_files(repo_root) - if not changed_files: - return "✅ `src/spec.lock` is already up to date; no PR needed.", True - - unexpected = {path for path in changed_files if path != "src/spec.lock"} - if unexpected: - paths = ", ".join(sorted(unexpected)) - return ( - "❌ Unexpected tracked file changes detected; refusing to open a PR. " - f"Please review: {paths}", - False, + return privileged_command_policy.CompletePrivilegedExecution( + status="failed_closed", + result_code="update_failed", + result_message=f"❌ Failed to update spec.lock.{detail_text}", ) - branch_date = datetime.now(timezone.utc).strftime("%Y-%m-%d") - base_branch = bot.get_default_branch() - branch_name = f"chore/spec-lock-{branch_date}-issue-{issue_number}" - if bot.run_command(["git", "rev-parse", "--verify", branch_name], cwd=repo_root, check=False).returncode == 0: - suffix = datetime.now(timezone.utc).strftime("%H%M%S") - branch_name = f"{branch_name}-{suffix}" - - try: - bot.run_command(["git", "checkout", "-b", branch_name], cwd=repo_root) - bot.run_command(["git", "add", "src/spec.lock"], cwd=repo_root) - bot.run_command( - [ - "git", - "-c", - "user.name=guidelines-bot", - "-c", - "user.email=guidelines-bot@users.noreply.github.com", - "commit", - "-m", - "chore: update spec.lock; no affected guidelines", - ], - cwd=repo_root, - ) - bot.run_command(["git", "push", "origin", branch_name], cwd=repo_root) - except RuntimeError as exc: - return f"❌ Failed to create branch or push changes: {exc}", False - - pr = bot.create_pull_request(branch_name, base_branch, issue_number) - if not pr or "html_url" not in pr: - return "❌ Failed to open a pull request for the spec.lock update.", False - - return f"✅ Opened PR {pr['html_url']}", True + changed_files_after = bot.adapters.automation.list_changed_files(repo_root) + planning = _resolve_accept_no_fls_changes_plan( + bot, + repo_root, + execution_plan.record.issue_number, + audit_result=audit_result, + update_result=update_result, + changed_files_after=changed_files_after, + ) + if not isinstance(planning, privileged_command_policy.AcceptNoFlsChangesPlan): + return planning + return _execute_accept_no_fls_changes_plan(bot, repo_root, execution_plan.record.issue_number, planning) diff --git a/scripts/reviewer_bot_lib/bootstrap_runtime.py b/scripts/reviewer_bot_lib/bootstrap_runtime.py new file mode 100644 index 000000000..d2b750aaa --- /dev/null +++ b/scripts/reviewer_bot_lib/bootstrap_runtime.py @@ -0,0 +1,428 @@ +"""Bootstrap runtime composition for the reviewer-bot entrypoint.""" + +from __future__ import annotations + +from . import ( + automation, + commands, + comment_routing, + config, + github_api, + lease_lock, + lifecycle, + maintenance, + members, + reconcile, + review_state, + reviews, + state_store, +) +from .queue import ( + get_next_reviewer, + process_pass_until_expirations, + record_assignment, + reposition_member_as_next, + sync_members_with_queue, +) +from .runtime import ( + ReviewerBotRuntime, + _EnvConfig, + _FileOutputSink, + _JsonDeferredPayloadLoader, +) + + +class _BootstrapStateStoreServices: + def __init__(self, runtime_getter): + self._runtime_getter = runtime_getter + + def load_state(self, *, fail_on_unavailable: bool = False): + return state_store.load_state(self._runtime_getter(), fail_on_unavailable=fail_on_unavailable) + + def save_state(self, current_state): + return state_store.save_state(self._runtime_getter(), current_state) + + +class _BootstrapGitHubServices: + def __init__(self, runtime_getter): + self._runtime_getter = runtime_getter + + def github_api_request(self, *args, **kwargs): + return github_api.github_api_request(self._runtime_getter(), *args, **kwargs) + + def github_api(self, *args, **kwargs): + return github_api.github_api(self._runtime_getter(), *args, **kwargs) + + def get_github_token(self): + return github_api.get_github_token(self._runtime_getter()) + + def get_github_graphql_token(self, *, prefer_board_token=False): + return github_api.get_github_graphql_token(self._runtime_getter(), prefer_board_token=prefer_board_token) + + def github_graphql(self, query, variables=None, *, token=None): + return github_api.github_graphql(self._runtime_getter(), query, variables, token=token) + + def post_comment(self, issue_number, body): + return github_api.post_comment(self._runtime_getter(), issue_number, body) + + def get_repo_labels(self): + return github_api.get_repo_labels(self._runtime_getter()) + + def add_label(self, issue_number, label): + return github_api.add_label(self._runtime_getter(), issue_number, label) + + def remove_label(self, issue_number, label): + return github_api.remove_label(self._runtime_getter(), issue_number, label) + + def ensure_label_exists(self, label, *, color=None, description=None): + return github_api.ensure_label_exists(self._runtime_getter(), label, color=color, description=description) + + def get_issue_assignees(self, issue_number): + return github_api.get_issue_assignees(self._runtime_getter(), issue_number) + + def request_pr_reviewer_assignment(self, issue_number, username): + return github_api.request_pr_reviewer_assignment(self._runtime_getter(), issue_number, username) + + def assign_issue_assignee(self, issue_number, username): + return github_api.assign_issue_assignee(self._runtime_getter(), issue_number, username) + + def add_reaction(self, comment_id, reaction): + return github_api.add_reaction(self._runtime_getter(), comment_id, reaction) + + def remove_issue_assignee(self, issue_number, username): + return github_api.remove_issue_assignee(self._runtime_getter(), issue_number, username) + + def remove_pr_reviewer(self, issue_number, username): + return github_api.remove_pr_reviewer(self._runtime_getter(), issue_number, username) + + def get_user_permission_status(self, username, required_permission="triage"): + return github_api.get_user_permission_status(self._runtime_getter(), username, required_permission) + + def check_user_permission(self, username, required_permission="triage"): + return github_api.check_user_permission(self._runtime_getter(), username, required_permission) + + def get_issue_or_pr_snapshot(self, issue_number): + return github_api.github_api(self._runtime_getter(), "GET", f"issues/{issue_number}") + + def get_pull_request_reviews(self, issue_number): + return reviews.get_pull_request_reviews(self._runtime_getter(), issue_number) + + +class _BootstrapLockServices: + def __init__(self, runtime_getter): + self._runtime_getter = runtime_getter + + def acquire(self): + return lease_lock.acquire_state_issue_lease_lock(self._runtime_getter()) + + def release(self) -> bool: + return lease_lock.release_state_issue_lease_lock(self._runtime_getter()) + + def refresh(self) -> bool: + return lease_lock.ensure_state_issue_lease_lock_fresh(self._runtime_getter()) + + +class _BootstrapHandlerServices: + def __init__(self, runtime_getter): + self._runtime_getter = runtime_getter + + def handle_issue_or_pr_opened(self, current_state): + return lifecycle.handle_issue_or_pr_opened(self._runtime_getter(), current_state) + + def handle_labeled_event(self, current_state): + return lifecycle.handle_labeled_event(self._runtime_getter(), current_state) + + def handle_issue_edited_event(self, current_state): + return lifecycle.handle_issue_edited_event(self._runtime_getter(), current_state) + + def handle_closed_event(self, current_state): + return lifecycle.handle_closed_event(self._runtime_getter(), current_state) + + def handle_pull_request_target_synchronize(self, current_state): + return lifecycle.handle_pull_request_target_synchronize(self._runtime_getter(), current_state) + + def handle_comment_event(self, current_state): + return comment_routing.handle_comment_event(self._runtime_getter(), current_state) + + def handle_manual_dispatch(self, current_state): + return maintenance.handle_manual_dispatch(self._runtime_getter(), current_state) + + def handle_scheduled_check_result(self, current_state): + return maintenance.handle_scheduled_check_result(self._runtime_getter(), current_state) + + +class _BootstrapReviewStateAdapterServices: + def __init__(self, runtime_getter): + self._runtime_getter = runtime_getter + + def _runtime(self): + return self._runtime_getter() + + def maybe_record_head_observation_repair(self, issue_number, review_data): + return lifecycle.maybe_record_head_observation_repair(self._runtime(), issue_number, review_data) + + def handle_transition_notice(self, current_state, issue_number, reviewer): + return lifecycle.handle_transition_notice(self._runtime(), current_state, issue_number, reviewer) + + # Adapter-only mutable review-state compatibility surface. + def ensure_review_entry(self, current_state, issue_number, create=False): + return review_state.ensure_review_entry(current_state, issue_number, create=create) + + def set_current_reviewer(self, current_state, issue_number, reviewer, assignment_method="round-robin"): + return review_state.set_current_reviewer(current_state, issue_number, reviewer, assignment_method=assignment_method) + + def update_reviewer_activity(self, current_state, issue_number, reviewer): + return review_state.update_reviewer_activity(current_state, issue_number, reviewer) + + def mark_review_complete(self, current_state, issue_number, reviewer, source): + return review_state.mark_review_complete(current_state, issue_number, reviewer, source) + + def is_triage_or_higher(self, username): + return reviews.is_triage_or_higher(self._runtime(), username) + + def trigger_mandatory_approver_escalation(self, current_state, issue_number): + return reviews.trigger_mandatory_approver_escalation(self._runtime(), current_state, issue_number) + + def satisfy_mandatory_approver_requirement(self, current_state, issue_number, approver): + return reviews.satisfy_mandatory_approver_requirement(self._runtime(), current_state, issue_number, approver) + + def compute_reviewer_response_state(self, issue_number, review_data, *, issue_snapshot=None): + return reviews.compute_reviewer_response_state(self._runtime(), issue_number, review_data, issue_snapshot=issue_snapshot) + + def rebuild_pr_approval_state(self, issue_number, review_data, *, pull_request=None, reviews=None): + return reviews.rebuild_pr_approval_state( + self._runtime(), + issue_number, + review_data, + pull_request=pull_request, + reviews=reviews, + ) + + +class _BootstrapCommandAdapterServices: + def __init__(self, runtime_getter): + self._runtime_getter = runtime_getter + + def _runtime(self): + return self._runtime_getter() + + def handle_pass_command(self, current_state, issue_number, comment_author, reason, request=None): + return commands.handle_pass_command(self._runtime(), current_state, issue_number, comment_author, reason, request=request) + + def handle_pass_until_command(self, current_state, issue_number, comment_author, return_date, reason, request=None): + return commands.handle_pass_until_command(self._runtime(), current_state, issue_number, comment_author, return_date, reason, request=request) + + def handle_label_command(self, current_state, issue_number, label_string, request=None): + return commands.handle_label_command(self._runtime(), current_state, issue_number, label_string, request=request) + + def handle_sync_members_command(self, current_state): + return commands.handle_sync_members_command(self._runtime(), current_state) + + def handle_queue_command(self, current_state): + return commands.handle_queue_command(self._runtime(), current_state) + + def handle_commands_command(self): + return commands.handle_commands_command(self._runtime()) + + def handle_claim_command(self, current_state, issue_number, comment_author, request=None): + return commands.handle_claim_command(self._runtime(), current_state, issue_number, comment_author, request=request) + + def handle_release_command(self, current_state, issue_number, comment_author, args=None, request=None): + return commands.handle_release_command(self._runtime(), current_state, issue_number, comment_author, args, request=request) + + def handle_rectify_command(self, current_state, issue_number, comment_author): + return reconcile.handle_rectify_command(self._runtime(), current_state, issue_number, comment_author) + + def handle_assign_command(self, current_state, issue_number, username, request=None): + return commands.handle_assign_command(self._runtime(), current_state, issue_number, username, request=request) + + def handle_assign_from_queue_command(self, current_state, issue_number, request=None): + return commands.handle_assign_from_queue_command(self._runtime(), current_state, issue_number, request=request) + + def handle_accept_no_fls_changes_command(self, issue_number, comment_author, request=None): + return automation.handle_accept_no_fls_changes_command(self._runtime(), issue_number, comment_author, request=request) + + def get_commands_help(self): + return config.get_commands_help() + + def strip_code_blocks(self, comment_body): + return commands.strip_code_blocks(comment_body) + + def parse_command(self, comment_body): + return commands.parse_command(self._runtime(), comment_body) + + +class _BootstrapQueueAdapterServices: + def get_next_reviewer(self, state, skip_usernames=None): + return get_next_reviewer(state, skip_usernames) + + def record_assignment(self, state, github, issue_number, kind): + return record_assignment(state, github, issue_number, kind) + + def reposition_member_as_next(self, state, username): + return reposition_member_as_next(state, username) + + +class _BootstrapWorkflowAdapterServices: + def __init__(self, runtime_getter): + self._runtime_getter = runtime_getter + + def _runtime(self): + return self._runtime_getter() + + def process_pass_until_expirations(self, state): + return process_pass_until_expirations(state) + + def sync_members_with_queue(self, current_state): + return sync_members_with_queue(self._runtime(), current_state) + + def sync_status_labels_for_items(self, current_state, issue_numbers): + return reviews.sync_status_labels_for_items(self._runtime(), current_state, issue_numbers) + + def fetch_members(self): + return members.fetch_members(self._runtime()) + + +class _BootstrapAutomationAdapterServices: + def __init__(self, runtime_getter): + self._runtime_getter = runtime_getter + + def _runtime(self): + return self._runtime_getter() + + def run_command(self, command, cwd=None, check=True): + return automation.run_command(command, cwd, check) + + def summarize_output(self, result, limit=20): + return automation.summarize_output(result, limit) + + def list_changed_files(self, repo_root): + return automation.list_changed_files(repo_root) + + def get_default_branch(self): + return automation.get_default_branch(self._runtime()) + + def find_open_pr_for_branch_status(self, branch): + return automation.find_open_pr_for_branch_status(self._runtime(), branch) + + def create_pull_request(self, branch, base, issue_number): + return automation.create_pull_request(self._runtime(), branch, base, issue_number) + +class _BootstrapStateLockAdapterServices: + def __init__(self, runtime_getter, lock_services): + self._runtime_getter = runtime_getter + self._lock_services = lock_services + + def _runtime(self): + return self._runtime_getter() + + def normalize_lock_metadata(self, lock_meta): + return state_store.normalize_lock_metadata(lock_meta) + + def parse_iso8601_timestamp(self, value): + return state_store.parse_iso8601_timestamp(value) + + def get_state_issue(self): + return state_store.get_state_issue(self._runtime()) + + def clear_lock_metadata(self): + return lease_lock.clear_lock_metadata(self._runtime()) + + def get_state_issue_snapshot(self): + return state_store.get_state_issue_snapshot(self._runtime()) + + def conditional_patch_state_issue(self, body, etag=None): + return state_store.conditional_patch_state_issue(self._runtime(), body, etag) + + def render_state_issue_body(self, current_state, base_body=None, *, preserve_state_block=False): + return state_store.render_state_issue_body(current_state, base_body, preserve_state_block=preserve_state_block) + + def get_state_issue_html_url(self): + return lease_lock.get_state_issue_html_url(self._runtime()) + + def get_lock_ref_display(self): + return lease_lock.get_lock_ref_display(self._runtime()) + + def get_lock_ref_snapshot(self): + return lease_lock.get_lock_ref_snapshot(self._runtime()) + + def build_lock_metadata(self, *args, **kwargs): + return lease_lock.build_lock_metadata(self._runtime(), *args, **kwargs) + + def create_lock_commit(self, parent_sha, tree_sha, lock_meta): + return lease_lock.create_lock_commit(self._runtime(), parent_sha, tree_sha, lock_meta) + + def cas_update_lock_ref(self, new_sha): + return lease_lock.cas_update_lock_ref(self._runtime(), new_sha) + + def lock_is_currently_valid(self, lock_meta, now=None): + return lease_lock.lock_is_currently_valid(self._runtime(), lock_meta, now) + + def renew_state_issue_lease_lock(self, context): + return lease_lock.renew_state_issue_lease_lock(self._runtime(), context) + + def ensure_state_issue_lease_lock_fresh(self): + return self._lock_services.refresh() + + def acquire_state_issue_lease_lock(self): + return self._lock_services.acquire() + + def release_state_issue_lease_lock(self): + return self._lock_services.release() + + def get_active_lease_context(self): + return self._runtime().ACTIVE_LEASE_CONTEXT + + +class _BootstrapAdapterGroups: + def __init__(self, *, github, review_state, commands, queue, workflow, automation, state_lock): + self.github = github + self.review_state = review_state + self.commands = commands + self.queue = queue + self.workflow = workflow + self.automation = automation + self.state_lock = state_lock + + +def build_runtime(*, requests, sys, random, time, active_lease_context=None) -> ReviewerBotRuntime: + runtime: ReviewerBotRuntime | None = None + + def runtime_getter() -> ReviewerBotRuntime: + assert runtime is not None + return runtime + + config_service = _EnvConfig() + output_sink = _FileOutputSink(config_service) + deferred_payload_loader = _JsonDeferredPayloadLoader(config_service) + + state_store_services = _BootstrapStateStoreServices(runtime_getter) + github_services = _BootstrapGitHubServices(runtime_getter) + lock_services = _BootstrapLockServices(runtime_getter) + handlers = _BootstrapHandlerServices(runtime_getter) + adapters = _BootstrapAdapterGroups( + github=github_services, + review_state=_BootstrapReviewStateAdapterServices(runtime_getter), + commands=_BootstrapCommandAdapterServices(runtime_getter), + queue=_BootstrapQueueAdapterServices(), + workflow=_BootstrapWorkflowAdapterServices(runtime_getter), + automation=_BootstrapAutomationAdapterServices(runtime_getter), + state_lock=_BootstrapStateLockAdapterServices(runtime_getter, lock_services), + ) + + runtime = ReviewerBotRuntime( + requests=requests, + sys=sys, + random=random, + time=time, + config=config_service, + outputs=output_sink, + deferred_payloads=deferred_payload_loader, + state_store=state_store_services, + github=github_services, + locks=lock_services, + handlers=handlers, + adapters=adapters, + active_lease_context=active_lease_context, + ) + return runtime diff --git a/scripts/reviewer_bot_lib/commands.py b/scripts/reviewer_bot_lib/commands.py index 4dd9bfdec..fff8be8eb 100644 --- a/scripts/reviewer_bot_lib/commands.py +++ b/scripts/reviewer_bot_lib/commands.py @@ -1,14 +1,46 @@ """Reviewer-bot command parsing and handlers.""" -import json -import os import re -import subprocess -from datetime import datetime, timezone -from pathlib import Path +from datetime import datetime +from . import review_state from .config import AssignmentAttempt -from .guidance import get_issue_guidance, get_pr_guidance +from .context import AssignmentRequest +from .event_inputs import build_assignment_request as decode_assignment_request +from .guidance import ( + get_assignment_failure_comment, + get_issue_guidance, + get_pr_guidance, +) + + +def _log(bot, level: str, message: str, **fields) -> None: + bot.logger.event(level, message, **fields) + +_CONVERSATIONAL_WORDS = { + "i", + "we", + "you", + "the", + "a", + "an", + "is", + "are", + "can", + "could", + "would", + "should", + "please", + "thanks", + "thank", + "hi", + "hello", + "hey", +} + + +def build_assignment_request(bot, *, issue_number: int) -> AssignmentRequest: + return decode_assignment_request(bot, issue_number=issue_number) def strip_code_blocks(comment_body: str) -> str: @@ -41,8 +73,7 @@ def parse_command(bot, comment_body: str) -> tuple[str, list[str]] | None: malformed_match = re.search(malformed_pattern, comment_body, re.IGNORECASE | re.MULTILINE) if malformed_match: attempted = malformed_match.group(1).lower() - conversational = {"i", "we", "you", "the", "a", "an", "is", "are", "can", "could", "would", "should", "please", "thanks", "thank", "hi", "hello", "hey"} - if attempted in conversational: + if attempted in _CONVERSATIONAL_WORDS: return None if attempted in bot.COMMANDS or attempted in {"r?-user", "assign-from-queue"}: return "_malformed_known", [attempted] @@ -84,39 +115,62 @@ def parse_command(bot, comment_body: str) -> tuple[str, list[str]] | None: def _apply_assignment_side_effects( bot, state: dict, - issue_number: int, + request: AssignmentRequest, reviewer: str, assignment_method: str, - issue_author: str, ) -> tuple[AssignmentAttempt, str | None]: - is_pr = os.environ.get("IS_PULL_REQUEST", "false").lower() == "true" - assignment_attempt = bot.request_reviewer_assignment(issue_number, reviewer) - bot.set_current_reviewer(state, issue_number, reviewer, assignment_method=assignment_method) - bot.record_assignment(state, reviewer, issue_number, "pr" if is_pr else "issue") - failure_comment = bot.get_assignment_failure_comment(reviewer, assignment_attempt) + issue_number = request.issue_number + if request.is_pull_request: + assignment_attempt = bot.github.request_pr_reviewer_assignment(issue_number, reviewer) + else: + assignment_attempt = bot.github.assign_issue_assignee(issue_number, reviewer) + review_state.set_current_reviewer(state, issue_number, reviewer, assignment_method=assignment_method) + bot.adapters.queue.record_assignment(state, reviewer, issue_number, "pr" if request.is_pull_request else "issue") + failure_comment = get_assignment_failure_comment( + reviewer, + assignment_attempt, + is_pull_request=request.is_pull_request, + ) if failure_comment: - bot.post_comment(issue_number, failure_comment) + bot.github.post_comment(issue_number, failure_comment) if assignment_attempt.success: - if is_pr: - bot.post_comment(issue_number, get_pr_guidance(reviewer, issue_author)) + if request.is_pull_request: + bot.github.post_comment(issue_number, get_pr_guidance(reviewer, request.issue_author)) else: - labels = set(parse_issue_labels()) + labels = set(request.issue_labels) guidance = ( - bot.get_fls_audit_guidance(reviewer, issue_author) + bot.get_fls_audit_guidance(reviewer, request.issue_author) if bot.FLS_AUDIT_LABEL in labels - else get_issue_guidance(reviewer, issue_author) + else get_issue_guidance(reviewer, request.issue_author) ) - bot.post_comment(issue_number, guidance) + bot.github.post_comment(issue_number, guidance) return assignment_attempt, failure_comment -def handle_pass_command(bot, state: dict, issue_number: int, comment_author: str, reason: str | None) -> tuple[str, bool]: - issue_data = bot.ensure_review_entry(state, issue_number, create=True) +def _current_assignees_or_error(bot, issue_number: int) -> tuple[list[str] | None, str | None]: + current_assignees = bot.github.get_issue_assignees(issue_number) + if current_assignees is None: + return None, "❌ Unable to determine current assignees/reviewers from GitHub; refusing to continue." + return current_assignees, None + + +def handle_pass_command( + bot, + state: dict, + issue_number: int, + comment_author: str, + reason: str | None, + request: AssignmentRequest | None = None, +) -> tuple[str, bool]: + assignment_request = request or build_assignment_request(bot, issue_number=issue_number) + issue_data = review_state.ensure_review_entry(state, issue_number, create=True) if issue_data is None: return "❌ Unable to load review state.", False passed_reviewer = issue_data.get("current_reviewer") if not passed_reviewer: - current_assignees = bot.get_issue_assignees(issue_number) + current_assignees, assignee_error = _current_assignees_or_error(bot, issue_number) + if assignee_error: + return assignee_error, False passed_reviewer = current_assignees[0] if current_assignees else None if not passed_reviewer: return "❌ No reviewer is currently assigned to pass.", False @@ -125,22 +179,23 @@ def handle_pass_command(bot, state: dict, issue_number: int, comment_author: str is_first_pass = len(issue_data["skipped"]) == 0 if passed_reviewer not in issue_data["skipped"]: issue_data["skipped"].append(passed_reviewer) - issue_author = os.environ.get("ISSUE_AUTHOR", "") skip_set = set(issue_data["skipped"]) - if issue_author: - skip_set.add(issue_author) - next_reviewer = bot.get_next_reviewer(state, skip_usernames=skip_set) + if assignment_request.issue_author: + skip_set.add(assignment_request.issue_author) + next_reviewer = bot.adapters.queue.get_next_reviewer(state, skip_usernames=skip_set) if not next_reviewer: return ("❌ No other reviewers available. Everyone in the queue has either passed on this issue or is the author."), False - bot.reposition_member_as_next(state, passed_reviewer) - bot.unassign_reviewer(issue_number, passed_reviewer) + bot.adapters.queue.reposition_member_as_next(state, passed_reviewer) + if assignment_request.is_pull_request: + bot.github.remove_pr_reviewer(issue_number, passed_reviewer) + else: + bot.github.remove_issue_assignee(issue_number, passed_reviewer) assignment_attempt, failure_comment = _apply_assignment_side_effects( bot, state, - issue_number, + assignment_request, next_reviewer, "round-robin", - issue_author, ) assignment_line = f"@{next_reviewer} is now assigned as the reviewer." if not assignment_attempt.success: @@ -156,12 +211,22 @@ def handle_pass_command(bot, state: dict, issue_number: int, comment_author: str return (f"✅ @{passed_reviewer} has passed this review.{reason_text}\n\n{assignment_line}\n\n_@{original_passer} remains next in queue for future issues._"), True -def handle_pass_until_command(bot, state: dict, issue_number: int, comment_author: str, return_date: str, reason: str | None) -> tuple[str, bool]: +def handle_pass_until_command( + bot, + state: dict, + issue_number: int, + comment_author: str, + return_date: str, + reason: str | None, + request: AssignmentRequest | None = None, +) -> tuple[str, bool]: + assignment_request = request or build_assignment_request(bot, issue_number=issue_number) try: parsed_date = datetime.strptime(return_date, "%Y-%m-%d").date() except ValueError: return (f"❌ Invalid date format: `{return_date}`. Please use YYYY-MM-DD format (e.g., 2025-02-01)."), False - if parsed_date <= datetime.now(timezone.utc).date(): + normalized_return_date = parsed_date.isoformat() + if parsed_date <= bot.clock.now().date(): return "❌ Return date must be in the future.", False user_in_queue = None user_index = None @@ -173,13 +238,13 @@ def handle_pass_until_command(bot, state: dict, issue_number: int, comment_autho if not user_in_queue: for entry in state.get("pass_until", []): if entry["github"].lower() == comment_author.lower(): - entry["return_date"] = return_date + entry["return_date"] = normalized_return_date if reason: entry["reason"] = reason - return (f"✅ Updated your return date to {return_date}.\n\nYou're already marked as away."), True + return (f"✅ Updated your return date to {normalized_return_date}.\n\nYou're already marked as away."), True return (f"❌ @{comment_author} is not in the reviewer queue. Only Producers can use this command."), False state["queue"].remove(user_in_queue) - pass_entry = {"github": user_in_queue["github"], "name": user_in_queue.get("name", user_in_queue["github"]), "return_date": return_date, "original_queue_position": user_index} + pass_entry = {"github": user_in_queue["github"], "name": user_in_queue.get("name", user_in_queue["github"]), "return_date": normalized_return_date, "original_queue_position": user_index} if reason: pass_entry["reason"] = reason state["pass_until"].append(pass_entry) @@ -195,22 +260,25 @@ def handle_pass_until_command(bot, state: dict, issue_number: int, comment_autho issue_data = state["active_reviews"][issue_key] if isinstance(issue_data, dict): tracked_reviewer = issue_data.get("current_reviewer") - current_assignees = bot.get_issue_assignees(issue_number) + current_assignees, assignee_error = _current_assignees_or_error(bot, issue_number) + if assignee_error: + return assignee_error, False is_current_reviewer = ((tracked_reviewer and tracked_reviewer.lower() == comment_author.lower()) or comment_author.lower() in [a.lower() for a in current_assignees]) reassigned_msg = "" if is_current_reviewer: - bot.unassign_reviewer(issue_number, comment_author) - issue_author = os.environ.get("ISSUE_AUTHOR", "") - skip_set = {issue_author} if issue_author else set() - next_reviewer = bot.get_next_reviewer(state, skip_usernames=skip_set) + if assignment_request.is_pull_request: + bot.github.remove_pr_reviewer(issue_number, comment_author) + else: + bot.github.remove_issue_assignee(issue_number, comment_author) + skip_set = {assignment_request.issue_author} if assignment_request.issue_author else set() + next_reviewer = bot.adapters.queue.get_next_reviewer(state, skip_usernames=skip_set) if next_reviewer: assignment_attempt, failure_comment = _apply_assignment_side_effects( bot, state, - issue_number, + assignment_request, next_reviewer, "round-robin", - issue_author, ) if assignment_attempt.success: reassigned_msg = f"\n\n@{next_reviewer} has been assigned as the new reviewer for this issue." @@ -225,15 +293,22 @@ def handle_pass_until_command(bot, state: dict, issue_number: int, comment_autho state["active_reviews"][issue_key]["current_reviewer"] = None reassigned_msg = "\n\n⚠️ No other reviewers available to assign." reason_text = f" ({reason})" if reason else "" - return (f"✅ @{comment_author} is now away until {return_date}{reason_text}.\n\nYou'll be automatically added back to the queue on that date.{reassigned_msg}"), True + return (f"✅ @{comment_author} is now away until {normalized_return_date}{reason_text}.\n\nYou'll be automatically added back to the queue on that date.{reassigned_msg}"), True -def handle_label_command(bot, state: dict, issue_number: int, label_string: str) -> tuple[str, bool, bool]: +def handle_label_command( + bot, + state: dict, + issue_number: int, + label_string: str, + request: AssignmentRequest | None = None, +) -> tuple[str, bool, bool]: + assignment_request = request or build_assignment_request(bot, issue_number=issue_number) pattern = r'(?:(?<=^)|(?<=\s))([+-])(.+?)(?=\s[+-]|\s*$)' matches = re.findall(pattern, label_string) if not matches: return "❌ No valid labels found. Use `+label-name` to add or `-label-name` to remove.", False, False - existing_labels = bot.get_repo_labels() + existing_labels = bot.github.get_repo_labels() results = [] all_success = True state_changed = False @@ -245,21 +320,20 @@ def handle_label_command(bot, state: dict, issue_number: int, label_string: str) if label not in existing_labels: results.append(f"⚠️ Label `{label}` does not exist in this repository") all_success = False - elif bot.add_label(issue_number, label): + elif bot.github.add_label(issue_number, label): results.append(f"✅ Added label `{label}`") - if label == "sign-off: create pr" and os.environ.get("IS_PULL_REQUEST", "false").lower() != "true": - review_data = bot.ensure_review_entry(state, issue_number) + if label == "sign-off: create pr" and not assignment_request.is_pull_request: + review_data = review_state.ensure_review_entry(state, issue_number) reviewer = review_data.get("current_reviewer") if review_data else None - completion_changed = bot.mark_review_complete( + completion_changed = review_state.mark_review_complete( state, issue_number, reviewer, "issue_label: sign-off: create pr" ) - status_changed = bot.sync_status_labels_for_items(state, [issue_number]) - state_changed = completion_changed or status_changed or state_changed + state_changed = completion_changed or state_changed else: results.append(f"❌ Failed to add label `{label}`") all_success = False elif action == "-": - if bot.remove_label(issue_number, label): + if bot.github.remove_label(issue_number, label): results.append(f"✅ Removed label `{label}`") else: results.append(f"❌ Failed to remove label `{label}`") @@ -269,181 +343,26 @@ def handle_label_command(bot, state: dict, issue_number: int, label_string: str) return "\n".join(results), all_success, state_changed -def parse_issue_labels() -> list[str]: - labels_json = os.environ.get("ISSUE_LABELS", "[]") - try: - labels = json.loads(labels_json) - except json.JSONDecodeError: - labels = [] - if not isinstance(labels, list): - return [] - return [str(label) for label in labels] - - -def run_command(command: list[str], cwd: Path, check: bool = True) -> subprocess.CompletedProcess: - result = subprocess.run(command, cwd=cwd, capture_output=True, text=True) - if check and result.returncode != 0: - raise RuntimeError((result.stderr or result.stdout or "Command failed").strip()) - return result - - -def summarize_output(result: subprocess.CompletedProcess, limit: int = 20) -> str: - combined = "\n".join([line for line in [result.stdout, result.stderr] if line]).strip() - if not combined: - return "" - lines = combined.splitlines() - return "\n".join(lines[-limit:]) - - -def list_changed_files(repo_root: Path) -> list[str]: - files: list[str] = [] - for command in (["git", "diff", "--name-only"], ["git", "diff", "--cached", "--name-only"]): - result = run_command(command, cwd=repo_root) - for line in result.stdout.splitlines(): - path = line.strip() - if path: - files.append(path) - return sorted(set(files)) - - -def get_target_repo_root() -> Path: - configured = os.environ.get("REVIEWER_BOT_TARGET_REPO_ROOT", "").strip() - if configured: - return Path(configured) - return Path(__file__).resolve().parents[2] - - -def get_default_branch(bot) -> str: - repo_info = bot.github_api("GET", "") - if isinstance(repo_info, dict): - return repo_info.get("default_branch", "main") - return "main" - - -def find_open_pr_for_branch(bot, branch: str) -> dict | None: - owner = os.environ.get("REPO_OWNER", "").strip() - branch = branch.strip() - if not owner or not branch: - return None - response = bot.github_api("GET", f"pulls?state=open&head={owner}:{branch}") - if isinstance(response, list) and response: - first = response[0] - if isinstance(first, dict): - return first - return None - - -def resolve_workflow_run_pr_number(bot) -> int: - pr_number_raw = os.environ.get("WORKFLOW_RUN_RECONCILE_PR_NUMBER", "").strip() - if not pr_number_raw: - raise RuntimeError("Missing WORKFLOW_RUN_RECONCILE_PR_NUMBER in workflow_run reconcile context") - try: - pr_number = int(pr_number_raw) - except ValueError as exc: - raise RuntimeError("WORKFLOW_RUN_RECONCILE_PR_NUMBER must be a positive integer") from exc - if pr_number <= 0: - raise RuntimeError("WORKFLOW_RUN_RECONCILE_PR_NUMBER must be a positive integer") - reconcile_head_sha = os.environ.get("WORKFLOW_RUN_RECONCILE_HEAD_SHA", "").strip() - if not reconcile_head_sha: - raise RuntimeError("Missing WORKFLOW_RUN_RECONCILE_HEAD_SHA in workflow_run reconcile context") - workflow_run_head_sha = os.environ.get("WORKFLOW_RUN_HEAD_SHA", "").strip() - if not workflow_run_head_sha: - raise RuntimeError("Missing WORKFLOW_RUN_HEAD_SHA for workflow_run reconcile") - if reconcile_head_sha != workflow_run_head_sha: - raise RuntimeError("Workflow_run reconcile context SHA mismatch between artifact and workflow payload") - pull_request = bot.github_api("GET", f"pulls/{pr_number}") - if not isinstance(pull_request, dict): - raise RuntimeError(f"Failed to fetch pull request #{pr_number} during workflow_run reconcile") - head = pull_request.get("head") - pull_request_head_sha = "" - if isinstance(head, dict): - head_sha = head.get("sha") - if isinstance(head_sha, str): - pull_request_head_sha = head_sha.strip() - if not pull_request_head_sha: - raise RuntimeError(f"Pull request #{pr_number} is missing a valid head SHA") - if pull_request_head_sha != reconcile_head_sha: - raise RuntimeError(f"Pull request #{pr_number} head SHA does not match workflow_run reconcile context") - print(f"Resolved workflow_run PR from reconcile context: #{pr_number}") - return pr_number - - -def create_pull_request(bot, branch: str, base: str, issue_number: int) -> dict | None: - existing = bot.find_open_pr_for_branch(branch) - if existing: - return existing - title = "chore: update spec.lock (no guideline impact)" - body = "Updates `src/spec.lock` after confirming the audit reported no affected guidelines.\n\n" f"Closes #{issue_number}" - response = bot.github_api("POST", "pulls", {"title": title, "head": branch, "base": base, "body": body}) - if isinstance(response, dict): - return response - return None - - -def handle_accept_no_fls_changes_command(bot, issue_number: int, comment_author: str) -> tuple[str, bool]: - if os.environ.get("IS_PULL_REQUEST", "false").lower() == "true": - return "❌ This command can only be used on issues, not PRs.", False - labels = bot.parse_issue_labels() - if bot.FLS_AUDIT_LABEL not in labels: - return "❌ This command is only available on issues labeled `fls-audit`.", False - if not bot.check_user_permission(comment_author, "triage"): - return "❌ You must have triage permissions to run this command.", False - repo_root = get_target_repo_root() - if bot.list_changed_files(repo_root): - return "❌ Working tree is not clean; refusing to update spec.lock.", False - audit_result = bot.run_command(["uv", "run", "--locked", "python", "scripts/fls_audit.py", "--summary-only", "--fail-on-impact"], cwd=repo_root, check=False) - if audit_result.returncode == 2: - return ("❌ The audit reports affected guidelines. Please review and open a PR with the necessary guideline updates instead."), False - if audit_result.returncode != 0: - details = bot.summarize_output(audit_result) - detail_text = f"\n\nDetails:\n```\n{details}\n```" if details else "" - return (f"❌ Audit command failed.{detail_text}"), False - update_result = bot.run_command(["uv", "run", "--locked", "python", "./make.py", "--update-spec-lock-file"], cwd=repo_root, check=False) - if update_result.returncode != 0: - details = bot.summarize_output(update_result) - detail_text = f"\n\nDetails:\n```\n{details}\n```" if details else "" - return (f"❌ Failed to update spec.lock.{detail_text}"), False - changed_files = bot.list_changed_files(repo_root) - if not changed_files: - return "✅ `src/spec.lock` is already up to date; no PR needed.", True - unexpected = {path for path in changed_files if path != "src/spec.lock"} - if unexpected: - paths = ", ".join(sorted(unexpected)) - return (f"❌ Unexpected tracked file changes detected; refusing to open a PR. Please review: {paths}"), False - branch_date = datetime.now(timezone.utc).strftime("%Y-%m-%d") - base_branch = bot.get_default_branch() - branch_name = f"chore/spec-lock-{branch_date}-issue-{issue_number}" - if bot.run_command(["git", "rev-parse", "--verify", branch_name], cwd=repo_root, check=False).returncode == 0: - suffix = datetime.now(timezone.utc).strftime("%H%M%S") - branch_name = f"{branch_name}-{suffix}" - try: - bot.run_command(["git", "checkout", "-b", branch_name], cwd=repo_root) - bot.run_command(["git", "add", "src/spec.lock"], cwd=repo_root) - bot.run_command(["git", "-c", "user.name=guidelines-bot", "-c", "user.email=guidelines-bot@users.noreply.github.com", "commit", "-m", "chore: update spec.lock; no affected guidelines"], cwd=repo_root) - bot.run_command(["git", "push", "origin", branch_name], cwd=repo_root) - except RuntimeError as exc: - return (f"❌ Failed to create branch or push changes: {exc}"), False - pr = bot.create_pull_request(branch_name, base_branch, issue_number) - if not pr or "html_url" not in pr: - return "❌ Failed to open a pull request for the spec.lock update.", False - return (f"✅ Opened PR {pr['html_url']}"), True - - def handle_sync_members_command(bot, state: dict) -> tuple[str, bool]: - state, changes = bot.sync_members_with_queue(state) + state, changes = bot.adapters.workflow.sync_members_with_queue(state) if changes: changes_text = "\n".join(f"- {change}" for change in changes) return f"✅ Queue synced with members.md:\n\n{changes_text}", True return "✅ Queue is already in sync with members.md.", True -def handle_queue_command(bot, state: dict) -> tuple[str, bool]: +def handle_queue_command( + bot, + state: dict, + request: AssignmentRequest | None = None, +) -> tuple[str, bool]: + del request queue_size = len(state["queue"]) - repo_owner = os.environ.get("REPO_OWNER", "") - repo_name = os.environ.get("REPO_NAME", "") + github_repository = bot.get_config_value("GITHUB_REPOSITORY").strip() + state_issue_number = bot.state_issue_number() state_issue_link = "" - if repo_owner and repo_name and bot.STATE_ISSUE_NUMBER: - state_issue_link = f"\n\n[View full state details](https://github.com/{repo_owner}/{repo_name}/issues/{bot.STATE_ISSUE_NUMBER})" + if github_repository and state_issue_number: + state_issue_link = f"\n\n[View full state details](https://github.com/{github_repository}/issues/{state_issue_number})" if queue_size == 0: return f"📊 **Queue Status**: No reviewers in queue.{state_issue_link}", True current_index = state["current_index"] @@ -467,24 +386,34 @@ def handle_commands_command(bot) -> tuple[str, bool]: return (f"ℹ️ **Available Commands**\n\n**Pass or step away:**\n- `{bot.BOT_MENTION} /pass [reason]` - Pass this review to next in queue (current reviewer only)\n- `{bot.BOT_MENTION} /away YYYY-MM-DD [reason]` - Step away from queue until a date\n- `{bot.BOT_MENTION} /release [@username] [reason]` - Release assignment (yours or someone else's with triage+ permission)\n\n**Assign reviewers:**\n- `{bot.BOT_MENTION} /r? @username` - Assign a specific reviewer\n- `{bot.BOT_MENTION} /r? producers` - Request the next reviewer from the queue\n- `{bot.BOT_MENTION} /claim` - Claim this review for yourself\n\n**Other:**\n- `{bot.BOT_MENTION} /label +label-name` - Add a label\n- `{bot.BOT_MENTION} /label -label-name` - Remove a label\n- `{bot.BOT_MENTION} /rectify` - Reconcile this issue/PR review state from GitHub\n- `{bot.BOT_MENTION} /accept-no-fls-changes` - Update spec.lock and open a PR when no guidelines are impacted\n- `{bot.BOT_MENTION} /queue` - Show current queue status\n- `{bot.BOT_MENTION} /sync-members` - Sync queue with members.md"), True -def handle_claim_command(bot, state: dict, issue_number: int, comment_author: str) -> tuple[str, bool]: +def handle_claim_command( + bot, + state: dict, + issue_number: int, + comment_author: str, + request: AssignmentRequest | None = None, +) -> tuple[str, bool]: + assignment_request = request or build_assignment_request(bot, issue_number=issue_number) is_producer = any(member["github"].lower() == comment_author.lower() for member in state["queue"]) is_away = any(member["github"].lower() == comment_author.lower() for member in state.get("pass_until", [])) if not is_producer and not is_away: return (f"❌ @{comment_author} is not in the reviewer queue. Only Producers can claim reviews."), False if is_away: return (f"❌ @{comment_author} is currently marked as away. Please use `{bot.BOT_MENTION} /away YYYY-MM-DD` to update your return date first, or wait until your scheduled return."), False - current_assignees = bot.get_issue_assignees(issue_number) + current_assignees, assignee_error = _current_assignees_or_error(bot, issue_number) + if assignee_error: + return assignee_error, False for assignee in current_assignees: - bot.unassign_reviewer(issue_number, assignee) - issue_author = os.environ.get("ISSUE_AUTHOR", "") + if assignment_request.is_pull_request: + bot.github.remove_pr_reviewer(issue_number, assignee) + else: + bot.github.remove_issue_assignee(issue_number, assignee) assignment_attempt, failure_comment = _apply_assignment_side_effects( bot, state, - issue_number, + assignment_request, comment_author, "claim", - issue_author, ) prev_text = f" (previously: @{', @'.join(current_assignees)})" if current_assignees else "" response = f"✅ @{comment_author} has claimed this review{prev_text}." @@ -494,8 +423,16 @@ def handle_claim_command(bot, state: dict, issue_number: int, comment_author: st return response, True -def handle_release_command(bot, state: dict, issue_number: int, comment_author: str, args: list | None = None) -> tuple[str, bool]: +def handle_release_command( + bot, + state: dict, + issue_number: int, + comment_author: str, + args: list | None = None, + request: AssignmentRequest | None = None, +) -> tuple[str, bool]: args = args or [] + request = request or build_assignment_request(bot, issue_number=issue_number) target_username = None reason = None releasing_other = False @@ -503,7 +440,10 @@ def handle_release_command(bot, state: dict, issue_number: int, comment_author: target_username = args[0].lstrip("@") reason = " ".join(args[1:]) if len(args) > 1 else None releasing_other = target_username.lower() != comment_author.lower() - if releasing_other and not bot.check_user_permission(comment_author, "triage"): + permission_status = bot.github.get_user_permission_status(comment_author, "triage") + if permission_status == "unavailable": + return "❌ Unable to verify triage permissions right now; refusing to continue.", False + if releasing_other and permission_status != "granted": return (f"❌ @{comment_author} does not have permission to release other reviewers. Triage access or higher is required."), False else: target_username = comment_author @@ -516,7 +456,9 @@ def handle_release_command(bot, state: dict, issue_number: int, comment_author: if isinstance(issue_data, dict): tracked_reviewer = issue_data.get("current_reviewer") assignment_method = issue_data.get("assignment_method") - current_assignees = bot.get_issue_assignees(issue_number) + current_assignees, assignee_error = _current_assignees_or_error(bot, issue_number) + if assignee_error: + return assignee_error, False is_tracked = tracked_reviewer and tracked_reviewer.lower() == target_username.lower() is_assigned = target_username.lower() in [assignee.lower() for assignee in current_assignees] if not is_tracked and not is_assigned: @@ -531,18 +473,28 @@ def handle_release_command(bot, state: dict, issue_number: int, comment_author: if current_assignees: return (f"❌ @{comment_author} is not assigned to this issue/PR. Current assignee(s): @{', @'.join(current_assignees)}"), False return "❌ No reviewer is currently assigned to release.", False - bot.unassign_reviewer(issue_number, target_username) + if request.is_pull_request: + bot.github.remove_pr_reviewer(issue_number, target_username) + else: + bot.github.remove_issue_assignee(issue_number, target_username) if "active_reviews" in state and issue_key in state["active_reviews"] and isinstance(state["active_reviews"][issue_key], dict): state["active_reviews"][issue_key]["current_reviewer"] = None if assignment_method == "round-robin": - bot.reposition_member_as_next(state, target_username) + bot.adapters.queue.reposition_member_as_next(state, target_username) reason_text = f" Reason: {reason}" if reason else "" if releasing_other: return (f"✅ @{comment_author} has released @{target_username} from this review.{reason_text}\n\n_This issue/PR is now unassigned. Use `{bot.BOT_MENTION} /r? producers` to assign the next reviewer from the queue, or `{bot.BOT_MENTION} /claim` to claim it._"), True return (f"✅ @{target_username} has released this review.{reason_text}\n\n_This issue/PR is now unassigned. Use `{bot.BOT_MENTION} /r? producers` to assign the next reviewer from the queue, or `{bot.BOT_MENTION} /claim` to claim it._"), True -def handle_assign_command(bot, state: dict, issue_number: int, username: str) -> tuple[str, bool]: +def handle_assign_command( + bot, + state: dict, + issue_number: int, + username: str, + request: AssignmentRequest | None = None, +) -> tuple[str, bool]: + assignment_request = request or build_assignment_request(bot, issue_number=issue_number) username = username.lstrip("@") if not username: return (f"❌ Missing username. Usage: `{bot.BOT_MENTION} /r? @username`"), False @@ -555,17 +507,20 @@ def handle_assign_command(bot, state: dict, issue_number: int, username: str) -> if entry["github"].lower() == username.lower(): return_date = entry.get("return_date", "unknown") return (f"⚠️ @{username} is currently marked as away until {return_date}. Consider assigning someone else or waiting."), False - current_assignees = bot.get_issue_assignees(issue_number) + current_assignees, assignee_error = _current_assignees_or_error(bot, issue_number) + if assignee_error: + return assignee_error, False for assignee in current_assignees: - bot.unassign_reviewer(issue_number, assignee) - issue_author = os.environ.get("ISSUE_AUTHOR", "") + if assignment_request.is_pull_request: + bot.github.remove_pr_reviewer(issue_number, assignee) + else: + bot.github.remove_issue_assignee(issue_number, assignee) assignment_attempt, failure_comment = _apply_assignment_side_effects( bot, state, - issue_number, + assignment_request, username, "manual", - issue_author, ) prev_text = f" (previously: @{', @'.join(current_assignees)})" if current_assignees else "" if assignment_attempt.success: @@ -576,22 +531,31 @@ def handle_assign_command(bot, state: dict, issue_number: int, username: str) -> return response, True -def handle_assign_from_queue_command(bot, state: dict, issue_number: int) -> tuple[str, bool]: - current_assignees = bot.get_issue_assignees(issue_number) +def handle_assign_from_queue_command( + bot, + state: dict, + issue_number: int, + request: AssignmentRequest | None = None, +) -> tuple[str, bool]: + assignment_request = request or build_assignment_request(bot, issue_number=issue_number) + current_assignees, assignee_error = _current_assignees_or_error(bot, issue_number) + if assignee_error: + return assignee_error, False for assignee in current_assignees: - bot.unassign_reviewer(issue_number, assignee) - issue_author = os.environ.get("ISSUE_AUTHOR", "") - skip_set = {issue_author} if issue_author else set() - next_reviewer = bot.get_next_reviewer(state, skip_usernames=skip_set) + if assignment_request.is_pull_request: + bot.github.remove_pr_reviewer(issue_number, assignee) + else: + bot.github.remove_issue_assignee(issue_number, assignee) + skip_set = {assignment_request.issue_author} if assignment_request.issue_author else set() + next_reviewer = bot.adapters.queue.get_next_reviewer(state, skip_usernames=skip_set) if not next_reviewer: return (f"❌ No reviewers available in the queue. Please use `{bot.BOT_MENTION} /sync-members` to update the queue."), False assignment_attempt, _failure_comment = _apply_assignment_side_effects( bot, state, - issue_number, + assignment_request, next_reviewer, "round-robin", - issue_author, ) prev_text = f" (previously: @{', @'.join(current_assignees)})" if current_assignees else "" if assignment_attempt.success: diff --git a/scripts/reviewer_bot_lib/comment_application.py b/scripts/reviewer_bot_lib/comment_application.py new file mode 100644 index 000000000..e276424c5 --- /dev/null +++ b/scripts/reviewer_bot_lib/comment_application.py @@ -0,0 +1,324 @@ +"""Comment mutation and replay application helpers.""" + +from __future__ import annotations + +import hashlib +from dataclasses import dataclass + +from scripts.reviewer_bot_core import ( + comment_command_policy, + comment_freshness_policy, + privileged_command_policy, +) + +from . import commands as commands_module +from . import config as config_module +from .context import AssignmentRequest, CommentEventRequest +from .review_state import ( + accept_channel_event, + ensure_review_entry, + record_reviewer_activity, +) +from .runtime_protocols import CommentApplicationRuntimeContext + + +@dataclass(frozen=True) +class CommandExecutionResult: + response: str + success: bool + state_changed: bool + + +def _route_comment_application(classified: dict, *, comment_id: int) -> str: + comment_class = str(classified.get("comment_class", "")) + command_count = int(classified.get("command_count", 0)) + if comment_class in {"plain_text", "command_plus_text"} and comment_id > 0: + if comment_class in {"command_only", "command_plus_text"} and command_count == 1: + return "both" + return "freshness_only" + if comment_class in {"command_only", "command_plus_text"} and command_count == 1: + return "command_only" + return "noop" + + +def normalize_comment_body(body: str) -> str: + return "\n".join(line.rstrip() for line in body.replace("\r\n", "\n").split("\n")).strip() + + +def digest_comment_body(body: str) -> str: + return hashlib.sha256(normalize_comment_body(body).encode("utf-8")).hexdigest() + + +def record_conversation_freshness( + bot: CommentApplicationRuntimeContext, + state: dict, + request: CommentEventRequest, +) -> bool: + issue_number = request.issue_number + review_data = ensure_review_entry(state, issue_number, create=True) + if review_data is None: + return False + decision = comment_freshness_policy.decide_comment_freshness(review_data, request) + if decision.kind != "accept_channel_event": + return False + changed = accept_channel_event( + review_data, + str(decision.channel_name), + semantic_key=str(decision.semantic_key), + timestamp=str(decision.timestamp), + actor=str(decision.actor), + ) + if decision.update_reviewer_activity: + previous_activity = review_data.get("last_reviewer_activity") + previous_warning = review_data.get("transition_warning_sent") + previous_notice = review_data.get("transition_notice_sent_at") + record_reviewer_activity(review_data, str(decision.timestamp)) + activity_changed = ( + previous_activity != review_data.get("last_reviewer_activity") + or previous_warning != review_data.get("transition_warning_sent") + or previous_notice != review_data.get("transition_notice_sent_at") + ) + return changed or activity_changed + return changed + + +def _build_assignment_request_from_comment_request(request: CommentEventRequest) -> AssignmentRequest: + return AssignmentRequest( + issue_number=request.issue_number, + issue_author=request.issue_author, + is_pull_request=request.is_pull_request, + issue_labels=request.issue_labels, + ) + + +def _build_execution_result(command_id: comment_command_policy.OrdinaryCommandId, result) -> CommandExecutionResult: + if command_id in { + comment_command_policy.OrdinaryCommandId.PASS, + comment_command_policy.OrdinaryCommandId.AWAY, + comment_command_policy.OrdinaryCommandId.SYNC_MEMBERS, + comment_command_policy.OrdinaryCommandId.CLAIM, + comment_command_policy.OrdinaryCommandId.RELEASE, + comment_command_policy.OrdinaryCommandId.ASSIGN_SPECIFIC, + comment_command_policy.OrdinaryCommandId.ASSIGN_FROM_QUEUE, + }: + response, success = result + return CommandExecutionResult(response=response, success=success, state_changed=success) + if command_id in { + comment_command_policy.OrdinaryCommandId.QUEUE, + comment_command_policy.OrdinaryCommandId.COMMANDS, + }: + response, success = result + return CommandExecutionResult(response=response, success=success, state_changed=False) + response, success, state_changed = result + return CommandExecutionResult(response=response, success=success, state_changed=state_changed) + + +def _execute_pass(bot, state: dict, decision, assignment_request: AssignmentRequest | None) -> CommandExecutionResult: + return _build_execution_result( + decision.command_id, + commands_module.handle_pass_command( + bot, + state, + decision.issue_number, + decision.actor, + " ".join(decision.raw_args) if decision.raw_args else None, + request=assignment_request, + ), + ) + + +def _execute_away(bot, state: dict, decision, assignment_request: AssignmentRequest | None) -> CommandExecutionResult: + return _build_execution_result( + decision.command_id, + commands_module.handle_pass_until_command( + bot, + state, + decision.issue_number, + decision.actor, + decision.raw_args[0], + " ".join(decision.raw_args[1:]) if len(decision.raw_args) > 1 else None, + request=assignment_request, + ), + ) + + +def _execute_label(bot, state: dict, decision, assignment_request: AssignmentRequest | None) -> CommandExecutionResult: + return _build_execution_result( + decision.command_id, + commands_module.handle_label_command( + bot, + state, + decision.issue_number, + " ".join(decision.raw_args), + request=assignment_request, + ), + ) + + +def _execute_sync_members(bot, state: dict, decision, assignment_request: AssignmentRequest | None) -> CommandExecutionResult: + del decision, assignment_request + return _build_execution_result(comment_command_policy.OrdinaryCommandId.SYNC_MEMBERS, commands_module.handle_sync_members_command(bot, state)) + + +def _execute_queue(bot, state: dict, decision, assignment_request: AssignmentRequest | None) -> CommandExecutionResult: + del decision + if assignment_request is None: + result = commands_module.handle_queue_command(bot, state) + else: + result = commands_module.handle_queue_command(bot, state, request=assignment_request) + return _build_execution_result(comment_command_policy.OrdinaryCommandId.QUEUE, result) + + +def _execute_commands(bot, state: dict, decision, assignment_request: AssignmentRequest | None) -> CommandExecutionResult: + del state, decision, assignment_request + return _build_execution_result(comment_command_policy.OrdinaryCommandId.COMMANDS, commands_module.handle_commands_command(bot)) + + +def _execute_claim(bot, state: dict, decision, assignment_request: AssignmentRequest | None) -> CommandExecutionResult: + return _build_execution_result( + decision.command_id, + commands_module.handle_claim_command(bot, state, decision.issue_number, decision.actor, request=assignment_request), + ) + + +def _execute_release(bot, state: dict, decision, assignment_request: AssignmentRequest | None) -> CommandExecutionResult: + return _build_execution_result( + decision.command_id, + commands_module.handle_release_command(bot, state, decision.issue_number, decision.actor, list(decision.raw_args), request=assignment_request), + ) + + +def _execute_rectify(bot, state: dict, decision, assignment_request: AssignmentRequest | None) -> CommandExecutionResult: + del assignment_request + from . import reconcile as reconcile_module + + return _build_execution_result( + decision.command_id, + reconcile_module.handle_rectify_command(bot, state, decision.issue_number, decision.actor), + ) + + +def _execute_assign_specific(bot, state: dict, decision, assignment_request: AssignmentRequest | None) -> CommandExecutionResult: + username = decision.raw_args[0] if decision.raw_args else "" + return _build_execution_result( + decision.command_id, + commands_module.handle_assign_command(bot, state, decision.issue_number, username, request=assignment_request), + ) + + +def _execute_assign_from_queue(bot, state: dict, decision, assignment_request: AssignmentRequest | None) -> CommandExecutionResult: + return _build_execution_result( + decision.command_id, + commands_module.handle_assign_from_queue_command(bot, state, decision.issue_number, request=assignment_request), + ) + + +ORDINARY_COMMAND_HANDLERS = { + comment_command_policy.OrdinaryCommandId.PASS: _execute_pass, + comment_command_policy.OrdinaryCommandId.AWAY: _execute_away, + comment_command_policy.OrdinaryCommandId.LABEL: _execute_label, + comment_command_policy.OrdinaryCommandId.SYNC_MEMBERS: _execute_sync_members, + comment_command_policy.OrdinaryCommandId.QUEUE: _execute_queue, + comment_command_policy.OrdinaryCommandId.COMMANDS: _execute_commands, + comment_command_policy.OrdinaryCommandId.CLAIM: _execute_claim, + comment_command_policy.OrdinaryCommandId.RELEASE: _execute_release, + comment_command_policy.OrdinaryCommandId.RECTIFY: _execute_rectify, + comment_command_policy.OrdinaryCommandId.ASSIGN_SPECIFIC: _execute_assign_specific, + comment_command_policy.OrdinaryCommandId.ASSIGN_FROM_QUEUE: _execute_assign_from_queue, +} + + +def apply_comment_command( + bot: CommentApplicationRuntimeContext, + state: dict, + request: CommentEventRequest, + classified: dict, + *, + classify_issue_comment_actor, +) -> bool: + actor_class = classify_issue_comment_actor(request) + decision = comment_command_policy.decide_comment_command( + bot, + request, + classified, + actor_class=actor_class, + commands_help=config_module.get_commands_help(), + ) + if isinstance(decision, comment_command_policy.IgnoreDecision): + return False + + issue_number = request.issue_number + review_data = ensure_review_entry(state, issue_number, create=True) + if review_data is None: + return False + source_event_key = request.comment_source_event_key or f"issue_comment:{request.comment_id}" + if isinstance(decision, comment_command_policy.DeferPrivilegedHandoffDecision): + permission_status = bot.github.get_user_permission_status(request.comment_author, "triage") + handoff = privileged_command_policy.validate_accept_no_fls_changes_handoff( + request, + permission_status, + source_event_key=source_event_key, + ) + if isinstance(handoff, privileged_command_policy.BlockedPrivilegedHandoff): + bot.github.post_comment(issue_number, handoff.response) + return False + pending = privileged_command_policy.build_pending_privileged_command(created_at=request.comment_created_at, handoff=handoff) + privileged_command_policy.put_pending_accept_no_fls_changes(review_data, pending) + stored = True + if stored: + state_issue_number = bot.state_issue_number() + if state_issue_number <= 0: + state_issue_number = int(getattr(bot, "STATE_ISSUE_NUMBER", 0) or 0) + issue_reference = f"#{state_issue_number}" if state_issue_number > 0 else "the configured state issue" + bot.github.post_comment( + issue_number, + "✅ Recorded pending privileged command `accept-no-fls-changes` from trusted live validation. " + f"Use the isolated privileged workflow to execute it from issue `{issue_reference}` state.", + ) + return stored + + if isinstance(decision, comment_command_policy.InlineResponseDecision): + execution = CommandExecutionResult(response=decision.response, success=decision.success, state_changed=False) + react = decision.react + else: + assignment_request = ( + _build_assignment_request_from_comment_request(request) + if decision.needs_assignment_request + else None + ) + execution = ORDINARY_COMMAND_HANDLERS[decision.command_id](bot, state, decision, assignment_request) + react = True + + comment_id = request.comment_id + if comment_id > 0 and react: + bot.github.add_reaction(comment_id, "eyes") + if execution.success: + bot.github.add_reaction(comment_id, "+1") + if execution.response: + bot.github.post_comment(issue_number, execution.response) + return execution.state_changed + + +def process_comment_event( + bot: CommentApplicationRuntimeContext, + state: dict, + request: CommentEventRequest, + *, + classify_comment_payload, + classify_issue_comment_actor, +) -> bool: + comment_id = request.comment_id + classified = classify_comment_payload(bot, request.comment_body) + routing = _route_comment_application(classified, comment_id=comment_id) + state_changed = False + if routing in {"freshness_only", "both"}: + state_changed = record_conversation_freshness(bot, state, request) or state_changed + if routing in {"command_only", "both"}: + state_changed = apply_comment_command( + bot, + state, + request, + classified, + classify_issue_comment_actor=classify_issue_comment_actor, + ) or state_changed + return state_changed diff --git a/scripts/reviewer_bot_lib/comment_routing.py b/scripts/reviewer_bot_lib/comment_routing.py index 505593581..0029888c6 100644 --- a/scripts/reviewer_bot_lib/comment_routing.py +++ b/scripts/reviewer_bot_lib/comment_routing.py @@ -2,393 +2,206 @@ from __future__ import annotations -import hashlib -import os -import re -from datetime import datetime, timezone +from scripts.reviewer_bot_core import comment_routing_policy +from scripts.reviewer_bot_core.comment_routing_policy import PrCommentRouterOutcome +from .comment_application import ( + normalize_comment_body, + process_comment_event, +) +from .context import CommentEventRequest, PrCommentAdmission +from .event_inputs import ( + build_comment_event_request as decode_comment_event_request, +) +from .event_inputs import ( + build_pr_comment_admission as decode_pr_comment_admission, +) +from .runtime_protocols import CommentRoutingRuntimeContext -def _now() -> datetime: - return datetime.now(timezone.utc) - -def _now_iso() -> str: - return _now().isoformat() +def _log(bot: CommentRoutingRuntimeContext, level: str, message: str, **fields) -> None: + bot.logger.event(level, message, **fields) def _runtime_epoch(state: dict) -> str: return str(state.get("freshness_runtime_epoch", "")).strip() or "legacy_v14" -def _is_pr_event() -> bool: - return os.environ.get("IS_PULL_REQUEST", "false").lower() == "true" +def build_comment_event_request(bot: CommentRoutingRuntimeContext, *, issue_number: int | None = None) -> CommentEventRequest: + return decode_comment_event_request(bot, issue_number=issue_number) + + +def build_pr_comment_admission(bot: CommentRoutingRuntimeContext) -> PrCommentAdmission | None: + return decode_pr_comment_admission(bot) + + +def _resolve_comment_request(bot: CommentRoutingRuntimeContext, request: CommentEventRequest | None, *, issue_number: int | None = None) -> CommentEventRequest: + return request or build_comment_event_request(bot, issue_number=issue_number) + + +def _resolve_pr_admission(bot: CommentRoutingRuntimeContext, pr_admission: PrCommentAdmission | None) -> PrCommentAdmission | None: + return pr_admission or build_pr_comment_admission(bot) -def _issue_state() -> str: - return os.environ.get("ISSUE_STATE", "").strip().lower() +def _resolve_pr_admission_for_request( + bot: CommentRoutingRuntimeContext, + request: CommentEventRequest, + pr_admission: PrCommentAdmission | None, +) -> PrCommentAdmission | None: + return pr_admission or decode_pr_comment_admission(bot, request) -def _require_v18_for_pr(state: dict, context: str) -> bool: - if not _is_pr_event(): +def _require_v18_for_pr(bot: CommentRoutingRuntimeContext, state: dict, request: CommentEventRequest, context: str) -> bool: + if not request.is_pull_request: return True epoch = _runtime_epoch(state) if epoch != "freshness_v15": - print(f"V18 PR freshness path safe-noop for {context}; epoch is {epoch}") + _log( + bot, + "info", + f"V18 PR freshness path safe-noop for {context}; epoch is {epoch}", + context=context, + runtime_epoch=epoch, + ) return False return True def _normalize_comment_body(body: str) -> str: - return "\n".join(line.rstrip() for line in body.replace("\r\n", "\n").split("\n")).strip() + return normalize_comment_body(body) -def _semantic_digest(value: str) -> str: - return hashlib.sha256(_normalize_comment_body(value).encode("utf-8")).hexdigest() +def _comment_line_is_command(bot: CommentRoutingRuntimeContext, line: str) -> bool: + return comment_routing_policy.comment_line_is_command(bot.BOT_MENTION, line) -def _digest_body(body: str) -> str: - return hashlib.sha256(_normalize_comment_body(body).encode("utf-8")).hexdigest() +def classify_comment_payload(bot: CommentRoutingRuntimeContext, body: str) -> dict: + normalized = _normalize_comment_body(bot.adapters.commands.strip_code_blocks(body)) + parsed = bot.adapters.commands.parse_command(normalized) + return comment_routing_policy.classify_comment_payload(bot.BOT_MENTION, normalized, parsed) -def _comment_line_is_command(bot, line: str) -> bool: - stripped = line.strip() - if not stripped: - return False - pattern = rf"^{re.escape(bot.BOT_MENTION)}\s+/[A-Za-z0-9?_-]+(?:\s+.*)?$" - return re.match(pattern, stripped) is not None - - -def classify_comment_payload(bot, body: str) -> dict: - normalized = _normalize_comment_body(bot.strip_code_blocks(body)) - if not normalized: - return { - "comment_class": "empty_or_whitespace", - "has_non_command_text": False, - "command_count": 0, - "command": None, - "args": [], - "normalized_body": normalized, - } - lines = [line for line in normalized.splitlines() if line.strip()] - command_lines = [line for line in lines if _comment_line_is_command(bot, line)] - non_command_lines = [line for line in lines if not _comment_line_is_command(bot, line)] - parsed = bot.parse_command(normalized) - command = None - args: list[str] = [] - if parsed: - command, args = parsed - if command_lines and not non_command_lines: - comment_class = "command_only" - elif command_lines and non_command_lines: - comment_class = "command_plus_text" - else: - comment_class = "plain_text" - return { - "comment_class": comment_class, - "has_non_command_text": bool(non_command_lines), - "command_count": len(command_lines), - "command": command, - "args": args, - "normalized_body": normalized, - } - - -def classify_issue_comment_actor() -> str: - comment_user_type = os.environ.get("COMMENT_USER_TYPE", "").strip() - comment_author = os.environ.get("COMMENT_AUTHOR", "").strip() - sender_type = os.environ.get("COMMENT_SENDER_TYPE", "").strip() - installation_id = os.environ.get("COMMENT_INSTALLATION_ID", "").strip() - via_github_app = os.environ.get("COMMENT_PERFORMED_VIA_GITHUB_APP", "").strip().lower() - if comment_user_type == "Bot" or comment_author.endswith("[bot]"): - return "bot_account" - if installation_id or via_github_app == "true" or (sender_type and sender_type not in {"User", "Bot"}): - return "github_app_or_other_automation" - if comment_user_type == "User" and comment_author and not comment_author.endswith("[bot]") and not installation_id and via_github_app != "true": - return "repo_user_principal" - return "unknown_actor" - - -def _is_self_comment(bot, author: str) -> bool: - return author.strip().lower() == bot.BOT_NAME.lower() or author.strip().lower() == bot.BOT_MENTION.lstrip("@").lower() +def _classify_issue_comment_actor(request: CommentEventRequest) -> str: + return comment_routing_policy.classify_issue_comment_actor(request) -def _fetch_pr_metadata(bot, issue_number: int) -> dict: - pull_request = bot.github_api("GET", f"pulls/{issue_number}") - if not isinstance(pull_request, dict): - raise RuntimeError(f"Failed to fetch live PR metadata for #{issue_number}") - if not isinstance(pull_request.get("head"), dict) or not isinstance(pull_request.get("user"), dict): - raise RuntimeError(f"Unusable PR metadata for #{issue_number}") - return pull_request - - -def classify_pr_comment_processing_target(bot, issue_number: int) -> str: - actor_class = classify_issue_comment_actor() - if actor_class in {"bot_account", "github_app_or_other_automation"} or _is_self_comment(bot, os.environ.get("COMMENT_AUTHOR", "")): - return "safe_noop" - pull_request = _fetch_pr_metadata(bot, issue_number) - head_repo = pull_request.get("head", {}).get("repo", {}) - head_full_name = head_repo.get("full_name") if isinstance(head_repo, dict) else None - if not isinstance(head_full_name, str) or not head_full_name: - raise RuntimeError("Missing PR head repository metadata for trust routing") - is_cross_repo = head_full_name != os.environ.get("GITHUB_REPOSITORY", "") - pr_author = pull_request.get("user", {}).get("login") - is_dependabot_restricted = pr_author == "dependabot[bot]" - author_association = os.environ.get("COMMENT_AUTHOR_ASSOCIATION", "").strip() - trusted_principal = actor_class == "repo_user_principal" and author_association in bot.AUTHOR_ASSOCIATION_TRUST_ALLOWLIST - if is_cross_repo or is_dependabot_restricted: - return "pr_deferred_reconcile" - if trusted_principal: - return "pr_trusted_direct" - raise RuntimeError("Ambiguous same-repo PR comment trust posture; failing closed") - - -def route_issue_comment_trust(bot, issue_number: int) -> str: - if not _is_pr_event(): - return "issue_direct" - target = classify_pr_comment_processing_target(bot, issue_number) - if target != "pr_trusted_direct": - return target - workflow_file = os.environ.get("CURRENT_WORKFLOW_FILE", "").strip() - workflow_ref = os.environ.get("GITHUB_REF", "").strip() - if workflow_file == ".github/workflows/reviewer-bot-pr-comment-trusted.yml" and workflow_ref == "refs/heads/main": - return "pr_trusted_direct" - raise RuntimeError("Ambiguous same-repo PR comment trust posture; failing closed") - - -def build_pr_comment_observer_payload(bot, issue_number: int) -> dict: - actor_class = classify_issue_comment_actor() - comment_id = int(os.environ["COMMENT_ID"]) - base_payload = { - "source_workflow_name": "Reviewer Bot PR Comment Observer", - "source_workflow_file": ".github/workflows/reviewer-bot-pr-comment-observer.yml", - "source_run_id": int(os.environ["GITHUB_RUN_ID"]), - "source_run_attempt": int(os.environ["GITHUB_RUN_ATTEMPT"]), - "source_event_name": "issue_comment", - "source_event_action": "created", - "source_event_key": f"issue_comment:{comment_id}", - "pr_number": issue_number, - } - if actor_class in {"bot_account", "github_app_or_other_automation"} or _is_self_comment(bot, os.environ.get("COMMENT_AUTHOR", "")): - return { - "schema_version": 1, - "kind": "observer_noop", - "reason": "ignored_non_human_automation", - **base_payload, - } - processing_target = classify_pr_comment_processing_target(bot, issue_number) - if processing_target == "pr_trusted_direct": - return { - "schema_version": 1, - "kind": "observer_noop", - "reason": "trusted_direct_same_repo_human_comment", - **base_payload, - } - body = os.environ["COMMENT_BODY"] - normalized = _normalize_comment_body(body) - command_pattern = re.compile(r"^@guidelines\-bot\s+/[A-Za-z0-9?_\-]+(?:\s+.*)?$") - lines = [line for line in normalized.splitlines() if line.strip()] - command_lines = [line for line in lines if command_pattern.match(line.strip())] - non_command_lines = [line for line in lines if not command_pattern.match(line.strip())] - if not normalized: - comment_class = "empty_or_whitespace" - elif command_lines and not non_command_lines: - comment_class = "command_only" - elif command_lines and non_command_lines: - comment_class = "command_plus_text" - else: - comment_class = "plain_text" - return { - "schema_version": 2, - **base_payload, - "comment_id": comment_id, - "comment_class": comment_class, - "has_non_command_text": bool(non_command_lines), - "source_body_digest": _digest_body(body), - "source_created_at": os.environ["COMMENT_CREATED_AT"], - "actor_login": os.environ["COMMENT_AUTHOR"], - "actor_id": int(os.environ["COMMENT_AUTHOR_ID"]), - "actor_class": "repo_user_principal" if actor_class == "repo_user_principal" else "unknown_actor", - "source_artifact_name": f"reviewer-bot-comment-context-{os.environ['GITHUB_RUN_ID']}-attempt-{os.environ['GITHUB_RUN_ATTEMPT']}", - } - - -def _record_conversation_freshness(bot, state: dict, issue_number: int, comment_author: str, comment_id: int, created_at: str) -> bool: - review_data = bot.ensure_review_entry(state, issue_number, create=True) - if review_data is None: - return False - issue_author = os.environ.get("ISSUE_AUTHOR", "") - semantic_key = os.environ.get("COMMENT_SOURCE_EVENT_KEY", "").strip() or f"issue_comment:{comment_id}" - if issue_author and issue_author.lower() == comment_author.lower(): - return bot.reviews_module.accept_channel_event( - review_data, - "contributor_comment", - semantic_key=semantic_key, - timestamp=created_at, - actor=comment_author, - ) - current_reviewer = review_data.get("current_reviewer") - if isinstance(current_reviewer, str) and current_reviewer.lower() == comment_author.lower(): - changed = bot.reviews_module.accept_channel_event( - review_data, - "reviewer_comment", - semantic_key=semantic_key, - timestamp=created_at, - actor=comment_author, - ) - bot.reviews_module.record_reviewer_activity(review_data, created_at) - return changed - return False - - -def _store_pending_privileged_command(review_data: dict, issue_number: int, source_event_key: str, command_name: str, actor: str, args: list[str]) -> bool: - pending = review_data.setdefault("pending_privileged_commands", {}) - pending[source_event_key] = { - "source_event_key": source_event_key, - "command_name": command_name, - "issue_number": issue_number, - "actor": actor, - "args": args, - "status": "pending", - "created_at": _now_iso(), - } - return True +def classify_issue_comment_actor(request: CommentEventRequest | None = None) -> str: + if request is None: + raise RuntimeError("classify_issue_comment_actor requires an explicit request or runtime-aware caller") + return _classify_issue_comment_actor(request) -def _validate_accept_no_fls_changes_handoff(bot, issue_number: int, comment_author: str) -> tuple[bool, dict]: - if _is_pr_event(): - return False, {"reason": "pull_request_target_not_allowed"} - labels = bot.parse_issue_labels() - if bot.FLS_AUDIT_LABEL not in labels: - return False, {"reason": "missing_fls_audit_label"} - if not bot.check_user_permission(comment_author, "triage"): - return False, {"reason": "authorization_failed"} - return True, { - "command_name": "accept-no-fls-changes", - "issue_number": issue_number, - "actor": comment_author, - "authorization": {"required_permission": "triage", "authorized": True}, - "target": {"kind": "issue", "number": issue_number, "labels": sorted(labels)}, - } - - -def _handle_command(bot, state: dict, issue_number: int, comment_author: str, classified: dict) -> bool: - command = classified.get("command") - args = classified.get("args") or [] - if not isinstance(command, str): - return False - actor_class = classify_issue_comment_actor() - if actor_class in {"unknown_actor", "bot_account", "github_app_or_other_automation"}: - return False - review_data = bot.ensure_review_entry(state, issue_number, create=True) - if review_data is None: - return False - source_event_key = os.environ.get("COMMENT_SOURCE_EVENT_KEY", "").strip() or f"issue_comment:{os.environ.get('COMMENT_ID', '')}" - if command == "accept-no-fls-changes": - is_valid, metadata = _validate_accept_no_fls_changes_handoff(bot, issue_number, comment_author) - if not is_valid: - bot.post_comment( - issue_number, - "❌ This command is not eligible for privileged handoff from the current trusted live state.", - ) - return False - stored = _store_pending_privileged_command(review_data, issue_number, source_event_key, command, comment_author, list(args)) - if stored: - review_data["pending_privileged_commands"][source_event_key].update(metadata) - bot.post_comment( - issue_number, - "✅ Recorded pending privileged command `accept-no-fls-changes` from trusted live validation. Use the isolated privileged workflow to execute it from issue `#314` state.", - ) - return stored - if command == "_multiple_commands": - bot.post_comment(issue_number, f"⚠️ Multiple bot commands in one comment are ignored. Please post a single command per comment. For a list of commands, use `{bot.BOT_MENTION} /commands`.") - return False - response = "" - success = False - state_changed = False - if command == "pass": - response, success = bot.handle_pass_command(state, issue_number, comment_author, " ".join(args) if args else None) - state_changed = success - elif command == "away": - if args: - response, success = bot.handle_pass_until_command(state, issue_number, comment_author, args[0], " ".join(args[1:]) if len(args) > 1 else None) - state_changed = success - else: - response = f"❌ Missing date. Usage: `{bot.BOT_MENTION} /away YYYY-MM-DD [reason]`" - elif command == "label": - response, success, state_changed = bot.handle_label_command(state, issue_number, " ".join(args)) - elif command == "sync-members": - response, success = bot.handle_sync_members_command(state) - state_changed = success - elif command == "queue": - response, success = bot.handle_queue_command(state) - elif command == "commands": - response, success = bot.handle_commands_command() - elif command == "claim": - response, success = bot.handle_claim_command(state, issue_number, comment_author) - state_changed = success - elif command == "release": - response, success = bot.handle_release_command(state, issue_number, comment_author, list(args)) - state_changed = success - elif command == "rectify": - response, success, state_changed = bot.handle_rectify_command(state, issue_number, comment_author) - elif command == "r?-user": - response, success = bot.handle_assign_command(state, issue_number, args[0] if args else "") - state_changed = success - elif command == "assign-from-queue": - response, success = bot.handle_assign_from_queue_command(state, issue_number) - state_changed = success - elif command == "r?": - response = f"❌ Missing target. Usage:\n- `{bot.BOT_MENTION} /r? @username` - Assign a specific reviewer\n- `{bot.BOT_MENTION} /r? producers` - Assign next reviewer from queue" - elif command == "_malformed_known": - attempted = args[0] if args else "command" - response = f"⚠️ Did you mean `{bot.BOT_MENTION} /{attempted}`?\n\nCommands require a `/` prefix." - elif command == "_malformed_unknown": - attempted = args[0] if args else "" - response = f"⚠️ Unknown command `{attempted}`. Commands require a `/` prefix.\n\nTry `{bot.BOT_MENTION} /commands` to see available commands." - else: - response = f"❌ Unknown command: `/{command}`\n\nAvailable commands:\n{bot.get_commands_help()}" - comment_id = int(os.environ.get("COMMENT_ID", "0") or 0) - if comment_id > 0 and command != "_multiple_commands": - bot.add_reaction(comment_id, "eyes") - if success: - bot.add_reaction(comment_id, "+1") - if response: - bot.post_comment(issue_number, response) - return state_changed - - -def _process_comment_event(bot, state: dict, issue_number: int) -> bool: - comment_body = os.environ.get("COMMENT_BODY", "") - comment_author = os.environ.get("COMMENT_AUTHOR", "") - comment_id = int(os.environ.get("COMMENT_ID", "0") or 0) - comment_created_at = os.environ.get("COMMENT_CREATED_AT", "") or _now_iso() - classified = classify_comment_payload(bot, comment_body) - comment_class = classified["comment_class"] - state_changed = False - if comment_class in {"plain_text", "command_plus_text"} and comment_id > 0: - state_changed = _record_conversation_freshness(bot, state, issue_number, comment_author, comment_id, comment_created_at) or state_changed - if comment_class in {"command_only", "command_plus_text"} and int(classified.get("command_count", 0)) == 1: - state_changed = _handle_command(bot, state, issue_number, comment_author, classified) or state_changed - return state_changed - - -def handle_comment_event(bot, state: dict) -> bool: +def _is_self_comment(bot: CommentRoutingRuntimeContext, author: str) -> bool: + return author.strip().lower() == bot.BOT_NAME.lower() or author.strip().lower() == bot.BOT_MENTION.lstrip("@").lower() + + +def _classify_pr_comment_processing_target( + bot: CommentRoutingRuntimeContext, + request: CommentEventRequest, + pr_admission: PrCommentAdmission, +): + actor_class = _classify_issue_comment_actor(request) + return comment_routing_policy.classify_pr_comment_processing_target( + request, + pr_admission, + actor_class=actor_class, + is_self_comment=_is_self_comment(bot, request.comment_author), + ) + + +def classify_pr_comment_processing_target( + bot: CommentRoutingRuntimeContext, + issue_number: int, + request: CommentEventRequest | None = None, + pr_admission: PrCommentAdmission | None = None, +): + comment_request = _resolve_comment_request(bot, request, issue_number=issue_number) + resolved_admission = _resolve_pr_admission_for_request(bot, comment_request, pr_admission) + if resolved_admission is None: + raise RuntimeError("Trusted direct PR comment handling requires pr_admission") + return _classify_pr_comment_processing_target( + bot, + comment_request, + resolved_admission, + ) + + +def _route_issue_comment_trust( + bot: CommentRoutingRuntimeContext, + request: CommentEventRequest, + pr_admission: PrCommentAdmission | None, +): + processing_target = None + if request.is_pull_request: + if pr_admission is None: + raise RuntimeError("Trusted direct PR comment handling requires pr_admission") + processing_target = _classify_pr_comment_processing_target(bot, request, pr_admission) + return comment_routing_policy.route_issue_comment_trust( + request, + pr_admission, + processing_target=processing_target, + ) + + +def route_issue_comment_trust( + bot: CommentRoutingRuntimeContext, + issue_number: int, + request: CommentEventRequest | None = None, + pr_admission: PrCommentAdmission | None = None, +): + comment_request = _resolve_comment_request(bot, request, issue_number=issue_number) + return _route_issue_comment_trust( + bot, + comment_request, + _resolve_pr_admission_for_request(bot, comment_request, pr_admission), + ) + + +def _process_comment_event(bot: CommentRoutingRuntimeContext, state: dict, request: CommentEventRequest) -> bool: + return process_comment_event( + bot, + state, + request, + classify_comment_payload=classify_comment_payload, + classify_issue_comment_actor=_classify_issue_comment_actor, + ) + + +def handle_comment_event( + bot: CommentRoutingRuntimeContext, + state: dict, + request: CommentEventRequest | None = None, + pr_admission: PrCommentAdmission | None = None, +) -> bool: bot.assert_lock_held("handle_comment_event") - issue_number = int(os.environ.get("ISSUE_NUMBER", 0)) + comment_request = _resolve_comment_request(bot, request) + issue_number = comment_request.issue_number if not issue_number: return False bot.collect_touched_item(issue_number) - route = route_issue_comment_trust(bot, issue_number) - if route == "safe_noop": + route = _route_issue_comment_trust( + bot, + comment_request, + _resolve_pr_admission_for_request(bot, comment_request, pr_admission), + ) + if route == PrCommentRouterOutcome.SAFE_NOOP: return False - if route == "issue_direct": - if _issue_state() == "closed": - state.get("active_reviews", {}).pop(str(issue_number), None) - print(f"Ignoring direct comment on closed issue #{issue_number}") + if route == comment_routing_policy.ProcessingTarget.ISSUE_DIRECT: + if comment_request.issue_state == "closed": + removed = state.get("active_reviews", {}).pop(str(issue_number), None) + _log( + bot, + "info", + f"Ignoring direct comment on closed issue #{issue_number}", + issue_number=issue_number, + issue_state=comment_request.issue_state, + ) + return removed is not None + return _process_comment_event(bot, state, comment_request) + if route == PrCommentRouterOutcome.TRUSTED_DIRECT: + if comment_request.issue_state != "open": return False - return _process_comment_event(bot, state, issue_number) - if route == "pr_trusted_direct": - if not _require_v18_for_pr(state, "pr_trusted_direct_comment"): + if not _require_v18_for_pr(bot, state, comment_request, "pr_trusted_direct_comment"): return False - return _process_comment_event(bot, state, issue_number) + return _process_comment_event(bot, state, comment_request) raise RuntimeError("Deferred PR comment events must not mutate directly in trusted workflows") diff --git a/scripts/reviewer_bot_lib/config.py b/scripts/reviewer_bot_lib/config.py index 428455d84..f5663cff9 100644 --- a/scripts/reviewer_bot_lib/config.py +++ b/scripts/reviewer_bot_lib/config.py @@ -1,6 +1,5 @@ """Reviewer-bot configuration constants and small shared types.""" -import os from dataclasses import dataclass from typing import Any @@ -9,7 +8,8 @@ CODING_GUIDELINE_LABEL = "coding guideline" FLS_AUDIT_LABEL = "fls-audit" REVIEW_LABELS = {CODING_GUIDELINE_LABEL, FLS_AUDIT_LABEL} -STATE_ISSUE_NUMBER = int(os.environ.get("STATE_ISSUE_NUMBER", "0")) +STATE_ISSUE_NUMBER_ENV = "STATE_ISSUE_NUMBER" +STATE_ISSUE_NUMBER = 0 MEMBERS_URL = ( "https://raw.githubusercontent.com/rustfoundation/" "safety-critical-rust-consortium/main/subcommittee/coding-guidelines/members.md" @@ -20,28 +20,34 @@ STATE_BLOCK_END_MARKER = "" LOCK_BLOCK_START_MARKER = "" LOCK_BLOCK_END_MARKER = "" +TRANSITION_NOTICE_MARKER_PREFIX = "reviewer-bot:transition-notice:v1" LOCK_SCHEMA_VERSION = 1 -LOCK_LEASE_TTL_SECONDS = int(os.environ.get("REVIEWER_BOT_LOCK_TTL_SECONDS", "300")) -LOCK_RETRY_BASE_SECONDS = float(os.environ.get("REVIEWER_BOT_LOCK_RETRY_SECONDS", "2")) -LOCK_MAX_WAIT_SECONDS = int(os.environ.get("REVIEWER_BOT_LOCK_MAX_WAIT_SECONDS", "1200")) -LOCK_API_RETRY_LIMIT = int(os.environ.get("REVIEWER_BOT_LOCK_API_RETRY_LIMIT", "5")) -STATE_READ_RETRY_LIMIT = int(os.environ.get("REVIEWER_BOT_STATE_READ_RETRY_LIMIT", "4")) -STATE_READ_RETRY_BASE_SECONDS = float( - os.environ.get("REVIEWER_BOT_STATE_READ_RETRY_SECONDS", "1") -) -LOCK_RENEWAL_WINDOW_SECONDS = int( - os.environ.get("REVIEWER_BOT_LOCK_RENEWAL_WINDOW_SECONDS", "60") -) -LOCK_REF_NAME = os.environ.get("REVIEWER_BOT_LOCK_REF_NAME", "heads/reviewer-bot-state-lock") -LOCK_REF_BOOTSTRAP_BRANCH = os.environ.get("REVIEWER_BOT_LOCK_BOOTSTRAP_BRANCH", "main") +LOCK_LEASE_TTL_SECONDS_ENV = "REVIEWER_BOT_LOCK_TTL_SECONDS" +LOCK_RETRY_BASE_SECONDS_ENV = "REVIEWER_BOT_LOCK_RETRY_SECONDS" +LOCK_MAX_WAIT_SECONDS_ENV = "REVIEWER_BOT_LOCK_MAX_WAIT_SECONDS" +LOCK_API_RETRY_LIMIT_ENV = "REVIEWER_BOT_LOCK_API_RETRY_LIMIT" +STATE_READ_RETRY_LIMIT_ENV = "REVIEWER_BOT_STATE_READ_RETRY_LIMIT" +STATE_READ_RETRY_BASE_SECONDS_ENV = "REVIEWER_BOT_STATE_READ_RETRY_SECONDS" +LOCK_RENEWAL_WINDOW_SECONDS_ENV = "REVIEWER_BOT_LOCK_RENEWAL_WINDOW_SECONDS" +LOCK_REF_NAME_ENV = "REVIEWER_BOT_LOCK_REF_NAME" +LOCK_REF_BOOTSTRAP_BRANCH_ENV = "REVIEWER_BOT_LOCK_BOOTSTRAP_BRANCH" +LOCK_LEASE_TTL_SECONDS = 300 +LOCK_RETRY_BASE_SECONDS = 2.0 +LOCK_MAX_WAIT_SECONDS = 1200 +LOCK_API_RETRY_LIMIT = 5 +STATE_READ_RETRY_LIMIT = 4 +STATE_READ_RETRY_BASE_SECONDS = 1.0 +LOCK_RENEWAL_WINDOW_SECONDS = 60 +LOCK_REF_NAME = "heads/reviewer-bot-state-lock" +LOCK_REF_BOOTSTRAP_BRANCH = "main" LOCK_COMMIT_MARKER = "reviewer-bot-lock-v1" EVENT_INTENT_MUTATING = "mutating" EVENT_INTENT_NON_MUTATING_DEFER = "non_mutating_defer" EVENT_INTENT_NON_MUTATING_READONLY = "non_mutating_readonly" -STATE_SCHEMA_VERSION = 18 +STATE_SCHEMA_VERSION = 19 FRESHNESS_RUNTIME_EPOCH_LEGACY = "legacy_v14" FRESHNESS_RUNTIME_EPOCH_V18 = "freshness_v15" STATUS_PROJECTION_EPOCH = "status_projection_v2" @@ -197,11 +203,14 @@ def get_commands_help() -> str: @dataclass class GitHubApiResult: - status_code: int + status_code: int | None payload: Any headers: dict[str, str] text: str ok: bool + failure_kind: str | None = None + retry_attempts: int = 0 + transport_error: str | None = None @dataclass @@ -211,6 +220,13 @@ class AssignmentAttempt: exhausted_retryable_failure: bool = False +@dataclass(frozen=True) +class MemberFetchResult: + ok: bool + producers: list[dict[str, str]] + failure_kind: str | None = None + + @dataclass class StateIssueSnapshot: body: str diff --git a/scripts/reviewer_bot_lib/context.py b/scripts/reviewer_bot_lib/context.py index 2c3d69c76..458f36d88 100644 --- a/scripts/reviewer_bot_lib/context.py +++ b/scripts/reviewer_bot_lib/context.py @@ -1,181 +1,127 @@ -"""Typed runtime context for extracted reviewer-bot modules.""" +"""Request-boundary dataclasses for reviewer-bot modules.""" from __future__ import annotations -from collections.abc import Iterable -from datetime import datetime -from typing import Any, Protocol, runtime_checkable - -from .config import AssignmentAttempt, GitHubApiResult, LeaseContext, StateIssueSnapshot - - -@runtime_checkable -class GitHubTransportContext(Protocol): - """GitHub API transport and mutation surface used by low-level helpers.""" - - LOCK_API_RETRY_LIMIT: int - LOCK_RETRY_BASE_SECONDS: float - REVIEWER_REQUEST_422_TEMPLATE: str - AssignmentAttempt: type[AssignmentAttempt] - GitHubApiResult: type[GitHubApiResult] - - def get_github_token(self) -> str: ... - def get_github_graphql_token(self, *, prefer_board_token: bool = False) -> str: ... - def github_api_request( - self, - method: str, - endpoint: str, - data: dict | None = None, - extra_headers: dict[str, str] | None = None, - *, - suppress_error_log: bool = False, - ) -> GitHubApiResult: ... - def github_api(self, method: str, endpoint: str, data: dict | None = None) -> Any | None: ... - def github_graphql_request( - self, - query: str, - variables: dict | None = None, - *, - token: str | None = None, - suppress_error_log: bool = False, - ) -> GitHubApiResult: ... - def github_graphql( - self, - query: str, - variables: dict | None = None, - *, - token: str | None = None, - ) -> Any | None: ... - def request_reviewer_assignment(self, issue_number: int, username: str) -> AssignmentAttempt: ... - def remove_assignee(self, issue_number: int, username: str) -> bool: ... - def remove_pr_reviewer(self, issue_number: int, username: str) -> bool: ... - - -@runtime_checkable -class StateStoreContext(Protocol): - """State issue and serialization surface used by state-store helpers.""" - - ACTIVE_LEASE_CONTEXT: LeaseContext | None - STATE_ISSUE_NUMBER: int - STATE_READ_RETRY_LIMIT: int - STATE_READ_RETRY_BASE_SECONDS: float - LOCK_API_RETRY_LIMIT: int - LOCK_RETRY_BASE_SECONDS: float - GitHubApiResult: type[GitHubApiResult] - sys: Any - - def github_api_request( - self, - method: str, - endpoint: str, - data: dict | None = None, - extra_headers: dict[str, str] | None = None, - *, - suppress_error_log: bool = False, - ) -> GitHubApiResult: ... - def get_state_issue(self) -> dict | None: ... - def get_state_issue_snapshot(self) -> StateIssueSnapshot | None: ... - def conditional_patch_state_issue(self, body: str, etag: str | None = None) -> GitHubApiResult: ... - def parse_lock_metadata_from_issue_body(self, body: str) -> dict: ... - def render_state_issue_body( - self, - state: dict, - lock_meta: dict, - base_body: str | None = None, - *, - preserve_state_block: bool = False, - ) -> str: ... - def assert_lock_held(self, operation: str) -> None: ... - def parse_iso8601_timestamp(self, value: Any) -> datetime | None: ... - def normalize_lock_metadata(self, lock_meta: dict | None) -> dict: ... - def ensure_state_issue_lease_lock_fresh(self) -> bool: ... - - -@runtime_checkable -class LeaseLockContext(Protocol): - """Lock-specific runtime surface used by lease-lock helpers.""" - - ACTIVE_LEASE_CONTEXT: LeaseContext | None - LOCK_API_RETRY_LIMIT: int - LOCK_RETRY_BASE_SECONDS: float - LOCK_LEASE_TTL_SECONDS: int - LOCK_MAX_WAIT_SECONDS: int - LOCK_RENEWAL_WINDOW_SECONDS: int - LOCK_REF_NAME: str - LOCK_REF_BOOTSTRAP_BRANCH: str - LOCK_COMMIT_MARKER: str - LOCK_SCHEMA_VERSION: int - LeaseContext: type[LeaseContext] - sys: Any - - def parse_iso8601_timestamp(self, value: Any) -> datetime | None: ... - def normalize_lock_metadata(self, lock_meta: dict | None) -> dict: ... - def clear_lock_metadata(self) -> dict: ... - def get_state_issue_snapshot(self) -> StateIssueSnapshot | None: ... - def github_api_request( - self, - method: str, - endpoint: str, - data: dict | None = None, - extra_headers: dict[str, str] | None = None, - *, - suppress_error_log: bool = False, - ) -> GitHubApiResult: ... - def get_lock_ref_display(self) -> str: ... - def get_state_issue_html_url(self) -> str: ... - def get_lock_ref_snapshot(self) -> tuple[str, str, dict]: ... - def build_lock_metadata( - self, - lock_token: str, - lock_owner_run_id: str, - lock_owner_workflow: str, - lock_owner_job: str, - ) -> dict: ... - def create_lock_commit(self, parent_sha: str, tree_sha: str, lock_meta: dict) -> GitHubApiResult: ... - def cas_update_lock_ref(self, new_sha: str) -> GitHubApiResult: ... - def lock_is_currently_valid(self, lock_meta: dict, now: datetime | None = None) -> bool: ... - def renew_state_issue_lease_lock(self, context: LeaseContext) -> bool: ... - - -@runtime_checkable -class ReviewerBotContext(GitHubTransportContext, StateStoreContext, LeaseLockContext, Protocol): - """Broader runtime surface expected by orchestration-heavy extracted modules. - - This protocol intentionally captures true runtime services and shared state, - not pure helper modules or formatting helpers that should be imported - directly where used. - """ - - EVENT_INTENT_MUTATING: str - EVENT_INTENT_NON_MUTATING_DEFER: str - EVENT_INTENT_NON_MUTATING_READONLY: str - datetime: type[datetime] - timezone: Any - def load_state(self, *, fail_on_unavailable: bool = False) -> dict: ... - def save_state(self, state: dict) -> bool: ... - def ensure_state_issue_lease_lock_fresh(self) -> bool: ... - def acquire_state_issue_lease_lock(self) -> LeaseContext: ... - def release_state_issue_lease_lock(self) -> bool: ... - def drain_touched_items(self) -> list[int]: ... - def process_pass_until_expirations(self, state: dict) -> tuple[dict, list[str]]: ... - def sync_members_with_queue(self, state: dict) -> tuple[dict, list[str]]: ... - def handle_issue_or_pr_opened(self, state: dict) -> bool: ... - def handle_labeled_event(self, state: dict) -> bool: ... - def handle_issue_edited_event(self, state: dict) -> bool: ... - def handle_closed_event(self, state: dict) -> bool: ... - def handle_pull_request_target_synchronize(self, state: dict) -> bool: ... - def handle_pull_request_review_event(self, state: dict) -> bool: ... - def handle_comment_event(self, state: dict) -> bool: ... - def handle_manual_dispatch(self, state: dict) -> bool: ... - def handle_scheduled_check(self, state: dict) -> bool: ... - def handle_workflow_run_event(self, state: dict) -> bool: ... - def sync_status_labels_for_items(self, state: dict, issue_numbers: Iterable[int]) -> bool: ... - def compute_reviewer_response_state( - self, - issue_number: int, - review_data: dict, - *, - issue_snapshot: dict | None = None, - pull_request: dict | None = None, - reviews: list[dict] | None = None, - ) -> dict[str, object]: ... +from dataclasses import dataclass +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from scripts.reviewer_bot_core.comment_routing_policy import PrCommentRouterOutcome + + +@dataclass(frozen=True) +class EventContext: + event_name: str + event_action: str + issue_number: int | None = None + is_pull_request: bool | None = None + issue_author: str | None = None + issue_state: str | None = None + issue_labels: tuple[str, ...] = () + comment_id: int | None = None + comment_author: str | None = None + comment_body: str | None = None + comment_source_event_key: str | None = None + pr_is_cross_repository: bool | None = None + review_author: str | None = None + review_state: str | None = None + workflow_kind: str | None = None + workflow_run_triggering_conclusion: str | None = None + workflow_artifact_contract: str | None = None + manual_action: str | None = None + + +@dataclass(frozen=True) +class ExecutionResult: + exit_code: int + state_changed: bool + release_failed: bool = False + + +@dataclass(frozen=True) +class CommentEventRequest: + issue_number: int + is_pull_request: bool + issue_state: str + issue_author: str + issue_labels: tuple[str, ...] + comment_id: int + comment_author: str + comment_author_id: int + comment_body: str + comment_created_at: str + comment_source_event_key: str + comment_user_type: str + comment_sender_type: str + comment_installation_id: str | None + comment_performed_via_github_app: bool + + +@dataclass(frozen=True) +class PrCommentAdmission: + route_outcome: PrCommentRouterOutcome + declared_trust_class: str + github_repository: str + pr_head_full_name: str + pr_author: str + issue_state: str + issue_labels: tuple[str, ...] + comment_author_id: int + github_run_id: int + github_run_attempt: int + + +@dataclass(frozen=True) +class AssignmentRequest: + issue_number: int + issue_author: str = "" + is_pull_request: bool = False + issue_labels: tuple[str, ...] = () + repo_owner: str = "" + repo_name: str = "" + + +@dataclass(frozen=True) +class PrivilegedCommandRequest: + issue_number: int + actor: str = "" + command_name: str = "" + is_pull_request: bool = False + issue_labels: tuple[str, ...] = () + + +@dataclass(frozen=True) +class ManualDispatchRequest: + action: str = "" + issue_number: int | None = None + privileged_source_event_key: str = "" + + +@dataclass(frozen=True) +class IssueLifecycleRequest: + issue_number: int = 0 + is_pull_request: bool = False + issue_labels: tuple[str, ...] = () + issue_author: str = "" + sender_login: str = "" + updated_at: str = "" + issue_title: str = "" + issue_body: str = "" + previous_title: str = "" + previous_body: str = "" + pr_head_sha: str = "" + event_created_at: str = "" + + +@dataclass(frozen=True) +class LabelEventRequest: + issue_number: int = 0 + is_pull_request: bool = False + label_name: str = "" + + +@dataclass(frozen=True) +class PullRequestSyncRequest: + issue_number: int = 0 + head_sha: str = "" + event_created_at: str = "" diff --git a/scripts/reviewer_bot_lib/deferred_gap_bookkeeping.py b/scripts/reviewer_bot_lib/deferred_gap_bookkeeping.py new file mode 100644 index 000000000..3d28f6d51 --- /dev/null +++ b/scripts/reviewer_bot_lib/deferred_gap_bookkeeping.py @@ -0,0 +1,125 @@ +"""Deferred-gap bookkeeping support shared by reconcile and sweeper.""" + +from __future__ import annotations + +from copy import deepcopy + + +def _sidecars(review_data: dict) -> dict: + sidecars = review_data.get("sidecars") + if not isinstance(sidecars, dict): + sidecars = {} + review_data["sidecars"] = sidecars + return sidecars + + +def _deferred_gaps(review_data: dict) -> dict: + sidecars = _sidecars(review_data) + deferred_gaps = sidecars.get("deferred_gaps") + if not isinstance(deferred_gaps, dict): + deferred_gaps = {} + sidecars["deferred_gaps"] = deferred_gaps + return deferred_gaps + + +def get_deferred_gaps(review_data: dict) -> dict: + return _deferred_gaps(review_data) + + +def _reconciled_source_events(review_data: dict) -> dict: + sidecars = _sidecars(review_data) + reconciled = sidecars.get("reconciled_source_events") + if not isinstance(reconciled, dict): + reconciled = {} + sidecars["reconciled_source_events"] = reconciled + return reconciled + + +def get_reconciled_source_events(review_data: dict) -> dict: + return _reconciled_source_events(review_data) + + +def _observer_discovery_watermarks(review_data: dict) -> dict: + sidecars = _sidecars(review_data) + watermarks = sidecars.get("observer_discovery_watermarks") + if not isinstance(watermarks, dict): + watermarks = {} + sidecars["observer_discovery_watermarks"] = watermarks + return watermarks + + +def get_observer_discovery_watermarks(review_data: dict) -> dict: + return _observer_discovery_watermarks(review_data) + + +def _now_iso(bot) -> str: + return bot.clock.now().isoformat() + + +def _ensure_source_event_key(review_data: dict, source_event_key: str, payload: dict | None = None) -> None: + deferred_gaps = _deferred_gaps(review_data) + if payload is None: + payload = {} + payload["source_event_key"] = source_event_key + deferred_gaps[source_event_key] = payload + + +def _clear_source_event_key(review_data: dict, source_event_key: str) -> bool: + deferred_gaps = _deferred_gaps(review_data) + if source_event_key in deferred_gaps: + deferred_gaps.pop(source_event_key, None) + return True + return False + + +def _mark_reconciled_source_event(review_data: dict, source_event_key: str) -> bool: + reconciled = _reconciled_source_events(review_data) + if source_event_key not in reconciled: + reconciled[source_event_key] = { + "source_event_key": source_event_key, + "reconciled_at": None, + } + return True + return False + + +def _was_reconciled_source_event(review_data: dict, source_event_key: str) -> bool: + return source_event_key in _reconciled_source_events(review_data) + + +def _update_deferred_gap( + bot, + review_data: dict, + payload: dict, + reason: str, + diagnostic_summary: str, + *, + failure_kind: str | None = None, +) -> bool: + source_event_key = str(payload.get("source_event_key", "")) + if not source_event_key: + return False + deferred_gaps = _deferred_gaps(review_data) + existing = deferred_gaps.get(source_event_key, {}) + if not isinstance(existing, dict): + existing = {} + previous = deepcopy(existing) + existing.update( + { + "source_event_key": source_event_key, + "source_event_kind": f"{payload.get('source_event_name')}:{payload.get('source_event_action')}", + "pr_number": payload.get("pr_number"), + "reason": reason, + "source_event_created_at": payload.get("source_created_at") or payload.get("source_submitted_at"), + "source_run_id": payload.get("source_run_id"), + "source_run_attempt": payload.get("source_run_attempt"), + "first_noted_at": existing.get("first_noted_at") or _now_iso(bot), + "last_checked_at": _now_iso(bot), + "operator_action_required": True, + "diagnostic_summary": diagnostic_summary, + "failure_kind": failure_kind, + } + ) + changed = previous != existing + deferred_gaps[source_event_key] = existing + return changed diff --git a/scripts/reviewer_bot_lib/event_inputs.py b/scripts/reviewer_bot_lib/event_inputs.py new file mode 100644 index 000000000..4a67be3a5 --- /dev/null +++ b/scripts/reviewer_bot_lib/event_inputs.py @@ -0,0 +1,402 @@ +"""Decode runtime/config inputs into typed reviewer-bot request objects.""" + +from __future__ import annotations + +import json +from datetime import datetime +from pathlib import Path + +from scripts.reviewer_bot_core.comment_routing_policy import PrCommentRouterOutcome + +from .context import ( + AssignmentRequest, + CommentEventRequest, + EventContext, + IssueLifecycleRequest, + LabelEventRequest, + ManualDispatchRequest, + PrCommentAdmission, + PrivilegedCommandRequest, + PullRequestSyncRequest, +) +from .runtime_protocols import EventInputsContext + + +class InvalidEventInput(RuntimeError): + def __init__(self, builder: str, problems: tuple[str, ...]): + self.builder = builder + self.problems = problems + super().__init__(f"{builder}: {'; '.join(problems)}") + + +def _parse_optional_int(value: str) -> int | None: + value = value.strip() + if not value: + return None + try: + return int(value) + except ValueError: + return None + + +def _parse_optional_bool(value: str) -> bool | None: + value = value.strip().lower() + if not value: + return None + if value == "true": + return True + if value == "false": + return False + return None + + +def _parse_required_bool(value: str) -> bool | None: + return _parse_optional_bool(value) + + +def _parse_labels(value: str) -> tuple[str, ...]: + value = value.strip() + if not value: + return () + try: + payload = json.loads(value) + except json.JSONDecodeError: + return () + if not isinstance(payload, list): + return () + return tuple(str(label) for label in payload) + + +def parse_issue_labels(bot: EventInputsContext) -> list[str]: + return list(_parse_labels(bot.get_config_value("ISSUE_LABELS", "[]"))) + + +def _is_parseable_iso8601(value: str) -> bool: + try: + datetime.fromisoformat(value.replace("Z", "+00:00")) + except ValueError: + return False + return True + + +def _raise_invalid(builder: str, problems: list[str]) -> None: + raise InvalidEventInput(builder, tuple(problems)) + + +def _parse_required_labels(builder: str, raw_value: str, problems: list[str]) -> tuple[str, ...]: + value = raw_value.strip() + if not value: + problems.append("ISSUE_LABELS must be present") + return () + try: + payload = json.loads(value) + except json.JSONDecodeError: + problems.append("ISSUE_LABELS must be valid JSON") + return () + if not isinstance(payload, list): + problems.append("ISSUE_LABELS must decode to a JSON list") + return () + return tuple(str(label) for label in payload) + + +def _derive_comment_source_event_key(bot: EventInputsContext, comment_id: int) -> str | None: + event_name = bot.get_config_value("EVENT_NAME").strip() + if event_name == "issue_comment": + return f"issue_comment:{comment_id}" + if event_name == "pull_request_review_comment": + return f"pull_request_review_comment:{comment_id}" + return None + + +def _parse_route_outcome(value: str) -> PrCommentRouterOutcome | None: + normalized = value.strip().lower() + if not normalized: + return None + try: + return PrCommentRouterOutcome(normalized) + except ValueError: + return None + + +def _read_workflow_run_name(bot: EventInputsContext) -> str: + event_path = bot.get_config_value("GITHUB_EVENT_PATH").strip() + if not event_path: + return "" + try: + payload = json.loads(Path(event_path).read_text(encoding="utf-8")) + except (OSError, json.JSONDecodeError): + return "" + workflow_run = payload.get("workflow_run") + if not isinstance(workflow_run, dict): + return "" + name = workflow_run.get("name") + return name.strip() if isinstance(name, str) else "" + + +def build_event_context(bot: EventInputsContext) -> EventContext: + workflow_kind = bot.get_config_value("REVIEWER_BOT_WORKFLOW_KIND").strip() or None + workflow_artifact_contract = None + if bot.get_config_value("EVENT_NAME").strip() == "workflow_run": + workflow_artifact_contract = ( + "artifact_optional_router" + if _read_workflow_run_name(bot) == "Reviewer Bot PR Comment Router" + else "artifact_required" + ) + return EventContext( + event_name=bot.get_config_value("EVENT_NAME").strip(), + event_action=bot.get_config_value("EVENT_ACTION").strip(), + issue_number=_parse_optional_int(bot.get_config_value("ISSUE_NUMBER")), + is_pull_request=_parse_optional_bool(bot.get_config_value("IS_PULL_REQUEST")), + issue_author=bot.get_config_value("ISSUE_AUTHOR").strip() or None, + issue_state=bot.get_config_value("ISSUE_STATE").strip() or None, + issue_labels=_parse_labels(bot.get_config_value("ISSUE_LABELS")), + comment_id=_parse_optional_int(bot.get_config_value("COMMENT_ID")), + comment_author=bot.get_config_value("COMMENT_AUTHOR").strip() or None, + comment_body=bot.get_config_value("COMMENT_BODY") or None, + comment_source_event_key=bot.get_config_value("COMMENT_SOURCE_EVENT_KEY").strip() or None, + pr_is_cross_repository=_parse_optional_bool(bot.get_config_value("PR_IS_CROSS_REPOSITORY")), + review_author=bot.get_config_value("REVIEW_AUTHOR").strip() or None, + review_state=bot.get_config_value("REVIEW_STATE").strip() or None, + workflow_kind=workflow_kind, + workflow_run_triggering_conclusion=bot.get_config_value("WORKFLOW_RUN_TRIGGERING_CONCLUSION").strip() or None, + workflow_artifact_contract=workflow_artifact_contract, + manual_action=bot.get_config_value("MANUAL_ACTION").strip() or None, + ) + + +def build_comment_event_request(bot: EventInputsContext, *, issue_number: int | None = None) -> CommentEventRequest: + builder = "build_comment_event_request" + problems: list[str] = [] + resolved_issue_number = issue_number if issue_number is not None else _parse_optional_int(bot.get_config_value("ISSUE_NUMBER")) + if resolved_issue_number is None or resolved_issue_number <= 0: + problems.append("ISSUE_NUMBER must be a positive integer") + is_pull_request = _parse_required_bool(bot.get_config_value("IS_PULL_REQUEST")) + if is_pull_request is None: + problems.append("IS_PULL_REQUEST must be parseable as a boolean") + issue_state = bot.get_config_value("ISSUE_STATE").strip().lower() + if not issue_state: + problems.append("ISSUE_STATE must be non-empty") + issue_author = bot.get_config_value("ISSUE_AUTHOR").strip() + if not issue_author: + problems.append("ISSUE_AUTHOR must be non-empty") + issue_labels = _parse_required_labels(builder, bot.get_config_value("ISSUE_LABELS", "[]"), problems) + comment_id = _parse_optional_int(bot.get_config_value("COMMENT_ID")) + if comment_id is None or comment_id <= 0: + problems.append("COMMENT_ID must be a positive integer") + comment_id = 0 + comment_author = bot.get_config_value("COMMENT_AUTHOR").strip() + if not comment_author: + problems.append("COMMENT_AUTHOR must be non-empty") + comment_author_id = _parse_optional_int(bot.get_config_value("COMMENT_AUTHOR_ID")) + if comment_author_id is None or comment_author_id <= 0: + problems.append("COMMENT_AUTHOR_ID must be a positive integer") + comment_author_id = 0 + comment_body = bot.get_config_value("COMMENT_BODY") + comment_created_at = bot.get_config_value("COMMENT_CREATED_AT").strip() + if not comment_created_at: + problems.append("COMMENT_CREATED_AT must be non-empty") + elif not _is_parseable_iso8601(comment_created_at): + problems.append("COMMENT_CREATED_AT must be parseable ISO-8601") + comment_source_event_key = bot.get_config_value("COMMENT_SOURCE_EVENT_KEY").strip() + if not comment_source_event_key and comment_id > 0: + comment_source_event_key = _derive_comment_source_event_key(bot, comment_id) or "" + if not comment_source_event_key: + problems.append("COMMENT_SOURCE_EVENT_KEY must be non-empty") + comment_user_type = bot.get_config_value("COMMENT_USER_TYPE").strip() + if not comment_user_type: + problems.append("COMMENT_USER_TYPE must be non-empty") + comment_sender_type = bot.get_config_value("COMMENT_SENDER_TYPE").strip() + if not comment_sender_type: + problems.append("COMMENT_SENDER_TYPE must be non-empty") + comment_installation_id = bot.get_config_value("COMMENT_INSTALLATION_ID").strip() or None + comment_performed_via_github_app = _parse_required_bool(bot.get_config_value("COMMENT_PERFORMED_VIA_GITHUB_APP")) + if comment_performed_via_github_app is None: + problems.append("COMMENT_PERFORMED_VIA_GITHUB_APP must be parseable as a boolean") + if problems: + _raise_invalid(builder, problems) + return CommentEventRequest( + issue_number=resolved_issue_number, + is_pull_request=is_pull_request, + issue_state=issue_state, + issue_author=issue_author, + issue_labels=issue_labels, + comment_id=comment_id, + comment_author=comment_author, + comment_author_id=comment_author_id, + comment_body=comment_body, + comment_created_at=comment_created_at, + comment_source_event_key=comment_source_event_key, + comment_user_type=comment_user_type, + comment_sender_type=comment_sender_type, + comment_installation_id=comment_installation_id, + comment_performed_via_github_app=comment_performed_via_github_app, + ) + + +def build_pr_comment_admission( + bot: EventInputsContext, + request: CommentEventRequest | None = None, +) -> PrCommentAdmission | None: + builder = "build_pr_comment_admission" + route_outcome = _parse_route_outcome(bot.get_config_value("REVIEWER_BOT_ROUTE_OUTCOME")) + if route_outcome in {None, PrCommentRouterOutcome.DEFERRED_RECONCILE, PrCommentRouterOutcome.SAFE_NOOP}: + return None + problems: list[str] = [] + if route_outcome is not PrCommentRouterOutcome.TRUSTED_DIRECT: + problems.append("REVIEWER_BOT_ROUTE_OUTCOME must be a retained PR comment router outcome") + declared_trust_class = bot.get_config_value("REVIEWER_BOT_TRUST_CLASS").strip() + if declared_trust_class != "pr_trusted_direct": + problems.append("REVIEWER_BOT_TRUST_CLASS must equal pr_trusted_direct") + github_repository = bot.get_config_value("GITHUB_REPOSITORY").strip() + if not github_repository: + problems.append("GITHUB_REPOSITORY must be non-empty") + pr_head_full_name = bot.get_config_value("PR_HEAD_FULL_NAME").strip() + if not pr_head_full_name: + problems.append("PR_HEAD_FULL_NAME must be non-empty") + pr_author = bot.get_config_value("PR_AUTHOR").strip() + if not pr_author: + problems.append("PR_AUTHOR must be non-empty") + issue_state = bot.get_config_value("ISSUE_STATE").strip().lower() + if not issue_state: + problems.append("ISSUE_STATE must be non-empty") + issue_labels = _parse_required_labels(builder, bot.get_config_value("ISSUE_LABELS", "[]"), problems) + comment_author_id = _parse_optional_int(bot.get_config_value("COMMENT_AUTHOR_ID")) + if comment_author_id is None or comment_author_id <= 0: + problems.append("COMMENT_AUTHOR_ID must be a positive integer") + comment_author_id = 0 + github_run_id = _parse_optional_int(bot.get_config_value("GITHUB_RUN_ID")) + if github_run_id is None or github_run_id <= 0: + problems.append("GITHUB_RUN_ID must be a positive integer") + github_run_id = 0 + github_run_attempt = _parse_optional_int(bot.get_config_value("GITHUB_RUN_ATTEMPT")) + if github_run_attempt is None or github_run_attempt <= 0: + problems.append("GITHUB_RUN_ATTEMPT must be a positive integer") + github_run_attempt = 0 + if request is not None: + if request.issue_state != issue_state: + problems.append("ISSUE_STATE must match comment request admission boundary copy") + if request.issue_labels != issue_labels: + problems.append("ISSUE_LABELS must match comment request admission boundary copy") + if request.comment_author_id != comment_author_id: + problems.append("COMMENT_AUTHOR_ID must match comment request admission boundary copy") + if problems: + _raise_invalid(builder, problems) + return PrCommentAdmission( + route_outcome=route_outcome, + declared_trust_class=declared_trust_class, + github_repository=github_repository, + pr_head_full_name=pr_head_full_name, + pr_author=pr_author, + issue_state=issue_state, + issue_labels=issue_labels, + comment_author_id=comment_author_id, + github_run_id=github_run_id, + github_run_attempt=github_run_attempt, + ) + + +def build_assignment_request(bot: EventInputsContext, *, issue_number: int) -> AssignmentRequest: + return AssignmentRequest( + issue_number=issue_number, + issue_author=bot.get_config_value("ISSUE_AUTHOR"), + is_pull_request=bool(_parse_optional_bool(bot.get_config_value("IS_PULL_REQUEST"))), + issue_labels=_parse_labels(bot.get_config_value("ISSUE_LABELS", "[]")), + repo_owner=bot.get_config_value("REPO_OWNER"), + repo_name=bot.get_config_value("REPO_NAME"), + ) + + +def build_privileged_command_request( + bot: EventInputsContext, + *, + issue_number: int, + actor: str = "", + command_name: str = "", +) -> PrivilegedCommandRequest: + return PrivilegedCommandRequest( + issue_number=issue_number, + actor=actor, + command_name=command_name, + is_pull_request=bool(_parse_optional_bool(bot.get_config_value("IS_PULL_REQUEST"))), + issue_labels=_parse_labels(bot.get_config_value("ISSUE_LABELS", "[]")), + ) + + +def build_pending_privileged_command_request(record) -> PrivilegedCommandRequest: + return PrivilegedCommandRequest( + issue_number=record.issue_number, + actor=record.actor, + command_name=record.command_name, + is_pull_request=False, + issue_labels=record.target_labels_snapshot, + ) + + +def build_manual_dispatch_request(bot: EventInputsContext) -> ManualDispatchRequest: + return ManualDispatchRequest( + action=bot.get_config_value("MANUAL_ACTION").strip(), + issue_number=_parse_optional_int(bot.get_config_value("ISSUE_NUMBER")), + privileged_source_event_key=bot.get_config_value("PRIVILEGED_SOURCE_EVENT_KEY").strip(), + ) + + +def build_issue_lifecycle_request(bot: EventInputsContext) -> IssueLifecycleRequest: + return IssueLifecycleRequest( + issue_number=_parse_optional_int(bot.get_config_value("ISSUE_NUMBER")) or 0, + is_pull_request=bool(_parse_optional_bool(bot.get_config_value("IS_PULL_REQUEST"))), + issue_labels=_parse_labels(bot.get_config_value("ISSUE_LABELS")), + issue_author=bot.get_config_value("ISSUE_AUTHOR").strip(), + sender_login=bot.get_config_value("SENDER_LOGIN").strip(), + updated_at=bot.get_config_value("ISSUE_UPDATED_AT").strip(), + issue_title=bot.get_config_value("ISSUE_TITLE"), + issue_body=bot.get_config_value("ISSUE_BODY"), + previous_title=bot.get_config_value("ISSUE_CHANGES_TITLE_FROM"), + previous_body=bot.get_config_value("ISSUE_CHANGES_BODY_FROM"), + pr_head_sha=bot.get_config_value("PR_HEAD_SHA").strip(), + event_created_at=bot.get_config_value("EVENT_CREATED_AT").strip(), + ) + + +def build_label_event_request(bot: EventInputsContext) -> LabelEventRequest: + return LabelEventRequest( + issue_number=_parse_optional_int(bot.get_config_value("ISSUE_NUMBER")) or 0, + is_pull_request=bool(_parse_optional_bool(bot.get_config_value("IS_PULL_REQUEST"))), + label_name=bot.get_config_value("LABEL_NAME"), + ) + + +def build_pull_request_sync_request(bot: EventInputsContext) -> PullRequestSyncRequest: + return PullRequestSyncRequest( + issue_number=_parse_optional_int(bot.get_config_value("ISSUE_NUMBER")) or 0, + head_sha=bot.get_config_value("PR_HEAD_SHA").strip(), + event_created_at=bot.get_config_value("EVENT_CREATED_AT").strip(), + ) + + +def build_replay_comment_event_request(payload, *, live_comment=None, comment_body: str | None = None) -> CommentEventRequest: + if getattr(getattr(payload, "identity", None), "schema_version", None) != 3: + raise InvalidEventInput("build_replay_comment_event_request", ("schema-v3 comment-like payload required",)) + if not hasattr(payload, "issue_state") or not hasattr(payload, "issue_author") or not hasattr(payload, "issue_labels"): + raise InvalidEventInput("build_replay_comment_event_request", ("typed deferred comment payload required",)) + resolved_body = payload.comment_body if comment_body is None else comment_body + return CommentEventRequest( + issue_number=payload.identity.pr_number, + is_pull_request=True, + issue_state=payload.issue_state, + issue_author=payload.issue_author, + issue_labels=payload.issue_labels, + comment_id=payload.comment_id, + comment_author=(live_comment.comment_author if live_comment is not None else payload.comment_author), + comment_author_id=payload.comment_author_id, + comment_body=resolved_body, + comment_created_at=payload.comment_created_at, + comment_source_event_key=payload.identity.source_event_key, + comment_user_type=(live_comment.comment_user_type if live_comment is not None else payload.comment_user_type), + comment_sender_type=(live_comment.comment_sender_type if live_comment is not None else payload.comment_sender_type), + comment_installation_id=(live_comment.comment_installation_id if live_comment is not None else payload.comment_installation_id), + comment_performed_via_github_app=( + live_comment.comment_performed_via_github_app + if live_comment is not None + else payload.comment_performed_via_github_app + ), + ) diff --git a/scripts/reviewer_bot_lib/events.py b/scripts/reviewer_bot_lib/events.py deleted file mode 100644 index 80549b198..000000000 --- a/scripts/reviewer_bot_lib/events.py +++ /dev/null @@ -1,46 +0,0 @@ -"""Reviewer-bot event, deferred-evidence, and freshness handlers.""" - -from __future__ import annotations - -import os - - -def _runtime_epoch(state: dict) -> str: - return str(state.get("freshness_runtime_epoch", "")).strip() or "legacy_v14" - - -def _is_pr_event() -> bool: - return os.environ.get("IS_PULL_REQUEST", "false").lower() == "true" - - -def _require_v18_for_pr(state: dict, context: str) -> bool: - if not _is_pr_event(): - return True - epoch = _runtime_epoch(state) - if epoch != "freshness_v15": - print(f"V18 PR freshness path safe-noop for {context}; epoch is {epoch}") - return False - return True - - -def _require_legacy_for_legacy_pr(state: dict, context: str) -> bool: - if not _is_pr_event(): - return True - epoch = _runtime_epoch(state) - if epoch == "freshness_v15": - print(f"Legacy PR freshness path safe-noop for {context}; epoch is {epoch}") - return False - return True -def handle_pull_request_review_event(bot, state: dict) -> bool: - issue_number = int(os.environ.get("ISSUE_NUMBER", 0)) - if not issue_number: - return False - bot.collect_touched_item(issue_number) - if _runtime_epoch(state) == "freshness_v15": - print("Legacy direct pull_request_review mutation disabled after epoch flip") - return False - review_action = os.environ.get("EVENT_ACTION", "").strip().lower() - if review_action not in {"submitted", "dismissed"}: - return False - print(f"Deferring pull_request_review {review_action} for #{issue_number}") - return False diff --git a/scripts/reviewer_bot_lib/github_api.py b/scripts/reviewer_bot_lib/github_api.py index 2a57973e0..4f63acfd0 100644 --- a/scripts/reviewer_bot_lib/github_api.py +++ b/scripts/reviewer_bot_lib/github_api.py @@ -1,34 +1,124 @@ """GitHub transport and issue/PR mutation helpers.""" import json -import os import random -import sys -import time from urllib.parse import quote import requests +from . import retrying from .config import ( LOCK_API_RETRY_LIMIT, LOCK_RETRY_BASE_SECONDS, REVIEWER_BOARD_TOKEN_ENV, - STATUS_LABEL_CONFIG, ) -from .context import GitHubTransportContext +from .runtime_protocols import GitHubApiContext, GitHubTransportContext +RETRY_POLICY_NONE = retrying.RETRY_POLICY_NONE +RETRY_POLICY_IDEMPOTENT_READ = retrying.RETRY_POLICY_IDEMPOTENT_READ -def get_github_token() -> str: - token = os.environ.get("GITHUB_TOKEN") + +class _RandomJitter: + def uniform(self, lower: float, upper: float) -> float: + return random.uniform(lower, upper) + + +def _log(bot: GitHubApiContext, level: str, message: str, **fields) -> None: + bot.logger.event(level, message, **fields) + + +def _sleep(bot: GitHubApiContext, seconds: float) -> None: + bot.sleeper.sleep(seconds) + + +def _retry_delay(bot: GitHubApiContext, base_seconds: float, retry_attempt: int) -> float: + return retrying.bounded_exponential_delay( + base_seconds, + retry_attempt, + jitter=bot.jitter, + ) + + +def _is_pull_request(bot: GitHubApiContext) -> bool: + return bot.get_config_value("IS_PULL_REQUEST", "false").lower() == "true" + + +def _should_retry_status(status_code: int | None) -> bool: + return retrying.is_retryable_status(status_code) + + +def _classify_failure(status_code: int | None, *, invalid_payload: bool = False, transport_error: bool = False) -> str | None: + if invalid_payload: + return "invalid_payload" + if transport_error: + return "transport_error" + if status_code is None: + return None + if status_code == 404: + return "not_found" + if status_code == 401: + return "unauthorized" + if status_code == 403: + return "forbidden" + if status_code == 429: + return "rate_limited" + if status_code >= 500: + return "server_error" + return None + + +def _validate_rest_retry_policy(method: str, retry_policy: str) -> None: + retrying.additional_attempts_for_policy(retry_policy, LOCK_API_RETRY_LIMIT) + if retry_policy == RETRY_POLICY_NONE: + return + if method.upper() != "GET": + raise ValueError("idempotent_read retry policy is only valid for REST GET requests") + + +def _validate_graphql_retry_policy(query: str, retry_policy: str) -> None: + retrying.additional_attempts_for_policy(retry_policy, LOCK_API_RETRY_LIMIT) + if retry_policy == RETRY_POLICY_NONE: + return + stripped = query.lstrip() + if stripped.startswith("mutation"): + raise ValueError("idempotent_read retry policy is only valid for GraphQL queries") + + +def _build_result( + bot: GitHubTransportContext, + *, + status_code: int | None, + payload, + headers: dict[str, str] | None, + text: str, + ok: bool, + failure_kind: str | None, + retry_attempts: int, + transport_error: str | None = None, +): + return bot.GitHubApiResult( + status_code=status_code, + payload=payload, + headers=headers or {}, + text=text, + ok=ok, + failure_kind=failure_kind, + retry_attempts=retry_attempts, + transport_error=transport_error, + ) + + +def get_github_token(bot: GitHubApiContext) -> str: + token = bot.get_config_value("GITHUB_TOKEN") if not token: - print("ERROR: GITHUB_TOKEN not set", file=sys.stderr) + _log(bot, "error", "GITHUB_TOKEN not set") raise SystemExit(1) return token -def get_github_graphql_token(bot: GitHubTransportContext, *, prefer_board_token: bool = False) -> str: +def get_github_graphql_token(bot: GitHubApiContext, *, prefer_board_token: bool = False) -> str: if prefer_board_token: - token = os.environ.get(REVIEWER_BOARD_TOKEN_ENV) + token = bot.get_config_value(REVIEWER_BOARD_TOKEN_ENV) if not token: raise RuntimeError(f"{REVIEWER_BOARD_TOKEN_ENV} not set") return token @@ -36,16 +126,19 @@ def get_github_graphql_token(bot: GitHubTransportContext, *, prefer_board_token: def github_api_request( - bot: GitHubTransportContext, + bot: GitHubApiContext, method: str, endpoint: str, data: dict | None = None, extra_headers: dict[str, str] | None = None, *, + retry_policy: str = RETRY_POLICY_NONE, + timeout_seconds: float | None = None, suppress_error_log: bool = False, ): + _validate_rest_retry_policy(method, retry_policy) token = bot.get_github_token() - repo = f"{os.environ['REPO_OWNER']}/{os.environ['REPO_NAME']}" + repo = f"{bot.get_config_value('REPO_OWNER')}/{bot.get_config_value('REPO_NAME')}" url = f"https://api.github.com/repos/{repo}/{endpoint}" headers = { @@ -56,27 +149,85 @@ def github_api_request( if extra_headers: headers.update(extra_headers) - response = requests.request(method, url, headers=headers, json=data) - - payload = None - if response.content: + retry_attempts = 0 + max_attempts = retrying.max_attempts_for_policy(retry_policy, LOCK_API_RETRY_LIMIT) + for attempt in range(1, max_attempts + 1): try: - payload = response.json() - except ValueError: - payload = None + response = bot.rest_transport.request( + method, + url, + headers=headers, + json_data=data, + timeout_seconds=timeout_seconds, + ) + except requests.RequestException as exc: + failure_kind = _classify_failure(None, transport_error=True) + if attempt < max_attempts: + retry_attempts += 1 + _sleep(bot, _retry_delay(bot, LOCK_RETRY_BASE_SECONDS, retry_attempts)) + continue + if not suppress_error_log: + _log(bot, "error", f"GitHub API transport error: {exc}", transport_error=str(exc)) + return _build_result( + bot, + status_code=None, + payload=None, + headers={}, + text="", + ok=False, + failure_kind=failure_kind, + retry_attempts=retry_attempts, + transport_error=str(exc), + ) + + payload = None + invalid_payload = False + if response.content: + try: + payload = response.json() + except ValueError: + invalid_payload = True + + ok = response.status_code < 400 and not invalid_payload + normalized_headers = {key.lower(): value for key, value in response.headers.items()} + if ok: + return _build_result( + bot, + status_code=response.status_code, + payload=payload, + headers=normalized_headers, + text=response.text, + ok=True, + failure_kind=None, + retry_attempts=retry_attempts, + ) - ok = response.status_code < 400 - if not ok and not suppress_error_log: - print(f"GitHub API error: {response.status_code} - {response.text}", file=sys.stderr) + failure_kind = _classify_failure(response.status_code, invalid_payload=invalid_payload) + if retry_policy == RETRY_POLICY_IDEMPOTENT_READ and _should_retry_status(response.status_code) and attempt < max_attempts: + retry_attempts += 1 + _sleep(bot, _retry_delay(bot, LOCK_RETRY_BASE_SECONDS, retry_attempts)) + continue + + if not suppress_error_log: + _log( + bot, + "error", + f"GitHub API error: {response.status_code} - {response.text}", + status_code=response.status_code, + failure_kind=failure_kind, + ) + return _build_result( + bot, + status_code=response.status_code, + payload=payload, + headers=normalized_headers, + text=response.text, + ok=False, + failure_kind=failure_kind, + retry_attempts=retry_attempts, + ) - normalized_headers = {key.lower(): value for key, value in response.headers.items()} - return bot.GitHubApiResult( - status_code=response.status_code, - payload=payload, - headers=normalized_headers, - text=response.text, - ok=ok, - ) + raise AssertionError("unreachable") def github_api(bot: GitHubTransportContext, method: str, endpoint: str, data: dict | None = None): @@ -89,48 +240,109 @@ def github_api(bot: GitHubTransportContext, method: str, endpoint: str, data: di def github_graphql_request( - bot: GitHubTransportContext, + bot: GitHubApiContext, query: str, variables: dict | None = None, *, token: str | None = None, + retry_policy: str = RETRY_POLICY_NONE, + timeout_seconds: float | None = None, suppress_error_log: bool = False, ): + _validate_graphql_retry_policy(query, retry_policy) graphql_token = token or bot.get_github_graphql_token() headers = { "Authorization": f"Bearer {graphql_token}", "Accept": "application/vnd.github+json", "X-GitHub-Api-Version": "2022-11-28", } - response = requests.post( - "https://api.github.com/graphql", - headers=headers, - json={"query": query, "variables": variables or {}}, - ) - payload = None - if response.content: + retry_attempts = 0 + max_attempts = retrying.max_attempts_for_policy(retry_policy, LOCK_API_RETRY_LIMIT) + for attempt in range(1, max_attempts + 1): try: - payload = response.json() - except ValueError: - payload = None - - graphql_errors = payload.get("errors") if isinstance(payload, dict) else None - ok = response.status_code < 400 and not graphql_errors - if not ok and not suppress_error_log: - details = response.text - if graphql_errors: - details = json.dumps(graphql_errors, sort_keys=True) - print(f"GitHub GraphQL error: {response.status_code} - {details}", file=sys.stderr) - - normalized_headers = {key.lower(): value for key, value in response.headers.items()} - return bot.GitHubApiResult( - status_code=response.status_code, - payload=payload, - headers=normalized_headers, - text=response.text, - ok=ok, - ) + response = bot.graphql_transport.query( + "https://api.github.com/graphql", + headers=headers, + query=query, + variables=variables, + timeout_seconds=timeout_seconds, + ) + except requests.RequestException as exc: + failure_kind = _classify_failure(None, transport_error=True) + if retry_policy == RETRY_POLICY_IDEMPOTENT_READ and attempt < max_attempts: + retry_attempts += 1 + _sleep(bot, _retry_delay(bot, LOCK_RETRY_BASE_SECONDS, retry_attempts)) + continue + if not suppress_error_log: + _log(bot, "error", f"GitHub GraphQL transport error: {exc}", transport_error=str(exc)) + return _build_result( + bot, + status_code=None, + payload=None, + headers={}, + text="", + ok=False, + failure_kind=failure_kind, + retry_attempts=retry_attempts, + transport_error=str(exc), + ) + + payload = None + invalid_payload = False + if response.content: + try: + payload = response.json() + except ValueError: + invalid_payload = True + + graphql_errors = payload.get("errors") if isinstance(payload, dict) else None + ok = response.status_code < 400 and not graphql_errors and not invalid_payload + normalized_headers = {key.lower(): value for key, value in response.headers.items()} + if ok: + return _build_result( + bot, + status_code=response.status_code, + payload=payload, + headers=normalized_headers, + text=response.text, + ok=True, + failure_kind=None, + retry_attempts=retry_attempts, + ) + + failure_kind = _classify_failure( + response.status_code, + invalid_payload=invalid_payload or bool(graphql_errors), + ) + if _should_retry_status(response.status_code) and attempt < max_attempts: + retry_attempts += 1 + _sleep(bot, _retry_delay(bot, LOCK_RETRY_BASE_SECONDS, retry_attempts)) + continue + + if not suppress_error_log: + details = response.text + if graphql_errors: + details = json.dumps(graphql_errors, sort_keys=True) + _log( + bot, + "error", + f"GitHub GraphQL error: {response.status_code} - {details}", + status_code=response.status_code, + failure_kind=failure_kind, + ) + return _build_result( + bot, + status_code=response.status_code, + payload=payload, + headers=normalized_headers, + text=response.text, + ok=False, + failure_kind=failure_kind, + retry_attempts=retry_attempts, + ) + + raise AssertionError("unreachable") def github_graphql( @@ -164,8 +376,12 @@ def add_label(bot: GitHubTransportContext, issue_number: int, label: str) -> boo def remove_label(bot: GitHubTransportContext, issue_number: int, label: str) -> bool: - bot.github_api("DELETE", f"issues/{issue_number}/labels/{quote(label, safe='')}") - return True + response = bot.github_api_request( + "DELETE", + f"issues/{issue_number}/labels/{quote(label, safe='')}", + suppress_error_log=True, + ) + return response.status_code in {200, 204, 404} def add_label_with_status(bot: GitHubTransportContext, issue_number: int, label: str) -> bool: @@ -181,10 +397,13 @@ def add_label_with_status(bot: GitHubTransportContext, issue_number: int, label: raise RuntimeError( f"Permission denied adding label '{label}' to #{issue_number}: {response.text}" ) - print( - f"WARNING: Failed to add label '{label}' to #{issue_number} " - f"(status {response.status_code}): {response.text}", - file=sys.stderr, + _log( + bot, + "warning", + f"Failed to add label '{label}' to #{issue_number} (status {response.status_code}): {response.text}", + issue_number=issue_number, + label=label, + status_code=response.status_code, ) return False @@ -201,10 +420,13 @@ def remove_label_with_status(bot: GitHubTransportContext, issue_number: int, lab raise RuntimeError( f"Permission denied removing label '{label}' from #{issue_number}: {response.text}" ) - print( - f"WARNING: Failed to remove label '{label}' from #{issue_number} " - f"(status {response.status_code}): {response.text}", - file=sys.stderr, + _log( + bot, + "warning", + f"Failed to remove label '{label}' from #{issue_number} (status {response.status_code}): {response.text}", + issue_number=issue_number, + label=label, + status_code=response.status_code, ) return False @@ -216,14 +438,13 @@ def ensure_label_exists( color: str | None = None, description: str | None = None, ) -> bool: - label_config = STATUS_LABEL_CONFIG.get(label, {}) response = bot.github_api_request( "POST", "labels", { "name": label, - "color": color or label_config.get("color", "d73a4a"), - "description": description or label_config.get("description", ""), + "color": color or "d73a4a", + "description": description or "", }, suppress_error_log=True, ) @@ -231,27 +452,27 @@ def ensure_label_exists( if response.status_code in {201, 422}: return True - print( - f"WARNING: Failed to ensure label '{label}' exists (status {response.status_code}): " - f"{response.text}", - file=sys.stderr, + _log( + bot, + "warning", + f"Failed to ensure label '{label}' exists (status {response.status_code}): {response.text}", + label=label, + status_code=response.status_code, ) return False -def request_reviewer_assignment(bot: GitHubTransportContext, issue_number: int, username: str): - is_pr = os.environ.get("IS_PULL_REQUEST", "false").lower() == "true" - if is_pr: - endpoint = f"pulls/{issue_number}/requested_reviewers" - payload = {"reviewers": [username]} - assignment_target = "PR reviewer" - else: - endpoint = f"issues/{issue_number}/assignees" - payload = {"assignees": [username]} - assignment_target = "issue assignee" - - lock_api_retry_limit = getattr(bot, "LOCK_API_RETRY_LIMIT", LOCK_API_RETRY_LIMIT) - lock_retry_base_seconds = getattr(bot, "LOCK_RETRY_BASE_SECONDS", LOCK_RETRY_BASE_SECONDS) +def _request_assignment( + bot: GitHubTransportContext, + endpoint: str, + payload: dict, + *, + assignment_target: str, + issue_number: int, + username: str, +): + lock_api_retry_limit = bot.lock_api_retry_limit() + lock_retry_base_seconds = bot.lock_retry_base_seconds() for attempt in range(1, lock_api_retry_limit + 1): response = bot.github_api_request("POST", endpoint, payload, suppress_error_log=True) @@ -266,12 +487,17 @@ def request_reviewer_assignment(bot: GitHubTransportContext, issue_number: int, ) if response.status_code == 429 or response.status_code >= 500: if attempt < lock_api_retry_limit: - delay = lock_retry_base_seconds + random.uniform(0, lock_retry_base_seconds) - print( - f"Retryable {assignment_target} API failure for @{username} on #{issue_number} " - f"(status {response.status_code}); retrying ({attempt}/{lock_api_retry_limit})" + delay = _retry_delay(bot, lock_retry_base_seconds, attempt) + _log( + bot, + "warning", + f"Retryable {assignment_target} API failure for @{username} on #{issue_number} (status {response.status_code}); retrying ({attempt}/{lock_api_retry_limit})", + issue_number=issue_number, + username=username, + status_code=response.status_code, + retry_attempt=attempt, ) - time.sleep(delay) + _sleep(bot, delay) continue return bot.AssignmentAttempt( success=False, @@ -279,48 +505,62 @@ def request_reviewer_assignment(bot: GitHubTransportContext, issue_number: int, exhausted_retryable_failure=True, ) - print( - f"WARNING: Unexpected {assignment_target} API status {response.status_code} " - f"for @{username} on #{issue_number}: {response.text}", - file=sys.stderr, + _log( + bot, + "warning", + f"Unexpected {assignment_target} API status {response.status_code} for @{username} on #{issue_number}: {response.text}", + issue_number=issue_number, + username=username, + status_code=response.status_code, ) return bot.AssignmentAttempt(success=False, status_code=response.status_code) return bot.AssignmentAttempt(success=False, status_code=None, exhausted_retryable_failure=True) -def assign_reviewer(bot: GitHubTransportContext, issue_number: int, username: str) -> bool: - return bot.request_reviewer_assignment(issue_number, username).success - +def request_pr_reviewer_assignment(bot: GitHubTransportContext, issue_number: int, username: str): + return _request_assignment( + bot, + f"pulls/{issue_number}/requested_reviewers", + {"reviewers": [username]}, + assignment_target="PR reviewer", + issue_number=issue_number, + username=username, + ) -def get_assignment_failure_comment(bot: GitHubTransportContext, reviewer: str, attempt) -> str | None: - is_pr = os.environ.get("IS_PULL_REQUEST", "false").lower() == "true" - if attempt.status_code == 422: - if is_pr: - return bot.REVIEWER_REQUEST_422_TEMPLATE.format(reviewer=reviewer) - return ( - f"@{reviewer} is designated as reviewer by queue rotation, but GitHub could not " - "add them as an assignee automatically (API 422)." - ) - if attempt.exhausted_retryable_failure: - return ( - f"@{reviewer} is designated as reviewer by queue rotation, but GitHub could not " - f"add them to PR Reviewers automatically after retries (status {attempt.status_code}). " - "A triage+ approver may still be required before merge queue." - ) - return None +def assign_issue_assignee(bot: GitHubTransportContext, issue_number: int, username: str): + return _request_assignment( + bot, + f"issues/{issue_number}/assignees", + {"assignees": [username]}, + assignment_target="issue assignee", + issue_number=issue_number, + username=username, + ) -def get_issue_assignees(bot: GitHubTransportContext, issue_number: int) -> list[str]: - is_pr = os.environ.get("IS_PULL_REQUEST", "false").lower() == "true" +def get_issue_assignees(bot: GitHubTransportContext, issue_number: int) -> list[str] | None: + is_pr = _is_pull_request(bot) if is_pr: - result = bot.github_api("GET", f"pulls/{issue_number}") - if result and "requested_reviewers" in result: + try: + response = bot.github_api_request("GET", f"pulls/{issue_number}", retry_policy=RETRY_POLICY_IDEMPOTENT_READ) + result = response.payload + if not response.ok: + return None + except SystemExit: + result = bot.github_api("GET", f"pulls/{issue_number}") + if isinstance(result, dict) and "requested_reviewers" in result: return [reviewer["login"] for reviewer in result["requested_reviewers"]] else: - result = bot.github_api("GET", f"issues/{issue_number}") - if result and "assignees" in result: + try: + response = bot.github_api_request("GET", f"issues/{issue_number}", retry_policy=RETRY_POLICY_IDEMPOTENT_READ) + result = response.payload + if not response.ok: + return None + except SystemExit: + result = bot.github_api("GET", f"issues/{issue_number}") + if isinstance(result, dict) and "assignees" in result: return [assignee["login"] for assignee in result["assignees"]] return [] @@ -332,7 +572,7 @@ def add_reaction(bot: GitHubTransportContext, comment_id: int, reaction: str) -> ) -def remove_assignee(bot: GitHubTransportContext, issue_number: int, username: str) -> bool: +def remove_issue_assignee(bot: GitHubTransportContext, issue_number: int, username: str) -> bool: return ( bot.github_api("DELETE", f"issues/{issue_number}/assignees", {"assignees": [username]}) is not None @@ -350,16 +590,36 @@ def remove_pr_reviewer(bot: GitHubTransportContext, issue_number: int, username: ) -def unassign_reviewer(bot: GitHubTransportContext, issue_number: int, username: str) -> bool: - is_pr = os.environ.get("IS_PULL_REQUEST", "false").lower() == "true" - if is_pr: - bot.remove_pr_reviewer(issue_number, username) - return bot.remove_assignee(issue_number, username) +def get_user_permission_status( + bot: GitHubTransportContext, + username: str, + required_permission: str = "triage", +) -> str: + try: + response = bot.github_api_request( + "GET", + f"collaborators/{username}/permission", + retry_policy=RETRY_POLICY_IDEMPOTENT_READ, + ) + except SystemExit: + return "unavailable" + if not response.ok: + return "unavailable" + result = response.payload + if not isinstance(result, dict): + return "unavailable" + permissions = result.get("user", {}).get("permissions", {}) + if not isinstance(permissions, dict): + return "unavailable" + return "granted" if permissions.get(required_permission, False) else "denied" -def check_user_permission(bot: GitHubTransportContext, username: str, required_permission: str = "triage") -> bool: - result = bot.github_api("GET", f"collaborators/{username}/permission") - if not result: - return False - permissions = result.get("user", {}).get("permissions", {}) - return permissions.get(required_permission, False) +def check_user_permission( + bot: GitHubTransportContext, + username: str, + required_permission: str = "triage", +) -> bool | None: + status = get_user_permission_status(bot, username, required_permission) + if status == "unavailable": + return None + return status == "granted" diff --git a/scripts/reviewer_bot_lib/guidance.py b/scripts/reviewer_bot_lib/guidance.py index 517d6b550..4d46a5633 100644 --- a/scripts/reviewer_bot_lib/guidance.py +++ b/scripts/reviewer_bot_lib/guidance.py @@ -3,6 +3,27 @@ from .config import BOT_MENTION +def get_assignment_failure_comment(reviewer: str, attempt, *, is_pull_request: bool) -> str | None: + if attempt.status_code == 422: + if is_pull_request: + return ( + "@{reviewer} is designated as reviewer by queue rotation, but GitHub could not add them to PR " + "Reviewers automatically (API 422). A triage+ approver may still be required before merge queue." + ).format(reviewer=reviewer) + return ( + f"@{reviewer} is designated as reviewer by queue rotation, but GitHub could not " + "add them as an assignee automatically (API 422)." + ) + if attempt.exhausted_retryable_failure: + target = "PR Reviewers" if is_pull_request else "issue assignees" + suffix = " A triage+ approver may still be required before merge queue." if is_pull_request else "" + return ( + f"@{reviewer} is designated as reviewer by queue rotation, but GitHub could not add them to " + f"{target} automatically after retries (status {attempt.status_code}).{suffix}" + ) + return None + + def get_issue_guidance(reviewer: str, issue_author: str) -> str: """Generate guidance text for an issue reviewer.""" return f"""👋 Hey @{reviewer}! You've been assigned to review this coding guideline issue. diff --git a/scripts/reviewer_bot_lib/lease_lock.py b/scripts/reviewer_bot_lib/lease_lock.py index 9bd2008c6..045900d3d 100644 --- a/scripts/reviewer_bot_lib/lease_lock.py +++ b/scripts/reviewer_bot_lib/lease_lock.py @@ -1,25 +1,70 @@ """Reviewer-bot lease lock helpers.""" -import json -import os -import random -import time -import uuid from datetime import datetime, timezone from typing import Any +from . import lock_codec, retrying from .config import ( - LOCK_API_RETRY_LIMIT, - LOCK_COMMIT_MARKER, - LOCK_LEASE_TTL_SECONDS, - LOCK_MAX_WAIT_SECONDS, - LOCK_REF_BOOTSTRAP_BRANCH, LOCK_REF_NAME, - LOCK_RENEWAL_WINDOW_SECONDS, - LOCK_RETRY_BASE_SECONDS, LeaseContext, ) -from .context import LeaseLockContext +from .runtime_protocols import LeaseLockContext, LeaseLockRuntimeContext + + +def _log(bot: LeaseLockRuntimeContext, level: str, message: str, **fields: Any) -> None: + bot.logger.event(level, message, **fields) + + +def _sleep(bot: LeaseLockRuntimeContext, seconds: float) -> None: + bot.sleeper.sleep(seconds) + + +def _jitter(bot: LeaseLockRuntimeContext, lower: float, upper: float) -> float: + return bot.jitter.uniform(lower, upper) + + +def _retry_delay(bot: LeaseLockRuntimeContext, base_seconds: float, retry_attempt: int) -> float: + class _BotJitter: + def uniform(self, lower: float, upper: float) -> float: + return _jitter(bot, lower, upper) + + return retrying.bounded_exponential_delay( + base_seconds, + retry_attempt, + jitter=_BotJitter(), + ) + + +def _now(bot: LeaseLockRuntimeContext) -> datetime: + return bot.clock.now() + + +def _monotonic(bot: LeaseLockRuntimeContext) -> float: + return bot.time.monotonic() + + +def _uuid4_hex(bot: LeaseLockRuntimeContext) -> str: + return bot.uuid_source.uuid4_hex() + + +def _lock_lease_ttl_seconds(bot: LeaseLockRuntimeContext) -> int: + return bot.lock_lease_ttl_seconds() + + +def _lock_api_retry_limit(bot: LeaseLockRuntimeContext) -> int: + return bot.lock_api_retry_limit() + + +def _lock_retry_base_seconds(bot: LeaseLockRuntimeContext) -> float: + return bot.lock_retry_base_seconds() + + +def _lock_max_wait_seconds(bot: LeaseLockRuntimeContext) -> int: + return bot.lock_max_wait_seconds() + + +def _lock_renewal_window_seconds(bot: LeaseLockRuntimeContext) -> int: + return bot.lock_renewal_window_seconds() def lock_is_currently_valid(bot: LeaseLockContext, lock_meta: dict, now: datetime | None = None) -> bool: @@ -33,33 +78,33 @@ def lock_is_currently_valid(bot: LeaseLockContext, lock_meta: dict, now: datetim expires_at = bot.parse_iso8601_timestamp(lock_meta.get("lock_expires_at")) if expires_at is None: return False - now = now or datetime.now(timezone.utc) + now = now or _now(bot) return expires_at > now -def get_lock_owner_context() -> tuple[str, str, str]: +def get_lock_owner_context(bot: LeaseLockContext) -> tuple[str, str, str]: run_id = ( - os.environ.get("WORKFLOW_RUN_ID", "").strip() - or os.environ.get("GITHUB_RUN_ID", "").strip() + bot.get_config_value("WORKFLOW_RUN_ID", "").strip() + or bot.get_config_value("GITHUB_RUN_ID", "").strip() or "local-run" ) workflow = ( - os.environ.get("WORKFLOW_NAME", "").strip() - or os.environ.get("GITHUB_WORKFLOW", "").strip() + bot.get_config_value("WORKFLOW_NAME", "").strip() + or bot.get_config_value("GITHUB_WORKFLOW", "").strip() or "reviewer-bot" ) job = ( - os.environ.get("WORKFLOW_JOB_NAME", "").strip() - or os.environ.get("GITHUB_JOB", "").strip() + bot.get_config_value("WORKFLOW_JOB_NAME", "").strip() + or bot.get_config_value("GITHUB_JOB", "").strip() or "reviewer-bot" ) return run_id, workflow, job def build_lock_metadata(bot: LeaseLockContext, lock_token: str, lock_owner_run_id: str, lock_owner_workflow: str, lock_owner_job: str) -> dict: - acquired_at = datetime.now(timezone.utc) - expires_at = acquired_at.timestamp() + getattr(bot, "LOCK_LEASE_TTL_SECONDS", LOCK_LEASE_TTL_SECONDS) - return bot.normalize_lock_metadata( + acquired_at = _now(bot) + expires_at = acquired_at.timestamp() + _lock_lease_ttl_seconds(bot) + return lock_codec.normalize_lock_metadata( { "schema_version": 1, "lock_state": "locked", @@ -74,7 +119,7 @@ def build_lock_metadata(bot: LeaseLockContext, lock_token: str, lock_owner_run_i def clear_lock_metadata(bot: LeaseLockContext) -> dict: - return bot.normalize_lock_metadata({"lock_state": "unlocked"}) + return lock_codec.normalize_lock_metadata({"lock_state": "unlocked"}) def normalize_lock_ref_name(ref_name: str) -> str: @@ -87,7 +132,7 @@ def normalize_lock_ref_name(ref_name: str) -> str: def get_lock_ref_name(bot: LeaseLockContext) -> str: - return normalize_lock_ref_name(getattr(bot, "LOCK_REF_NAME", LOCK_REF_NAME)) + return normalize_lock_ref_name(bot.lock_ref_name()) def get_lock_ref_display(bot: LeaseLockContext) -> str: @@ -163,9 +208,13 @@ def _activate_lease_context( lock_ref=bot.get_lock_ref_display(), lock_expires_at=lock_expires_at, ) - print( - "Acquired reviewer-bot lease lock " - f"(run_id={lock_owner_run_id}, token_prefix={lock_token[:8]}, lock_ref={bot.get_lock_ref_display()})" + _log( + bot, + "info", + "Acquired reviewer-bot lease lock", + run_id=lock_owner_run_id, + token_prefix=lock_token[:8], + lock_ref=bot.get_lock_ref_display(), ) return bot.ACTIVE_LEASE_CONTEXT @@ -179,24 +228,23 @@ def _snapshot_is_stale_unlocked_predecessor(current_lock: dict) -> bool: def render_lock_commit_message(bot: LeaseLockContext, lock_meta: dict) -> str: - lock_json = json.dumps(bot.normalize_lock_metadata(lock_meta), sort_keys=False) - return f"{LOCK_COMMIT_MARKER}\n{lock_json}" + del bot + return lock_codec.render_lock_commit_message(lock_meta) def parse_lock_metadata_from_lock_commit_message(bot: LeaseLockContext, message: str) -> dict: - if not message.startswith(f"{LOCK_COMMIT_MARKER}\n"): - return bot.clear_lock_metadata() - lock_json = message.split("\n", 1)[1] - try: - parsed = json.loads(lock_json) - except json.JSONDecodeError: - return bot.clear_lock_metadata() - return bot.normalize_lock_metadata(parsed if isinstance(parsed, dict) else None) + del bot + return lock_codec.parse_lock_commit_message(message) def ensure_lock_ref_exists(bot: LeaseLockContext) -> str: lock_ref = get_lock_ref_name(bot) - response = bot.github_api_request("GET", f"git/ref/{lock_ref}", suppress_error_log=True) + response = bot.github_api_request( + "GET", + f"git/ref/{lock_ref}", + retry_policy="idempotent_read", + suppress_error_log=True, + ) if response.status_code == 200: ref_sha = extract_ref_sha(response.payload) if not ref_sha: @@ -208,10 +256,11 @@ def ensure_lock_ref_exists(bot: LeaseLockContext) -> str: f"{get_lock_ref_display(bot)} (status {response.status_code}): {response.text}" ) - default_branch = getattr(bot, "LOCK_REF_BOOTSTRAP_BRANCH", LOCK_REF_BOOTSTRAP_BRANCH) + default_branch = bot.lock_ref_bootstrap_branch() branch_response = bot.github_api_request( "GET", f"git/ref/heads/{default_branch}", + retry_policy="idempotent_read", suppress_error_log=True, ) if branch_response.status_code != 200: @@ -236,7 +285,12 @@ def ensure_lock_ref_exists(bot: LeaseLockContext) -> str: f"{get_lock_ref_display(bot)} (status {create_response.status_code}): {create_response.text}" ) - refresh_response = bot.github_api_request("GET", f"git/ref/{lock_ref}", suppress_error_log=True) + refresh_response = bot.github_api_request( + "GET", + f"git/ref/{lock_ref}", + retry_policy="idempotent_read", + suppress_error_log=True, + ) if refresh_response.status_code != 200: raise RuntimeError( "Unable to read reviewer-bot lock ref after create " @@ -251,7 +305,12 @@ def ensure_lock_ref_exists(bot: LeaseLockContext) -> str: def get_lock_ref_snapshot(bot: LeaseLockContext) -> tuple[str, str, dict]: ref_sha = ensure_lock_ref_exists(bot) - commit_response = bot.github_api_request("GET", f"git/commits/{ref_sha}", suppress_error_log=True) + commit_response = bot.github_api_request( + "GET", + f"git/commits/{ref_sha}", + retry_policy="idempotent_read", + suppress_error_log=True, + ) if commit_response.status_code != 200: raise RuntimeError( f"Failed to read lock commit {ref_sha} (status {commit_response.status_code}): {commit_response.text}" @@ -295,32 +354,37 @@ def ensure_state_issue_lease_lock_fresh(bot: LeaseLockContext) -> bool: expires_at = bot.parse_iso8601_timestamp(context.lock_expires_at) if expires_at is None: return bot.renew_state_issue_lease_lock(context) - remaining_seconds = (expires_at - datetime.now(timezone.utc)).total_seconds() - renewal_window = getattr(bot, "LOCK_RENEWAL_WINDOW_SECONDS", LOCK_RENEWAL_WINDOW_SECONDS) + remaining_seconds = (expires_at - _now(bot)).total_seconds() + renewal_window = _lock_renewal_window_seconds(bot) if remaining_seconds > renewal_window: return True - print( - "Reviewer-bot lease lock nearing expiry; attempting renewal " - f"(remaining={int(remaining_seconds)}s, token_prefix={context.lock_token[:8]})" + _log( + bot, + "info", + "Reviewer-bot lease lock nearing expiry; attempting renewal", + remaining_seconds=int(remaining_seconds), + token_prefix=context.lock_token[:8], ) return bot.renew_state_issue_lease_lock(context) def renew_state_issue_lease_lock(bot: LeaseLockContext, context: LeaseContext) -> bool: - retry_limit = getattr(bot, "LOCK_API_RETRY_LIMIT", LOCK_API_RETRY_LIMIT) - retry_base = getattr(bot, "LOCK_RETRY_BASE_SECONDS", LOCK_RETRY_BASE_SECONDS) + retry_limit = _lock_api_retry_limit(bot) + retry_base = _lock_retry_base_seconds(bot) for attempt in range(1, retry_limit + 1): try: ref_head_sha, tree_sha, current_lock = bot.get_lock_ref_snapshot() except RuntimeError as exc: - print(f"ERROR: Failed to read lock snapshot during renewal: {exc}", file=bot.sys.stderr) + _log(bot, "error", f"Failed to read lock snapshot during renewal: {exc}") return False current_token = current_lock.get("lock_token") if current_token != context.lock_token: - print( - "ERROR: Cannot renew reviewer-bot lock due to token mismatch " - f"(expected prefix={context.lock_token[:8]}, got prefix={str(current_token)[:8]})", - file=bot.sys.stderr, + _log( + bot, + "error", + "Cannot renew reviewer-bot lock due to token mismatch", + expected_prefix=context.lock_token[:8], + actual_prefix=str(current_token)[:8], ) return False desired_lock = bot.build_lock_metadata( @@ -331,62 +395,75 @@ def renew_state_issue_lease_lock(bot: LeaseLockContext, context: LeaseContext) - ) create_response = bot.create_lock_commit(ref_head_sha, tree_sha, desired_lock) if create_response.status_code != 201: - if create_response.status_code == 429 or create_response.status_code >= 500: - delay = retry_base + random.uniform(0, retry_base) - print( - "Retryable lease lock renewal commit failure " - f"(status {create_response.status_code}); retrying ({attempt}/{retry_limit})", - file=bot.sys.stderr, + if retrying.is_retryable_status(create_response.status_code): + delay = _retry_delay(bot, retry_base, attempt) + _log( + bot, + "warning", + "Retryable lease lock renewal commit failure", + status_code=create_response.status_code, + retry_attempt=attempt, + retry_limit=retry_limit, ) - time.sleep(delay) + _sleep(bot, delay) continue - print( - f"ERROR: Failed to create lock renewal commit (status {create_response.status_code}): {create_response.text}", - file=bot.sys.stderr, + _log( + bot, + "error", + f"Failed to create lock renewal commit (status {create_response.status_code}): {create_response.text}", + status_code=create_response.status_code, ) return False new_commit_sha = extract_commit_sha(create_response.payload) if not new_commit_sha: - print("ERROR: Lock renewal commit response missing SHA", file=bot.sys.stderr) + _log(bot, "error", "Lock renewal commit response missing SHA") return False update_response = bot.cas_update_lock_ref(new_commit_sha) if update_response.status_code == 200: context.lock_expires_at = desired_lock.get("lock_expires_at") - print( - "Renewed reviewer-bot lease lock " - f"(run_id={context.lock_owner_run_id}, token_prefix={context.lock_token[:8]})" + _log( + bot, + "info", + "Renewed reviewer-bot lease lock", + run_id=context.lock_owner_run_id, + token_prefix=context.lock_token[:8], ) return True - if update_response.status_code in {409, 422, 429} or update_response.status_code >= 500: - delay = retry_base + random.uniform(0, retry_base) - print( - "Retryable lease lock renewal ref update failure " - f"(status {update_response.status_code}); retrying ({attempt}/{retry_limit})", - file=bot.sys.stderr, + if update_response.status_code in {409, 422} or retrying.is_retryable_status(update_response.status_code): + delay = _retry_delay(bot, retry_base, attempt) + _log( + bot, + "warning", + "Retryable lease lock renewal ref update failure", + status_code=update_response.status_code, + retry_attempt=attempt, + retry_limit=retry_limit, ) - time.sleep(delay) + _sleep(bot, delay) continue - print( - f"ERROR: Failed to update lock ref during renewal (status {update_response.status_code}): {update_response.text}", - file=bot.sys.stderr, + _log( + bot, + "error", + f"Failed to update lock ref during renewal (status {update_response.status_code}): {update_response.text}", + status_code=update_response.status_code, ) return False - print("ERROR: Exhausted retries while renewing reviewer-bot lease lock", file=bot.sys.stderr) + _log(bot, "error", "Exhausted retries while renewing reviewer-bot lease lock") return False def acquire_state_issue_lease_lock(bot: LeaseLockContext) -> LeaseContext: if bot.ACTIVE_LEASE_CONTEXT is not None: return bot.ACTIVE_LEASE_CONTEXT - lock_token = uuid.uuid4().hex - lock_owner_run_id, lock_owner_workflow, lock_owner_job = get_lock_owner_context() - wait_started_at = time.monotonic() + lock_token = _uuid4_hex(bot) + lock_owner_run_id, lock_owner_workflow, lock_owner_job = get_lock_owner_context(bot) + wait_started_at = _monotonic(bot) attempt = 0 - max_wait = getattr(bot, "LOCK_MAX_WAIT_SECONDS", LOCK_MAX_WAIT_SECONDS) - retry_base = getattr(bot, "LOCK_RETRY_BASE_SECONDS", LOCK_RETRY_BASE_SECONDS) + max_wait = _lock_max_wait_seconds(bot) + retry_base = _lock_retry_base_seconds(bot) while True: attempt += 1 - elapsed = time.monotonic() - wait_started_at + elapsed = _monotonic(bot) - wait_started_at if elapsed > max_wait: raise RuntimeError( "Timed out waiting for reviewer-bot lease lock " @@ -394,7 +471,7 @@ def acquire_state_issue_lease_lock(bot: LeaseLockContext) -> LeaseContext: f"lock_ref={bot.get_lock_ref_display()})" ) ref_head_sha, tree_sha, current_lock = bot.get_lock_ref_snapshot() - now = datetime.now(timezone.utc) + now = _now(bot) lock_valid = bot.lock_is_currently_valid(current_lock, now) if lock_valid and _snapshot_matches_expected_lock(current_lock, lock_token): if not _snapshot_matches_expected_owner( @@ -418,13 +495,16 @@ def acquire_state_issue_lease_lock(bot: LeaseLockContext) -> LeaseContext: ) create_response = bot.create_lock_commit(ref_head_sha, tree_sha, desired_lock) if create_response.status_code != 201: - if create_response.status_code == 429 or create_response.status_code >= 500: - print( - "Retryable lease lock acquire commit failure " - f"(status {create_response.status_code}); retrying (attempt {attempt})" + if retrying.is_retryable_status(create_response.status_code): + _log( + bot, + "warning", + "Retryable lease lock acquire commit failure", + status_code=create_response.status_code, + retry_attempt=attempt, ) - delay = retry_base + random.uniform(0, retry_base) - time.sleep(delay) + delay = _retry_delay(bot, retry_base, attempt) + _sleep(bot, delay) continue if create_response.status_code in {401, 403}: raise RuntimeError( @@ -459,12 +539,15 @@ def acquire_state_issue_lease_lock(bot: LeaseLockContext) -> LeaseContext: desired_lock.get("lock_expires_at"), ) if _snapshot_is_stale_unlocked_predecessor(snapshot_lock): - print( - "Lease lock acquire visibility lag detected; retrying confirmation " - f"(attempt {attempt}, token_prefix={lock_token[:8]})" + _log( + bot, + "warning", + "Lease lock acquire visibility lag detected; retrying confirmation", + retry_attempt=attempt, + token_prefix=lock_token[:8], ) - delay = retry_base + random.uniform(0, retry_base) - time.sleep(delay) + delay = _retry_delay(bot, retry_base, attempt) + _sleep(bot, delay) continue conflicting_token = snapshot_lock.get("lock_token") if isinstance(snapshot_lock, dict) else None raise RuntimeError( @@ -472,10 +555,7 @@ def acquire_state_issue_lease_lock(bot: LeaseLockContext) -> LeaseContext: f"(expected prefix={lock_token[:8]}, got prefix={str(conflicting_token)[:8]})" ) if update_response.status_code in {409, 422}: - print( - "Lease lock acquire conflict " - f"(status {update_response.status_code}); retrying (attempt {attempt})" - ) + _log(bot, "warning", "Lease lock acquire conflict", status_code=update_response.status_code, retry_attempt=attempt) elif update_response.status_code == 404: raise RuntimeError(f"Lock ref {bot.get_lock_ref_display()} not found while acquiring lease lock") elif update_response.status_code in {401, 403}: @@ -483,11 +563,8 @@ def acquire_state_issue_lease_lock(bot: LeaseLockContext) -> LeaseContext: "Insufficient permission to acquire reviewer-bot lease lock " f"(status {update_response.status_code}): {update_response.text}" ) - elif update_response.status_code == 429 or update_response.status_code >= 500: - print( - "Retryable lease lock acquire failure " - f"(status {update_response.status_code}); retrying (attempt {attempt})" - ) + elif retrying.is_retryable_status(update_response.status_code): + _log(bot, "warning", "Retryable lease lock acquire failure", status_code=update_response.status_code, retry_attempt=attempt) else: raise RuntimeError( "Unexpected status while acquiring reviewer-bot lease lock " @@ -496,12 +573,16 @@ def acquire_state_issue_lease_lock(bot: LeaseLockContext) -> LeaseContext: else: lock_owner = current_lock.get("lock_owner_run_id") or "unknown" lock_expires_at = current_lock.get("lock_expires_at") or "unknown" - print( - "Reviewer-bot lease lock currently held by " - f"run_id={lock_owner} until {lock_expires_at}; waiting (lock_ref={bot.get_lock_ref_display()})" + _log( + bot, + "info", + "Reviewer-bot lease lock currently held; waiting", + lock_owner=lock_owner, + lock_expires_at=lock_expires_at, + lock_ref=bot.get_lock_ref_display(), ) - delay = retry_base + random.uniform(0, retry_base) - time.sleep(delay) + delay = _retry_delay(bot, retry_base, attempt) + _sleep(bot, delay) def release_state_issue_lease_lock(bot: LeaseLockContext) -> bool: @@ -509,88 +590,109 @@ def release_state_issue_lease_lock(bot: LeaseLockContext) -> bool: if context is None: return True released = False - retry_limit = getattr(bot, "LOCK_API_RETRY_LIMIT", LOCK_API_RETRY_LIMIT) - retry_base = getattr(bot, "LOCK_RETRY_BASE_SECONDS", LOCK_RETRY_BASE_SECONDS) + retry_limit = _lock_api_retry_limit(bot) + retry_base = _lock_retry_base_seconds(bot) try: for attempt in range(1, retry_limit + 1): try: ref_head_sha, tree_sha, current_lock = bot.get_lock_ref_snapshot() except RuntimeError as exc: - print(f"ERROR: Failed to read lock snapshot while releasing lock: {exc}", file=bot.sys.stderr) + _log(bot, "error", f"Failed to read lock snapshot while releasing lock: {exc}") break current_token = current_lock.get("lock_token") if current_token != context.lock_token: if _snapshot_is_stale_unlocked_predecessor(current_lock): - print( - "Lease lock release observed stale unlocked predecessor; retrying " - f"(attempt {attempt}, token_prefix={context.lock_token[:8]})", - file=bot.sys.stderr, + _log( + bot, + "warning", + "Lease lock release observed stale unlocked predecessor; retrying", + retry_attempt=attempt, + token_prefix=context.lock_token[:8], ) - delay = retry_base + random.uniform(0, retry_base) - time.sleep(delay) + delay = _retry_delay(bot, retry_base, attempt) + _sleep(bot, delay) continue - print( - "WARNING: Lease lock token mismatch during release; " - f"expected prefix={context.lock_token[:8]}, got prefix={str(current_token)[:8]}", - file=bot.sys.stderr, + _log( + bot, + "warning", + "Lease lock token mismatch during release", + expected_prefix=context.lock_token[:8], + actual_prefix=str(current_token)[:8], ) return False create_response = bot.create_lock_commit(ref_head_sha, tree_sha, bot.clear_lock_metadata()) if create_response.status_code != 201: - if create_response.status_code in {429} or create_response.status_code >= 500: - print( - "Retryable lease lock release commit failure " - f"(status {create_response.status_code}); retrying ({attempt}/{retry_limit})", - file=bot.sys.stderr, + if retrying.is_retryable_status(create_response.status_code): + _log( + bot, + "warning", + "Retryable lease lock release commit failure", + status_code=create_response.status_code, + retry_attempt=attempt, + retry_limit=retry_limit, ) - delay = retry_base + random.uniform(0, retry_base) - time.sleep(delay) + delay = _retry_delay(bot, retry_base, attempt) + _sleep(bot, delay) continue - print( - f"ERROR: Failed to create lock release commit (status {create_response.status_code}): {create_response.text}", - file=bot.sys.stderr, + _log( + bot, + "error", + f"Failed to create lock release commit (status {create_response.status_code}): {create_response.text}", + status_code=create_response.status_code, ) break new_commit_sha = extract_commit_sha(create_response.payload) if not new_commit_sha: - print("ERROR: Lock release commit response missing SHA", file=bot.sys.stderr) + _log(bot, "error", "Lock release commit response missing SHA") break update_response = bot.cas_update_lock_ref(new_commit_sha) if update_response.status_code == 200: released = True - print( - "Released reviewer-bot lease lock " - f"(run_id={context.lock_owner_run_id}, token_prefix={context.lock_token[:8]}, lock_ref={bot.get_lock_ref_display()})" + _log( + bot, + "info", + "Released reviewer-bot lease lock", + run_id=context.lock_owner_run_id, + token_prefix=context.lock_token[:8], + lock_ref=bot.get_lock_ref_display(), ) return True - if update_response.status_code in {409, 422, 429} or update_response.status_code >= 500: - print( - "Retryable lease lock release failure " - f"(status {update_response.status_code}); retrying ({attempt}/{retry_limit})", - file=bot.sys.stderr, + if update_response.status_code in {409, 422} or retrying.is_retryable_status(update_response.status_code): + _log( + bot, + "warning", + "Retryable lease lock release failure", + status_code=update_response.status_code, + retry_attempt=attempt, + retry_limit=retry_limit, ) - delay = retry_base + random.uniform(0, retry_base) - time.sleep(delay) + delay = _retry_delay(bot, retry_base, attempt) + _sleep(bot, delay) continue if update_response.status_code in {401, 403, 404}: - print( - "ERROR: Hard failure releasing reviewer-bot lease lock " - f"(status {update_response.status_code}): {update_response.text}", - file=bot.sys.stderr, + _log( + bot, + "error", + f"Hard failure releasing reviewer-bot lease lock (status {update_response.status_code}): {update_response.text}", + status_code=update_response.status_code, ) break - print( - "ERROR: Unexpected status while releasing reviewer-bot lease lock " - f"(status {update_response.status_code}): {update_response.text}", - file=bot.sys.stderr, + _log( + bot, + "error", + f"Unexpected status while releasing reviewer-bot lease lock (status {update_response.status_code}): {update_response.text}", + status_code=update_response.status_code, ) break return False finally: if not released: - print( - "ERROR: Lease lock release failed " - f"(run_id={context.lock_owner_run_id}, token_prefix={context.lock_token[:8]}, state_issue_url={context.state_issue_url})", - file=bot.sys.stderr, + _log( + bot, + "error", + "Lease lock release failed", + run_id=context.lock_owner_run_id, + token_prefix=context.lock_token[:8], + state_issue_url=context.state_issue_url, ) bot.ACTIVE_LEASE_CONTEXT = None diff --git a/scripts/reviewer_bot_lib/lifecycle.py b/scripts/reviewer_bot_lib/lifecycle.py index bad3765ae..6b2f8b530 100644 --- a/scripts/reviewer_bot_lib/lifecycle.py +++ b/scripts/reviewer_bot_lib/lifecycle.py @@ -4,10 +4,41 @@ import hashlib import json -import os +from copy import deepcopy +from dataclasses import dataclass from datetime import datetime, timezone +from pathlib import Path -from .guidance import get_fls_audit_guidance, get_issue_guidance, get_pr_guidance +from .config import TRANSITION_NOTICE_MARKER_PREFIX +from .guidance import ( + get_assignment_failure_comment, + get_fls_audit_guidance, + get_issue_guidance, + get_pr_guidance, +) +from .review_state import ( + accept_channel_event, + ensure_review_entry, + mark_review_complete, + record_transition_notice_sent, + set_current_reviewer, +) +from .reviews import rebuild_pr_approval_state + + +def _log(bot, level: str, message: str, **fields) -> None: + bot.logger.event(level, message, **fields) + + +@dataclass(frozen=True) +class HeadObservationRepairResult: + changed: bool + outcome: str + failure_kind: str | None = None + reason: str | None = None + + def __bool__(self) -> bool: + return self.changed def _now_iso() -> str: @@ -18,10 +49,6 @@ def _runtime_epoch(state: dict) -> str: return str(state.get("freshness_runtime_epoch", "")).strip() or "legacy_v14" -def _is_pr_event() -> bool: - return os.environ.get("IS_PULL_REQUEST", "false").lower() == "true" - - def _normalize_comment_body(body: str) -> str: return "\n".join(line.rstrip() for line in body.replace("\r\n", "\n").split("\n")).strip() @@ -30,13 +57,34 @@ def _semantic_digest(value: str) -> str: return hashlib.sha256(_normalize_comment_body(value).encode("utf-8")).hexdigest() +def _write_transition_notice_marker_cutover(bot) -> None: + config_dir = Path(bot.get_config_value("OPENCODE_CONFIG_DIR") or "") + if not config_dir: + return + artifact_path = config_dir / "reviewer-bot" / "maintainability-remediation" / "transition-notice-marker-cutover.json" + artifact_path.parent.mkdir(parents=True, exist_ok=True) + artifact_path.write_text( + json.dumps( + { + "artifact_id": "transition-notice-marker-cutover", + "completed_at": bot.clock.now().isoformat(), + "state_issue_number": bot.state_issue_number(), + }, + indent=2, + ) + "\n", + encoding="utf-8", + ) + + def handle_transition_notice(bot, state: dict, issue_number: int, reviewer: str) -> bool: - review_data = bot.ensure_review_entry(state, issue_number, create=True) + review_data = ensure_review_entry(state, issue_number, create=True) if review_data is None: return False if review_data.get("transition_notice_sent_at"): return False - notice_message = f"""🔔 **Transition Period Ended** + notice_message = f""" + +🔔 **Transition Period Ended** @{reviewer}, the {bot.TRANSITION_PERIOD_DAYS}-day transition period has passed without activity on this review. @@ -45,9 +93,10 @@ def handle_transition_notice(bot, state: dict, issue_number: int, reviewer: str) You may still continue this review, or use `{bot.BOT_MENTION} /pass`, `{bot.BOT_MENTION} /release`, or `{bot.BOT_MENTION} /away` if you need to step back. _If you believe this is in error or have extenuating circumstances, please reach out to the subcommittee._""" - if not bot.post_comment(issue_number, notice_message): + if not bot.github.post_comment(issue_number, notice_message): return False - bot.reviews_module.record_transition_notice_sent( + _write_transition_notice_marker_cutover(bot) + record_transition_notice_sent( review_data, bot.datetime.now(bot.timezone.utc).isoformat(), ) @@ -56,7 +105,10 @@ def handle_transition_notice(bot, state: dict, issue_number: int, reviewer: str) def handle_issue_or_pr_opened(bot, state: dict) -> bool: bot.assert_lock_held("handle_issue_or_pr_opened") - issue_number = int(os.environ.get("ISSUE_NUMBER", 0)) + from .event_inputs import build_issue_lifecycle_request + + request = build_issue_lifecycle_request(bot) + issue_number = request.issue_number if not issue_number: return False bot.collect_touched_item(issue_number) @@ -68,61 +120,69 @@ def handle_issue_or_pr_opened(bot, state: dict) -> bool: tracked_reviewer = review_data.get("current_reviewer") if tracked_reviewer: return False - current_assignees = bot.get_issue_assignees(issue_number) + current_assignees = bot.github.get_issue_assignees(issue_number) + if current_assignees is None: + raise RuntimeError(f"Unable to determine assignees for #{issue_number}") if current_assignees: return False - labels_json = os.environ.get("ISSUE_LABELS", "[]") - try: - labels = json.loads(labels_json) - except json.JSONDecodeError: - labels = [] + labels = list(request.issue_labels) if not any(label in bot.REVIEW_LABELS for label in labels): return False - issue_author = os.environ.get("ISSUE_AUTHOR", "") - reviewer = bot.get_next_reviewer(state, skip_usernames={issue_author} if issue_author else set()) + issue_author = request.issue_author + reviewer = bot.adapters.queue.get_next_reviewer(state, skip_usernames={issue_author} if issue_author else set()) if not reviewer: - bot.post_comment(issue_number, f"⚠️ No reviewers available in the queue. Please use `{bot.BOT_MENTION} /sync-members` to update the queue.") + bot.github.post_comment(issue_number, f"⚠️ No reviewers available in the queue. Please use `{bot.BOT_MENTION} /sync-members` to update the queue.") return False - is_pr = _is_pr_event() - assignment_attempt = bot.request_reviewer_assignment(issue_number, reviewer) - bot.set_current_reviewer(state, issue_number, reviewer) - review_data = bot.ensure_review_entry(state, issue_number, create=True) + is_pr = request.is_pull_request + if is_pr: + assignment_attempt = bot.github.request_pr_reviewer_assignment(issue_number, reviewer) + else: + assignment_attempt = bot.github.assign_issue_assignee(issue_number, reviewer) + set_current_reviewer(state, issue_number, reviewer) + review_data = ensure_review_entry(state, issue_number, create=True) if is_pr and isinstance(review_data, dict): - head_sha = os.environ.get("PR_HEAD_SHA", "").strip() + head_sha = request.pr_head_sha if head_sha: review_data["active_head_sha"] = head_sha - bot.record_assignment(state, reviewer, issue_number, "pr" if is_pr else "issue") - failure_comment = bot.get_assignment_failure_comment(reviewer, assignment_attempt) + bot.adapters.queue.record_assignment(state, reviewer, issue_number, "pr" if is_pr else "issue") + failure_comment = get_assignment_failure_comment( + reviewer, + assignment_attempt, + is_pull_request=is_pr, + ) if failure_comment: - bot.post_comment(issue_number, failure_comment) + bot.github.post_comment(issue_number, failure_comment) if is_pr and assignment_attempt.success: - bot.post_comment(issue_number, get_pr_guidance(reviewer, issue_author)) + bot.github.post_comment(issue_number, get_pr_guidance(reviewer, issue_author)) if not is_pr: guidance = get_fls_audit_guidance(reviewer, issue_author) if bot.FLS_AUDIT_LABEL in labels else get_issue_guidance(reviewer, issue_author) - bot.post_comment(issue_number, guidance) + bot.github.post_comment(issue_number, guidance) return True def handle_issue_edited_event(bot, state: dict) -> bool: bot.assert_lock_held("handle_issue_edited_event") - if _is_pr_event(): + from .event_inputs import build_issue_lifecycle_request + + request = build_issue_lifecycle_request(bot) + if request.is_pull_request: return False - issue_number = int(os.environ.get("ISSUE_NUMBER", 0)) + issue_number = request.issue_number if not issue_number: return False bot.collect_touched_item(issue_number) - issue_author = os.environ.get("ISSUE_AUTHOR", "").strip() - editor = os.environ.get("SENDER_LOGIN", "").strip() or issue_author + issue_author = request.issue_author + editor = request.sender_login or issue_author if not issue_author or editor.lower() != issue_author.lower(): return False - review_data = bot.ensure_review_entry(state, issue_number) + review_data = ensure_review_entry(state, issue_number) if review_data is None: return False - updated_at = os.environ.get("ISSUE_UPDATED_AT", "").strip() or _now_iso() - current_title = os.environ.get("ISSUE_TITLE", "") - current_body = os.environ.get("ISSUE_BODY", "") - previous_title = os.environ.get("ISSUE_CHANGES_TITLE_FROM", "") - previous_body = os.environ.get("ISSUE_CHANGES_BODY_FROM", "") + updated_at = request.updated_at or _now_iso() + current_title = request.issue_title + current_body = request.issue_body + previous_title = request.previous_title + previous_body = request.previous_body title_changed = _normalize_comment_body(current_title) != _normalize_comment_body(previous_title) body_changed = _normalize_comment_body(current_body) != _normalize_comment_body(previous_body) if not title_changed and not body_changed: @@ -133,7 +193,7 @@ def handle_issue_edited_event(bot, state: dict) -> bool: semantic_key = f"issues_edit_title:{issue_number}:{_semantic_digest(current_title)}" else: semantic_key = f"issues_edit_body:{issue_number}:{_semantic_digest(current_body)}" - return bot.reviews_module.accept_channel_event( + return accept_channel_event( review_data, "contributor_comment", semantic_key=semantic_key, @@ -145,18 +205,21 @@ def handle_issue_edited_event(bot, state: dict) -> bool: def handle_labeled_event(bot, state: dict) -> bool: bot.assert_lock_held("handle_labeled_event") - issue_number = int(os.environ.get("ISSUE_NUMBER", 0)) + from .event_inputs import build_label_event_request + + request = build_label_event_request(bot) + issue_number = request.issue_number if not issue_number: return False - label_name = os.environ.get("LABEL_NAME", "") - is_pr = _is_pr_event() + label_name = request.label_name + is_pr = request.is_pull_request bot.collect_touched_item(issue_number) if label_name == "sign-off: create pr": if is_pr: return False - review_data = bot.ensure_review_entry(state, issue_number) + review_data = ensure_review_entry(state, issue_number) reviewer = review_data.get("current_reviewer") if review_data else None - return bot.mark_review_complete(state, issue_number, reviewer, "issue_label: sign-off: create pr") + return mark_review_complete(state, issue_number, reviewer, "issue_label: sign-off: create pr") if label_name not in bot.REVIEW_LABELS: return False return handle_issue_or_pr_opened(bot, state) @@ -164,22 +227,33 @@ def handle_labeled_event(bot, state: dict) -> bool: def handle_pull_request_target_synchronize(bot, state: dict) -> bool: bot.assert_lock_held("handle_pull_request_target_synchronize") + from .event_inputs import build_pull_request_sync_request + if _runtime_epoch(state) != "freshness_v15": - print("V18 synchronize repair safe-noop before epoch flip") + _log(bot, "info", "V18 synchronize repair safe-noop before epoch flip") return False - issue_number = int(os.environ.get("ISSUE_NUMBER", 0)) + request = build_pull_request_sync_request(bot) + issue_number = request.issue_number if not issue_number: return False - review_data = bot.ensure_review_entry(state, issue_number) + review_data = ensure_review_entry(state, issue_number) if review_data is None or not review_data.get("current_reviewer"): return False - head_sha = os.environ.get("PR_HEAD_SHA", "").strip() + head_sha = request.head_sha if not head_sha: raise RuntimeError("Missing PR_HEAD_SHA for synchronize event") + if not request.event_created_at: + raise RuntimeError("Missing EVENT_CREATED_AT for synchronize event") bot.collect_touched_item(issue_number) + previous_head_sha = review_data.get("active_head_sha") + previous_completion = deepcopy(review_data.get("current_cycle_completion")) + previous_write_approval = deepcopy(review_data.get("current_cycle_write_approval")) + previous_review_completed_at = review_data.get("review_completed_at") + previous_review_completed_by = review_data.get("review_completed_by") + previous_review_completion_source = review_data.get("review_completion_source") review_data["active_head_sha"] = head_sha - timestamp = os.environ.get("EVENT_CREATED_AT", "") or _now_iso() - changed = bot.reviews_module.accept_channel_event( + timestamp = request.event_created_at + changed = accept_channel_event( review_data, "contributor_revision", semantic_key=f"pull_request_sync:{issue_number}:{head_sha}", @@ -187,29 +261,81 @@ def handle_pull_request_target_synchronize(bot, state: dict) -> bool: reviewed_head_sha=head_sha, source_precedence=1, ) - bot.reviews_module.rebuild_pr_approval_state(bot, issue_number, review_data) - return changed + rebuild_pr_approval_state(bot, issue_number, review_data) + approval_changed = ( + previous_completion != review_data.get("current_cycle_completion") + or previous_write_approval != review_data.get("current_cycle_write_approval") + or previous_review_completed_at != review_data.get("review_completed_at") + or previous_review_completed_by != review_data.get("review_completed_by") + or previous_review_completion_source != review_data.get("review_completion_source") + ) + return changed or previous_head_sha != review_data.get("active_head_sha") or approval_changed -def maybe_record_head_observation_repair(bot, issue_number: int, review_data: dict) -> bool: - pull_request = bot.github_api("GET", f"pulls/{issue_number}") +def maybe_record_head_observation_repair(bot, issue_number: int, review_data: dict) -> HeadObservationRepairResult: + try: + response = bot.github_api_request("GET", f"pulls/{issue_number}", retry_policy="idempotent_read") + except SystemExit: + payload = bot.github_api("GET", f"pulls/{issue_number}") + if not isinstance(payload, dict): + return HeadObservationRepairResult( + changed=False, + outcome="skipped_unavailable", + failure_kind="unavailable", + reason="pull_request_unavailable", + ) + response = bot.GitHubApiResult( + status_code=200, + payload=payload, + headers={}, + text="", + ok=True, + failure_kind=None, + retry_attempts=0, + transport_error=None, + ) + if not response.ok: + if response.failure_kind == "not_found": + return HeadObservationRepairResult( + changed=False, + outcome="skipped_not_found", + failure_kind=response.failure_kind, + reason=f"pull_request_{response.failure_kind}", + ) + return HeadObservationRepairResult( + changed=False, + outcome="skipped_unavailable", + failure_kind=response.failure_kind, + reason="pull_request_unavailable", + ) + pull_request = response.payload if not isinstance(pull_request, dict): - raise RuntimeError(f"Failed to fetch live PR #{issue_number} for head observation repair") + return HeadObservationRepairResult( + changed=False, + outcome="invalid_live_payload", + failure_kind="invalid_payload", + reason="pull_request_payload_invalid", + ) if str(pull_request.get("state", "")).lower() != "open": - return False + return HeadObservationRepairResult(changed=False, outcome="skipped_not_open") head = pull_request.get("head") head_sha = head.get("sha") if isinstance(head, dict) else None if not isinstance(head_sha, str) or not head_sha.strip(): - raise RuntimeError(f"Pull request #{issue_number} is missing a usable head SHA") + return HeadObservationRepairResult( + changed=False, + outcome="invalid_live_payload", + failure_kind="invalid_payload", + reason="pull_request_head_unavailable", + ) head_sha = head_sha.strip() current_head = review_data.get("active_head_sha") if current_head == head_sha: - return False + return HeadObservationRepairResult(changed=False, outcome="unchanged") contributor_revision = review_data.get("contributor_revision", {}).get("accepted") if isinstance(contributor_revision, dict) and contributor_revision.get("reviewed_head_sha") == head_sha: review_data["active_head_sha"] = head_sha - return False - changed = bot.reviews_module.accept_channel_event( + return HeadObservationRepairResult(changed=True, outcome="changed") + changed = accept_channel_event( review_data, "contributor_revision", semantic_key=f"pull_request_head_observed:{issue_number}:{head_sha}", @@ -223,12 +349,15 @@ def maybe_record_head_observation_repair(bot, issue_number: int, review_data: di review_data["review_completed_at"] = None review_data["review_completed_by"] = None review_data["review_completion_source"] = None - return changed + return HeadObservationRepairResult(changed=changed, outcome="changed" if changed else "unchanged") def handle_closed_event(bot, state: dict) -> bool: bot.assert_lock_held("handle_closed_event") - issue_number = int(os.environ.get("ISSUE_NUMBER", 0)) + from .event_inputs import build_issue_lifecycle_request + + request = build_issue_lifecycle_request(bot) + issue_number = request.issue_number if not issue_number: return False bot.collect_touched_item(issue_number) diff --git a/scripts/reviewer_bot_lib/lock_codec.py b/scripts/reviewer_bot_lib/lock_codec.py new file mode 100644 index 000000000..eb255267c --- /dev/null +++ b/scripts/reviewer_bot_lib/lock_codec.py @@ -0,0 +1,43 @@ +"""Lock metadata codec helpers for reviewer-bot state and lock documents.""" + +from __future__ import annotations + +import json +from typing import Any + +from .config import LOCK_COMMIT_MARKER, LOCK_METADATA_KEYS, LOCK_SCHEMA_VERSION + + +def normalize_lock_metadata(lock_meta: dict | None) -> dict: + normalized: dict[str, Any] = dict.fromkeys(LOCK_METADATA_KEYS) + normalized["schema_version"] = LOCK_SCHEMA_VERSION + + if not isinstance(lock_meta, dict): + return normalized + + for key in LOCK_METADATA_KEYS: + if key == "schema_version": + schema_value = lock_meta.get("schema_version") + if isinstance(schema_value, int): + normalized["schema_version"] = schema_value + continue + if key in lock_meta: + normalized[key] = lock_meta.get(key) + + return normalized +def render_lock_commit_message(lock_meta: dict) -> str: + lock_json = json.dumps(normalize_lock_metadata(lock_meta), sort_keys=False) + return f"{LOCK_COMMIT_MARKER}\n{lock_json}" + + +def parse_lock_commit_message(message: str) -> dict: + if not message.startswith(f"{LOCK_COMMIT_MARKER}\n"): + raise RuntimeError("invalid reviewer-bot lock commit message") + lock_json = message.split("\n", 1)[1] + try: + parsed = json.loads(lock_json) + except json.JSONDecodeError: + raise RuntimeError("invalid reviewer-bot lock commit message") + if not isinstance(parsed, dict): + raise RuntimeError("invalid reviewer-bot lock commit message") + return normalize_lock_metadata(parsed) diff --git a/scripts/reviewer_bot_lib/maintenance.py b/scripts/reviewer_bot_lib/maintenance.py index 4f7e2dd1e..771d2afd1 100644 --- a/scripts/reviewer_bot_lib/maintenance.py +++ b/scripts/reviewer_bot_lib/maintenance.py @@ -1,28 +1,29 @@ -"""Maintenance and operator-dispatch handlers for reviewer-bot.""" +"""Manual maintenance and operator-dispatch handlers for reviewer-bot.""" from __future__ import annotations -import json -import os - import yaml -from .lifecycle import maybe_record_head_observation_repair -from .overdue import ( - backfill_transition_notice_if_present, - check_overdue_reviews, - handle_overdue_review_warning, -) +from . import maintenance_privileged, maintenance_schedule, reviews +from .event_inputs import build_manual_dispatch_request from .project_board import ( format_preview_for_output, preview_board_projection_for_item, reviewer_board_preflight, ) -from .sweeper import sweep_deferred_gaps - -def _now_iso(bot) -> str: - return bot.datetime.now(bot.timezone.utc).isoformat() +ScheduleHandlerResult = maintenance_schedule.ScheduleHandlerResult +_now_iso = maintenance_privileged._now_iso +_finalize_schedule_result = maintenance_schedule._finalize_schedule_result +_record_maintenance_repair_marker = maintenance_schedule._record_maintenance_repair_marker +_run_tracked_pr_repairs = maintenance_schedule._run_tracked_pr_repairs +repair_missing_reviewer_review_state = maintenance_schedule.repair_missing_reviewer_review_state +maybe_record_head_observation_repair = maintenance_schedule.maybe_record_head_observation_repair +check_overdue_reviews = maintenance_schedule.check_overdue_reviews +handle_overdue_review_warning = maintenance_schedule.handle_overdue_review_warning +backfill_transition_notice_if_present = maintenance_schedule.backfill_transition_notice_if_present +handle_transition_notice = maintenance_schedule.handle_transition_notice +sweep_deferred_gaps = maintenance_schedule.sweep_deferred_gaps def status_projection_repair_needed(bot, state: dict) -> bool: @@ -31,13 +32,12 @@ def status_projection_repair_needed(bot, state: dict) -> bool: def collect_status_projection_repair_items(bot, state: dict) -> list[int]: - numbers = set(bot.list_open_items_with_status_labels()) - numbers.update(bot.reviews_module.list_open_tracked_review_items(state)) - return sorted(number for number in numbers if isinstance(number, int) and number > 0) + return maintenance_schedule.collect_status_projection_repair_items(bot, state) def handle_manual_dispatch(bot, state: dict) -> bool: - action = os.environ.get("MANUAL_ACTION", "") + request = build_manual_dispatch_request(bot) + action = request.action if action == "show-state": print(f"Current state:\n{yaml.dump(state, default_flow_style=False)}") return False @@ -51,10 +51,9 @@ def handle_manual_dispatch(bot, state: dict) -> bool: "Reviewer board preview preflight failed: " + "; ".join(preflight.errors) ) - issue_number_raw = os.environ.get("ISSUE_NUMBER", "").strip() issue_numbers: list[int] = [] - if issue_number_raw: - issue_numbers = [int(issue_number_raw)] + if request.issue_number: + issue_numbers = [request.issue_number] else: active_reviews = state.get("active_reviews") if isinstance(active_reviews, dict): @@ -81,95 +80,21 @@ def handle_manual_dispatch(bot, state: dict) -> bool: return False bot.assert_lock_held("handle_manual_dispatch") if action == "sync-members": - _, changes = bot.sync_members_with_queue(state) + _, changes = bot.adapters.workflow.sync_members_with_queue(state) return bool(changes) if action == "repair-review-status-labels": - for issue_number in bot.list_open_items_with_status_labels(): + for issue_number in reviews.list_open_items_with_status_labels(bot): bot.collect_touched_item(issue_number) return False if action == "check-overdue": - return bot.handle_scheduled_check(state) + return maintenance_schedule.handle_scheduled_check_result(bot, state).state_changed if action == "execute-pending-privileged-command": - source_event_key = os.environ.get("PRIVILEGED_SOURCE_EVENT_KEY", "").strip() + source_event_key = request.privileged_source_event_key if not source_event_key: raise RuntimeError("Missing PRIVILEGED_SOURCE_EVENT_KEY for privileged command execution") - for issue_key, review_data in (state.get("active_reviews") or {}).items(): - if not isinstance(review_data, dict): - continue - pending = review_data.get("pending_privileged_commands") - if not isinstance(pending, dict) or source_event_key not in pending: - continue - record = pending[source_event_key] - if not isinstance(record, dict): - raise RuntimeError("Pending privileged command record is malformed") - if record.get("status") != "pending": - return False - issue_number = int(record.get("issue_number") or int(issue_key)) - actor = str(record.get("actor", "")).strip() - command_name = record.get("command_name") - if command_name != "accept-no-fls-changes": - record["status"] = "failed_closed" - record["completed_at"] = _now_iso(bot) - record["result"] = "unsupported_command" - return True - issue_snapshot = bot.get_issue_or_pr_snapshot(issue_number) - if not isinstance(issue_snapshot, dict) or isinstance(issue_snapshot.get("pull_request"), dict): - record["status"] = "failed_closed" - record["completed_at"] = _now_iso(bot) - record["result"] = "live_target_invalid" - return True - labels: set[str] = set() - for label in issue_snapshot.get("labels", []): - if not isinstance(label, dict): - continue - name = label.get("name") - if isinstance(name, str): - labels.add(name) - if bot.FLS_AUDIT_LABEL not in labels or not bot.check_user_permission(actor, "triage"): - record["status"] = "failed_closed" - record["completed_at"] = _now_iso(bot) - record["result"] = "live_revalidation_failed" - return True - os.environ["ISSUE_LABELS"] = json.dumps(sorted(labels)) - message, success = bot.handle_accept_no_fls_changes_command(issue_number, actor) - record["completed_at"] = _now_iso(bot) - record["result_message"] = message - record["status"] = "executed" if success else "failed_closed" - return True - raise RuntimeError(f"Pending privileged command not found for {source_event_key}") + return maintenance_privileged.execute_pending_privileged_command(bot, state, source_event_key) return False -def handle_scheduled_check(bot, state: dict) -> bool: - bot.assert_lock_held("handle_scheduled_check") - changed = sweep_deferred_gaps(bot, state) - active_reviews = state.get("active_reviews") - if isinstance(active_reviews, dict): - for issue_key, review_data in active_reviews.items(): - if not isinstance(review_data, dict) or not review_data.get("current_reviewer"): - continue - issue_number = int(issue_key) - issue_snapshot = bot.get_issue_or_pr_snapshot(issue_number) - if not isinstance(issue_snapshot, dict) or not isinstance(issue_snapshot.get("pull_request"), dict): - continue - if bot.reviews_module.repair_missing_reviewer_review_state(bot, issue_number, review_data): - changed = True - bot.collect_touched_item(issue_number) - if maybe_record_head_observation_repair(bot, issue_number, review_data): - changed = True - bot.collect_touched_item(issue_number) - overdue_reviews = check_overdue_reviews(bot, state) - if not overdue_reviews: - return changed - for review in overdue_reviews: - issue_number = review["issue_number"] - reviewer = review["reviewer"] - if review["needs_warning"]: - if handle_overdue_review_warning(bot, state, issue_number, reviewer): - changed = True - elif review["needs_transition"]: - if backfill_transition_notice_if_present(bot, state, issue_number): - changed = True - elif bot.handle_transition_notice(state, issue_number, reviewer): - changed = True - return changed +def handle_scheduled_check_result(bot, state: dict) -> ScheduleHandlerResult: + return maintenance_schedule.handle_scheduled_check_result(bot, state) diff --git a/scripts/reviewer_bot_lib/maintenance_privileged.py b/scripts/reviewer_bot_lib/maintenance_privileged.py new file mode 100644 index 000000000..cbbdcdf42 --- /dev/null +++ b/scripts/reviewer_bot_lib/maintenance_privileged.py @@ -0,0 +1,70 @@ +"""Pending privileged maintenance seam for reviewer-bot.""" + +from __future__ import annotations + +from scripts.reviewer_bot_core import privileged_command_policy + +from . import automation + + +def _now_iso(bot) -> str: + return bot.clock.now().isoformat() + + +def execute_pending_privileged_command(bot, state: dict, source_event_key: str) -> bool: + pending = privileged_command_policy.find_pending_accept_no_fls_changes(state, source_event_key) + if pending is None: + raise RuntimeError(f"Pending privileged command not found for {source_event_key}") + issue_number, review_data, record = pending + if record.status != "pending": + return False + actor = record.actor.strip() + if record.command_name != privileged_command_policy.PrivilegedCommandId.ACCEPT_NO_FLS_CHANGES.value: + privileged_command_policy.mark_pending_accept_no_fls_changes_failed_closed( + review_data, + source_event_key, + completed_at=_now_iso(bot), + result="unsupported_command", + ) + return True + issue_snapshot = bot.github.get_issue_or_pr_snapshot(issue_number) + permission_status = bot.github.get_user_permission_status(actor, "triage") + revalidation = privileged_command_policy.revalidate_pending_accept_no_fls_changes( + record, + issue_snapshot, + permission_status, + target_repo_root=str(automation.get_target_repo_root(bot)), + ) + if isinstance(revalidation, privileged_command_policy.BlockedPrivilegedExecution): + privileged_command_policy.mark_pending_accept_no_fls_changes_failed_closed( + review_data, + source_event_key, + completed_at=_now_iso(bot), + result=revalidation.result_code, + result_message=revalidation.result_message, + ) + return True + execution = automation.handle_accept_no_fls_changes_command( + bot, + issue_number, + actor, + execution_plan=revalidation, + ) + if execution.status == "executed": + privileged_command_policy.mark_pending_accept_no_fls_changes_executed( + review_data, + source_event_key, + completed_at=_now_iso(bot), + result=execution.result_code, + result_message=execution.result_message or "", + opened_pr_url=execution.opened_pr_url, + ) + return True + privileged_command_policy.mark_pending_accept_no_fls_changes_failed_closed( + review_data, + source_event_key, + completed_at=_now_iso(bot), + result=execution.result_code, + result_message=execution.result_message, + ) + return True diff --git a/scripts/reviewer_bot_lib/maintenance_schedule.py b/scripts/reviewer_bot_lib/maintenance_schedule.py new file mode 100644 index 000000000..bcdbc97dd --- /dev/null +++ b/scripts/reviewer_bot_lib/maintenance_schedule.py @@ -0,0 +1,156 @@ +"""Scheduled maintenance seam for reviewer-bot.""" + +from __future__ import annotations + +from dataclasses import dataclass + +from .lifecycle import handle_transition_notice, maybe_record_head_observation_repair +from .overdue import ( + backfill_transition_notice_if_present, + check_overdue_reviews, + handle_overdue_review_warning, +) +from .repair_records import ( + clear_repair_marker, + load_repair_marker, + maintenance_repair_marker, + store_repair_marker, +) +from .review_state import ( + list_open_tracked_review_items, + repair_missing_reviewer_review_state, +) +from .sweeper import sweep_deferred_gaps + + +@dataclass(frozen=True) +class ScheduleHandlerResult: + state_changed: bool + touched_items: list[int] + + +def _log(bot, level: str, message: str, **fields) -> None: + bot.logger.event(level, message, **fields) + + +def _now_iso(bot) -> str: + return bot.clock.now().isoformat() + + +def _clear_maintenance_repair_marker(review_data: dict, phase: str) -> bool: + marker = load_repair_marker(review_data, phase) + if not isinstance(marker, dict): + return False + if marker.get("kind") != "live_read_failure": + return False + return clear_repair_marker(review_data, phase) + + +def _repair_marker_matches(existing: dict | None, candidate: dict) -> bool: + if not isinstance(existing, dict): + return False + return {key: value for key, value in existing.items() if key != "recorded_at"} == { + key: value for key, value in candidate.items() if key != "recorded_at" + } + + +def _record_maintenance_repair_marker(bot, review_data: dict, *, phase: str, reason: str, failure_kind: str | None) -> bool: + marker = maintenance_repair_marker( + reason=reason, + failure_kind=failure_kind, + recorded_at=_now_iso(bot), + ) + existing_marker = load_repair_marker(review_data, phase) + if _repair_marker_matches(existing_marker, marker): + return False + return store_repair_marker(review_data, phase, marker) + + +def _run_deferred_gap_sweep(bot, state: dict) -> bool: + return sweep_deferred_gaps(bot, state) + + +def _run_tracked_pr_repair(bot, issue_number: int, review_data: dict) -> bool: + changed = False + try: + if repair_missing_reviewer_review_state(bot, issue_number, review_data): + changed = True + bot.collect_touched_item(issue_number) + if _clear_maintenance_repair_marker(review_data, "review_repair"): + changed = True + except Exception as exc: + _log(bot, "warning", f"Scheduled repair failed for #{issue_number} during review_repair: {exc}", issue_number=issue_number, phase="review_repair", error=str(exc)) + if _record_maintenance_repair_marker(bot, review_data, phase="review_repair", reason=str(exc), failure_kind=None): + changed = True + return changed + + try: + repair_result = maybe_record_head_observation_repair(bot, issue_number, review_data) + except Exception as exc: + _log(bot, "warning", f"Scheduled repair failed for #{issue_number} during head_observation_repair: {exc}", issue_number=issue_number, phase="head_observation_repair", error=str(exc)) + if _record_maintenance_repair_marker(bot, review_data, phase="head_observation_repair", reason=str(exc), failure_kind=None): + changed = True + return changed + + if repair_result.changed: + changed = True + bot.collect_touched_item(issue_number) + if repair_result.outcome in {"skipped_unavailable", "skipped_not_found", "invalid_live_payload"}: + if _record_maintenance_repair_marker(bot, review_data, phase="head_observation_repair", reason=repair_result.reason or repair_result.outcome, failure_kind=repair_result.failure_kind): + changed = True + elif _clear_maintenance_repair_marker(review_data, "head_observation_repair"): + changed = True + return changed + + +def _run_tracked_pr_repairs(bot, state: dict) -> bool: + changed = False + active_reviews = state.get("active_reviews") + if not isinstance(active_reviews, dict): + return False + for issue_key, review_data in active_reviews.items(): + if not isinstance(review_data, dict) or not review_data.get("current_reviewer"): + continue + issue_number = int(issue_key) + issue_snapshot = bot.github.get_issue_or_pr_snapshot(issue_number) + if not isinstance(issue_snapshot, dict) or not isinstance(issue_snapshot.get("pull_request"), dict): + continue + changed = _run_tracked_pr_repair(bot, issue_number, review_data) or changed + return changed + + +def _run_overdue_pass(bot, state: dict) -> bool: + changed = False + overdue_reviews = check_overdue_reviews(bot, state) + for review in overdue_reviews: + issue_number = review["issue_number"] + reviewer = review["reviewer"] + if review["needs_warning"]: + if handle_overdue_review_warning(bot, state, issue_number, reviewer): + changed = True + elif review["needs_transition"]: + if backfill_transition_notice_if_present(bot, state, issue_number): + changed = True + elif handle_transition_notice(bot, state, issue_number, reviewer): + changed = True + return changed + + +def _finalize_schedule_result(bot, state_changed: bool) -> ScheduleHandlerResult: + return ScheduleHandlerResult(state_changed=state_changed, touched_items=bot.drain_touched_items()) + + +def handle_scheduled_check_result(bot, state: dict) -> ScheduleHandlerResult: + bot.assert_lock_held("handle_scheduled_check_result") + changed = _run_deferred_gap_sweep(bot, state) + changed = _run_tracked_pr_repairs(bot, state) or changed + changed = _run_overdue_pass(bot, state) or changed + return _finalize_schedule_result(bot, changed) + + +def collect_status_projection_repair_items(bot, state: dict) -> list[int]: + from . import reviews + + numbers = set(reviews.list_open_items_with_status_labels(bot)) + numbers.update(list_open_tracked_review_items(state)) + return sorted(number for number in numbers if isinstance(number, int) and number > 0) diff --git a/scripts/reviewer_bot_lib/members.py b/scripts/reviewer_bot_lib/members.py index d9f0e61bf..2fd86a15b 100644 --- a/scripts/reviewer_bot_lib/members.py +++ b/scripts/reviewer_bot_lib/members.py @@ -1,23 +1,24 @@ """Reviewer queue membership helpers.""" -import sys +from .config import MEMBERS_URL, MemberFetchResult -import requests -from .config import MEMBERS_URL - - -def fetch_members() -> list[dict]: +def fetch_members(bot) -> MemberFetchResult: """Fetch and parse members.md from the consortium repo to extract Producers.""" try: - response = requests.get(MEMBERS_URL, timeout=10) - response.raise_for_status() - content = response.text - except requests.RequestException as exc: - print(f"WARNING: Failed to fetch members file from {MEMBERS_URL}: {exc}", file=sys.stderr) - return [] - - producers = [] + response = bot.rest_transport.request("GET", MEMBERS_URL, timeout_seconds=10) + except Exception: + return MemberFetchResult(ok=False, producers=[], failure_kind="transport_error") + + status_code = getattr(response, "status_code", 0) + if status_code >= 400: + return MemberFetchResult(ok=False, producers=[], failure_kind="http_error") + + content = getattr(response, "text", None) + if not isinstance(content, str): + return MemberFetchResult(ok=False, producers=[], failure_kind="invalid_payload") + + producers: list[dict[str, str]] = [] lines = content.split("\n") in_table = False headers = [] @@ -54,4 +55,4 @@ def fetch_members() -> list[dict]: } ) - return producers + return MemberFetchResult(ok=True, producers=producers) diff --git a/scripts/reviewer_bot_lib/overdue.py b/scripts/reviewer_bot_lib/overdue.py index 4e2ecd0d3..959f070f6 100644 --- a/scripts/reviewer_bot_lib/overdue.py +++ b/scripts/reviewer_bot_lib/overdue.py @@ -2,6 +2,15 @@ from __future__ import annotations +from .config import TRANSITION_NOTICE_MARKER_PREFIX + +_TRANSITION_NOTICE_FALLBACK_FIRST_LINE = "🔔 **Transition Period Ended**" +_TRANSITION_NOTICE_AUTHORS = {"github-actions[bot]", "guidelines-bot"} + + +def _log(bot, level: str, message: str, **fields) -> None: + bot.logger.event(level, message, **fields) + def check_overdue_reviews(bot, state: dict) -> list[dict]: """Check all active reviews for overdue ones.""" @@ -18,15 +27,20 @@ def check_overdue_reviews(bot, state: dict) -> list[dict]: if review_data.get("review_completed_at"): continue + if review_data.get("transition_notice_sent_at"): + continue + current_reviewer = review_data.get("current_reviewer") if not current_reviewer: continue issue_number = int(issue_key) - issue_snapshot = bot.get_issue_or_pr_snapshot(issue_number) - if isinstance(issue_snapshot, dict) and isinstance(issue_snapshot.get("pull_request"), dict): - response_state = bot.reviews_module.compute_reviewer_response_state( - bot, + issue_snapshot = bot.github.get_issue_or_pr_snapshot(issue_number) + if not isinstance(issue_snapshot, dict): + _log(bot, "warning", f"Skipping overdue evaluation for #{issue_number}; issue/PR snapshot unavailable", issue_number=issue_number) + continue + if isinstance(issue_snapshot.get("pull_request"), dict): + response_state = bot.adapters.review_state.compute_reviewer_response_state( issue_number, review_data, issue_snapshot=issue_snapshot, @@ -53,11 +67,6 @@ def check_overdue_reviews(bot, state: dict) -> list[dict]: continue transition_warning_sent = review_data.get("transition_warning_sent") - transition_notice_sent_at = review_data.get("transition_notice_sent_at") - - if transition_notice_sent_at: - continue - if transition_warning_sent: try: warning_dt = bot.datetime.fromisoformat(transition_warning_sent.replace("Z", "+00:00")) @@ -91,39 +100,45 @@ def check_overdue_reviews(bot, state: dict) -> list[dict]: return overdue -def find_existing_transition_notice(bot, issue_number: int, transition_warning_sent: str | None) -> str | None: +def find_existing_transition_notice(bot, issue_number: int, transition_warning_sent: str | None, reviewer: str | None = None) -> str | None: if not isinstance(transition_warning_sent, str) or not transition_warning_sent: return None try: warning_dt = bot.datetime.fromisoformat(transition_warning_sent.replace("Z", "+00:00")) except (ValueError, AttributeError): return None - response = bot.github_api("GET", f"issues/{issue_number}/comments?per_page=100") - if not isinstance(response, list): - return None first_match = None - for comment in response: - if not isinstance(comment, dict): - continue - user = comment.get("user") - login = user.get("login") if isinstance(user, dict) else None - created_at = comment.get("created_at") - body = comment.get("body") - if not isinstance(login, str) or not isinstance(created_at, str) or not isinstance(body, str): - continue - if login != "github-actions[bot]": - continue - first_line = body.splitlines()[0].strip() if body.splitlines() else "" - if first_line != "🔔 **Transition Period Ended**": - continue - try: - created_dt = bot.datetime.fromisoformat(created_at.replace("Z", "+00:00")) - except (ValueError, AttributeError): - continue - if created_dt < warning_dt: - continue - if first_match is None or created_dt < first_match[0]: - first_match = (created_dt, created_at) + marker = f"" + page = 1 + while True: + response = bot.github_api("GET", f"issues/{issue_number}/comments?per_page=100&page={page}") + if not isinstance(response, list): + return None + for comment in response: + if not isinstance(comment, dict): + continue + user = comment.get("user") + login = user.get("login") if isinstance(user, dict) else None + created_at = comment.get("created_at") + body = comment.get("body") + if not isinstance(login, str) or not isinstance(created_at, str) or not isinstance(body, str): + continue + if login not in _TRANSITION_NOTICE_AUTHORS: + continue + try: + created_dt = bot.datetime.fromisoformat(created_at.replace("Z", "+00:00")) + except (ValueError, AttributeError): + continue + if created_dt < warning_dt: + continue + lines = body.splitlines() + first_line = lines[0].strip() if lines else "" + if first_line == marker or first_line == _TRANSITION_NOTICE_FALLBACK_FIRST_LINE: + if first_match is None or created_dt < first_match[0]: + first_match = (created_dt, created_at) + if len(response) < 100: + break + page += 1 return first_match[1] if first_match else None @@ -137,7 +152,12 @@ def backfill_transition_notice_if_present(bot, state: dict, issue_number: int) - return False if review_data.get("transition_notice_sent_at"): return False - existing_notice = find_existing_transition_notice(bot, issue_number, review_data.get("transition_warning_sent")) + existing_notice = find_existing_transition_notice( + bot, + issue_number, + review_data.get("transition_warning_sent"), + review_data.get("current_reviewer"), + ) if not existing_notice: return False review_data["transition_notice_sent_at"] = existing_notice @@ -169,10 +189,11 @@ def handle_overdue_review_warning(bot, state: dict, issue_number: int, reviewer: _Life happens! If you're dealing with something, just let us know._""" - bot.post_comment(issue_number, warning_message) + if not bot.github.post_comment(issue_number, warning_message): + return False now = bot.datetime.now(bot.timezone.utc).isoformat() review_data["transition_warning_sent"] = now - print(f"Posted overdue warning for #{issue_number} to @{reviewer}") + _log(bot, "info", f"Posted overdue warning for #{issue_number} to @{reviewer}", issue_number=issue_number, reviewer=reviewer) return True diff --git a/scripts/reviewer_bot_lib/project_board.py b/scripts/reviewer_bot_lib/project_board.py index 3b21aafd2..9f27fed83 100644 --- a/scripts/reviewer_bot_lib/project_board.py +++ b/scripts/reviewer_bot_lib/project_board.py @@ -3,7 +3,6 @@ from __future__ import annotations import copy -import os from dataclasses import asdict, dataclass from typing import Any @@ -15,6 +14,7 @@ REVIEWER_BOARD_FIELD_REVIEWER, REVIEWER_BOARD_FIELD_WAITING_SINCE, REVIEWER_BOARD_OPTION_ATTENTION_NO, + REVIEWER_BOARD_OPTION_ATTENTION_PROJECTION_REPAIR_REQUIRED, REVIEWER_BOARD_OPTION_ATTENTION_TRANSITION_NOTICE_SENT, REVIEWER_BOARD_OPTION_ATTENTION_TRIAGE_APPROVAL_REQUIRED, REVIEWER_BOARD_OPTION_ATTENTION_WARNING_SENT, @@ -27,6 +27,11 @@ REVIEWER_BOARD_PROJECT_MANIFEST, REVIEWER_BOARD_PROJECT_NUMBER, ) +from .repair_records import load_repair_marker +from .runtime_protocols import ( + ProjectBoardMetadataContext, + ProjectBoardProjectionContext, +) PROJECT_BOARD_METADATA_QUERY = """ query ReviewerBoardProjectMetadata($organization: String!, $projectNumber: Int!) { @@ -119,9 +124,8 @@ class BoardPreviewResult: noop_reason: str | None -def reviewer_board_enabled(bot) -> bool: - del bot - return os.environ.get(REVIEWER_BOARD_ENABLED_ENV, "false").strip().lower() == "true" +def reviewer_board_enabled(bot: ProjectBoardMetadataContext) -> bool: + return bot.get_config_value(REVIEWER_BOARD_ENABLED_ENV, "false").strip().lower() == "true" def _field_type_name(field_node: dict[str, Any]) -> str: @@ -137,7 +141,7 @@ def _field_type_name(field_node: dict[str, Any]) -> str: return typename.lower() -def resolve_project_metadata(bot) -> ProjectMetadata: +def resolve_project_metadata(bot: ProjectBoardMetadataContext) -> ProjectMetadata: cached = getattr(bot, "_reviewer_board_project_metadata", None) if isinstance(cached, ProjectMetadata): return cached @@ -191,7 +195,7 @@ def resolve_project_metadata(bot) -> ProjectMetadata: return metadata -def validate_project_manifest(bot, metadata: ProjectMetadata) -> tuple[str, ...]: +def validate_project_manifest(bot: ProjectBoardMetadataContext, metadata: ProjectMetadata) -> tuple[str, ...]: del bot errors: list[str] = [] for field_name, expected in REVIEWER_BOARD_PROJECT_MANIFEST.items(): @@ -209,7 +213,7 @@ def validate_project_manifest(bot, metadata: ProjectMetadata) -> tuple[str, ...] return tuple(errors) -def reviewer_board_preflight(bot) -> ProjectBoardPreflight: +def reviewer_board_preflight(bot: ProjectBoardMetadataContext) -> ProjectBoardPreflight: if not reviewer_board_enabled(bot): return ProjectBoardPreflight( enabled=False, @@ -258,9 +262,14 @@ def _format_date(value: Any) -> str | None: return value[:10] -def _derive_review_state(bot, issue_number: int, review_data_snapshot: dict[str, Any], issue_snapshot: dict[str, Any]) -> ReviewStateDerivation: +def _derive_review_state( + bot: ProjectBoardProjectionContext, + issue_number: int, + review_data_snapshot: dict[str, Any], + issue_snapshot: dict[str, Any], +) -> ReviewStateDerivation: preview_review_data = copy.deepcopy(review_data_snapshot) - derived = bot.compute_reviewer_response_state( + derived = bot.adapters.review_state.compute_reviewer_response_state( issue_number, preview_review_data, issue_snapshot=copy.deepcopy(issue_snapshot), @@ -273,9 +282,15 @@ def _derive_review_state(bot, issue_number: int, review_data_snapshot: dict[str, ) -def build_board_projection_input(bot, state: dict, issue_number: int, *, issue_snapshot: dict[str, Any] | None = None) -> BoardProjectionInput: +def build_board_projection_input( + bot: ProjectBoardProjectionContext, + state: dict, + issue_number: int, + *, + issue_snapshot: dict[str, Any] | None = None, +) -> BoardProjectionInput: if issue_snapshot is None: - issue_snapshot = bot.get_issue_or_pr_snapshot(issue_number) + issue_snapshot = bot.github.get_issue_or_pr_snapshot(issue_number) if not isinstance(issue_snapshot, dict): raise RuntimeError(f"Unable to load issue or PR snapshot for #{issue_number}") @@ -339,7 +354,10 @@ def derive_board_projection(input: BoardProjectionInput) -> BoardProjectionValue raise RuntimeError(f"Unsupported board review state for #{input.issue_number}: {derivation.state}") needs_attention = REVIEWER_BOARD_OPTION_ATTENTION_NO - if review_data.get("mandatory_approver_required"): + repair_needed = load_repair_marker(review_data, "status_label_projection") + if isinstance(repair_needed, dict) and repair_needed.get("kind") == "projection_failure": + needs_attention = REVIEWER_BOARD_OPTION_ATTENTION_PROJECTION_REPAIR_REQUIRED + elif review_data.get("mandatory_approver_required"): needs_attention = REVIEWER_BOARD_OPTION_ATTENTION_TRIAGE_APPROVAL_REQUIRED elif review_data.get("transition_notice_sent_at"): needs_attention = REVIEWER_BOARD_OPTION_ATTENTION_TRANSITION_NOTICE_SENT @@ -357,7 +375,7 @@ def derive_board_projection(input: BoardProjectionInput) -> BoardProjectionValue def preview_board_projection_for_item(bot, state: dict, issue_number: int) -> BoardPreviewResult: - issue_snapshot = bot.get_issue_or_pr_snapshot(issue_number) + issue_snapshot = bot.github.get_issue_or_pr_snapshot(issue_number) input = build_board_projection_input(bot, state, issue_number, issue_snapshot=issue_snapshot) desired = derive_board_projection(input) return BoardPreviewResult( diff --git a/scripts/reviewer_bot_lib/queue.py b/scripts/reviewer_bot_lib/queue.py index 5cba44a37..4241d5504 100644 --- a/scripts/reviewer_bot_lib/queue.py +++ b/scripts/reviewer_bot_lib/queue.py @@ -4,9 +4,22 @@ from .config import MAX_RECENT_ASSIGNMENTS +def _log(bot, level: str, message: str, **fields) -> None: + bot.logger.event(level, message, **fields) + + def sync_members_with_queue(bot, state: dict) -> tuple[dict, list[str]]: """Sync the queue with the current members list.""" - producers = bot.fetch_members() + fetch_result = bot.adapters.workflow.fetch_members() + if not fetch_result.ok: + _log( + bot, + "warning", + "Failed to refresh members; keeping existing queue membership unchanged.", + failure_kind=fetch_result.failure_kind, + ) + return state, [] + producers = fetch_result.producers current_queue = {member["github"]: member for member in state["queue"]} pass_until_users = {member["github"] for member in state.get("pass_until", [])} @@ -84,9 +97,13 @@ def process_pass_until_expirations(state: dict) -> tuple[dict, list[str]]: try: return_date = datetime.strptime(return_date, "%Y-%m-%d").date() except ValueError: - return_date = datetime.fromisoformat(return_date).date() + still_away.append(entry) + continue elif isinstance(return_date, datetime): return_date = return_date.date() + else: + still_away.append(entry) + continue if return_date <= now: restored_member = { @@ -143,4 +160,3 @@ def record_assignment( state["recent_assignments"].insert(0, assignment) state["recent_assignments"] = state["recent_assignments"][:max_recent_assignments] - diff --git a/scripts/reviewer_bot_lib/reconcile.py b/scripts/reviewer_bot_lib/reconcile.py index b25759b95..0dec535ee 100644 --- a/scripts/reviewer_bot_lib/reconcile.py +++ b/scripts/reviewer_bot_lib/reconcile.py @@ -2,57 +2,103 @@ from __future__ import annotations -import json -import os - -from .comment_routing import ( - _digest_body, - _handle_command, - _record_conversation_freshness, - classify_comment_payload, +from copy import deepcopy +from dataclasses import dataclass + +from scripts.reviewer_bot_core import reconcile_replay_policy +from scripts.reviewer_bot_core.comment_routing_policy import ( + ObserverCommentClassification, +) + +from . import deferred_gap_bookkeeping as gap_bookkeeping +from . import reconcile_payloads as _reconcile_payloads +from .comment_application import ( + digest_comment_body, + process_comment_event, + record_conversation_freshness, +) +from .comment_routing import classify_comment_payload, classify_issue_comment_actor +from .context import CommentEventRequest +from .event_inputs import ( + InvalidEventInput, + build_event_context, + build_replay_comment_event_request, +) +from .reconcile_payloads import ( + DeferredCommentPayload, + DeferredCommentReplayContext, + DeferredReviewPayload, + ObserverNoopPayload, + _LegacyDeferredIssueCommentPayloadV2, + _LegacyDeferredReviewCommentPayloadV2, + build_deferred_comment_replay_context, + build_deferred_review_replay_context, + parse_deferred_context_payload, +) +from .reconcile_reads import ( + LiveCommentReplayContext, + ReconcileReadError, +) +from .reconcile_reads import ( + read_live_comment_replay_context as _read_live_comment_replay_context, +) +from .reconcile_reads import ( + read_live_pr_replay_context as _read_live_pr_replay_context, +) +from .reconcile_reads import ( + read_optional_reconcile_object as _read_optional_reconcile_object, ) -from .reviews import ( - find_triage_approval_after, +from .reconcile_reads import ( + read_reconcile_object as _read_reconcile_object, +) +from .reconcile_reads import ( + read_reconcile_reviews as _read_reconcile_reviews, +) +from .review_state import ( + accept_channel_event, + ensure_review_entry, + record_reviewer_activity, refresh_reviewer_review_from_live_preferred_review, ) +from .reviews import rebuild_pr_approval_state_result +from .runtime_protocols import ( + ReconcileRectifyRuntimeContext, + ReconcileWorkflowRuntimeContext, +) - -def _now_iso(bot) -> str: - return bot.datetime.now(bot.timezone.utc).isoformat() - - -def _ensure_source_event_key(review_data: dict, source_event_key: str, payload: dict | None = None) -> None: - review_data.setdefault("deferred_gaps", {}) - if payload is None: - payload = {} - payload["source_event_key"] = source_event_key - review_data["deferred_gaps"][source_event_key] = payload +DeferredArtifactIdentity = _reconcile_payloads.DeferredArtifactIdentity +DeferredReviewReplayContext = _reconcile_payloads.DeferredReviewReplayContext +_artifact_expected_name = _reconcile_payloads.artifact_expected_name +_artifact_expected_payload_name = _reconcile_payloads.artifact_expected_payload_name +ParsedWorkflowRunPayload = DeferredReviewPayload | DeferredCommentPayload | ObserverNoopPayload | _LegacyDeferredIssueCommentPayloadV2 | _LegacyDeferredReviewCommentPayloadV2 -def _clear_source_event_key(review_data: dict, source_event_key: str) -> None: - deferred_gaps = review_data.get("deferred_gaps") - if isinstance(deferred_gaps, dict): - deferred_gaps.pop(source_event_key, None) +@dataclass(frozen=True) +class WorkflowRunHandlerResult: + state_changed: bool + touched_items: list[int] -def _mark_reconciled_source_event(review_data: dict, source_event_key: str) -> None: - reconciled = review_data.setdefault("reconciled_source_events", []) - if source_event_key not in reconciled: - reconciled.append(source_event_key) +def _log(bot: ReconcileWorkflowRuntimeContext, level: str, message: str, **fields) -> None: + bot.logger.event(level, message, **fields) -def _was_reconciled_source_event(review_data: dict, source_event_key: str) -> bool: - reconciled = review_data.get("reconciled_source_events") - return isinstance(reconciled, list) and source_event_key in reconciled +def _now_iso(bot: ReconcileWorkflowRuntimeContext) -> str: + return bot.clock.now().isoformat() -def _record_review_rebuild(bot, state: dict, issue_number: int, review_data: dict) -> bool: - pull_request = bot.github_api("GET", f"pulls/{issue_number}") - if not isinstance(pull_request, dict): - raise RuntimeError(f"Failed to fetch pull request #{issue_number}") - reviews = bot.get_pull_request_reviews(issue_number) - if reviews is None: - raise RuntimeError(f"Failed to fetch live reviews for PR #{issue_number}") +def _record_review_rebuild(bot: ReconcileRectifyRuntimeContext, state: dict, issue_number: int, review_data: dict) -> bool: + pull_request = _read_reconcile_object(bot, f"pulls/{issue_number}", label=f"pull request #{issue_number}") + reviews = _read_reconcile_reviews(bot, issue_number) + before = { + "reviewer_review": deepcopy(review_data.get("reviewer_review")), + "active_head_sha": review_data.get("active_head_sha"), + "current_cycle_completion": deepcopy(review_data.get("current_cycle_completion")), + "current_cycle_write_approval": deepcopy(review_data.get("current_cycle_write_approval")), + "review_completed_at": review_data.get("review_completed_at"), + "review_completed_by": review_data.get("review_completed_by"), + "review_completion_source": review_data.get("review_completion_source"), + } refresh_reviewer_review_from_live_preferred_review( bot, issue_number, @@ -61,33 +107,54 @@ def _record_review_rebuild(bot, state: dict, issue_number: int, review_data: dic reviews=reviews, actor=review_data.get("current_reviewer"), ) - completion, _ = bot.reviews_module.rebuild_pr_approval_state(bot, issue_number, review_data, pull_request=pull_request, reviews=reviews) - if completion is None: - raise RuntimeError(f"Unable to rebuild approval state for PR #{issue_number}") - return bool(completion.get("completed")) + approval_result = rebuild_pr_approval_state_result( + bot, + issue_number, + review_data, + pull_request=pull_request, + reviews=reviews, + ) + if not approval_result.get("ok"): + raise ReconcileReadError( + f"Unable to rebuild approval state for PR #{issue_number}: {approval_result.get('reason')}", + failure_kind=str(approval_result.get("failure_kind") or "unavailable"), + ) + completion = approval_result["completion"] + after = { + "reviewer_review": deepcopy(review_data.get("reviewer_review")), + "active_head_sha": review_data.get("active_head_sha"), + "current_cycle_completion": deepcopy(review_data.get("current_cycle_completion")), + "current_cycle_write_approval": deepcopy(review_data.get("current_cycle_write_approval")), + "review_completed_at": review_data.get("review_completed_at"), + "review_completed_by": review_data.get("review_completed_by"), + "review_completion_source": review_data.get("review_completion_source"), + } + return before != after or bool(completion.get("completed")) def reconcile_active_review_entry( - bot, + bot: ReconcileRectifyRuntimeContext, state: dict, issue_number: int, *, require_pull_request_context: bool = True, completion_source: str = "rectify:reconcile-pr-review", ) -> tuple[str, bool, bool]: - review_data = bot.ensure_review_entry(state, issue_number) + review_data = ensure_review_entry(state, issue_number) if review_data is None: return f"ℹ️ No active review entry exists for #{issue_number}; nothing to rectify.", True, False assigned_reviewer = review_data.get("current_reviewer") if not assigned_reviewer: return f"ℹ️ #{issue_number} has no tracked assigned reviewer; nothing to rectify.", True, False - if require_pull_request_context and os.environ.get("IS_PULL_REQUEST", "false").lower() != "true": + if require_pull_request_context and bot.get_config_value("IS_PULL_REQUEST", "false").lower() != "true": return f"ℹ️ #{issue_number} is not a pull request in this event context; `/rectify` only reconciles PR reviews.", True, False - if str(state.get("freshness_runtime_epoch", "")).strip() != "freshness_v15" and os.environ.get("IS_PULL_REQUEST", "false").lower() == "true": + if str(state.get("freshness_runtime_epoch", "")).strip() != "freshness_v15" and bot.get_config_value("IS_PULL_REQUEST", "false").lower() == "true": return "ℹ️ PR review freshness rectify is epoch-gated and currently inactive.", True, False - state_changed = bot.maybe_record_head_observation_repair(issue_number, review_data) - reviews = bot.get_pull_request_reviews(issue_number) - if reviews is None: + head_repair_result = bot.adapters.review_state.maybe_record_head_observation_repair(issue_number, review_data) + state_changed = head_repair_result.changed + try: + reviews = _read_reconcile_reviews(bot, issue_number) + except ReconcileReadError: return f"❌ Failed to fetch reviews for PR #{issue_number}; cannot run `/rectify`.", False, False messages: list[str] = [] refreshed, latest_review = refresh_reviewer_review_from_live_preferred_review( @@ -106,8 +173,10 @@ def reconcile_active_review_entry( state_changed = True review_data["review_completion_source"] = completion_source if review_data.get("mandatory_approver_required"): + from scripts.reviewer_bot_core import approval_policy + escalation_opened_at = bot.parse_iso8601_timestamp(review_data.get("mandatory_approver_pinged_at")) or bot.parse_iso8601_timestamp(review_data.get("mandatory_approver_label_applied_at")) - triage_approval = find_triage_approval_after(bot, reviews, escalation_opened_at) + triage_approval = approval_policy.find_triage_approval_after(bot, reviews, escalation_opened_at) if triage_approval is not None: approver, _ = triage_approval if bot.satisfy_mandatory_approver_requirement(state, issue_number, approver): @@ -118,503 +187,431 @@ def reconcile_active_review_entry( return f"ℹ️ Rectify checked PR #{issue_number}: {'; '.join(messages) or 'no reconciliation transitions applied'}.", True, False -def _validate_deferred_comment_artifact(payload: dict) -> None: - required = { - "schema_version", - "source_workflow_name", - "source_workflow_file", - "source_run_id", - "source_run_attempt", - "source_event_name", - "source_event_action", - "source_event_key", - "pr_number", - "comment_id", - "comment_class", - "has_non_command_text", - "source_body_digest", - "source_created_at", - } - missing = sorted(required - set(payload)) - if missing: - raise RuntimeError("Deferred comment artifact missing required fields: " + ", ".join(missing)) - if payload.get("schema_version") != 2: - raise RuntimeError("Deferred comment artifact schema_version is not accepted by V18 reconcile") - if not isinstance(payload.get("comment_id"), int) or not isinstance(payload.get("pr_number"), int): - raise RuntimeError("Deferred comment artifact comment_id and pr_number must be integers") - if not isinstance(payload.get("comment_class"), str) or not isinstance(payload.get("has_non_command_text"), bool): - raise RuntimeError("Deferred comment artifact parse fields are malformed") - if not isinstance(payload.get("source_body_digest"), str) or not isinstance(payload.get("source_created_at"), str): - raise RuntimeError("Deferred comment artifact source digest or timestamp is malformed") - - -def _validate_deferred_review_artifact(payload: dict) -> None: - required = { - "schema_version", - "source_workflow_name", - "source_workflow_file", - "source_run_id", - "source_run_attempt", - "source_event_name", - "source_event_action", - "source_event_key", - "pr_number", - "review_id", - } - missing = sorted(required - set(payload)) - if missing: - raise RuntimeError("Deferred review artifact missing required fields: " + ", ".join(missing)) - if payload.get("schema_version") != 2: - raise RuntimeError("Deferred review artifact schema_version is not accepted by V18 reconcile") - if not isinstance(payload.get("review_id"), int) or not isinstance(payload.get("pr_number"), int): - raise RuntimeError("Deferred review artifact review_id and pr_number must be integers") - - -def _validate_deferred_review_comment_artifact(payload: dict) -> None: - required = { - "schema_version", - "source_workflow_name", - "source_workflow_file", - "source_run_id", - "source_run_attempt", - "source_event_name", - "source_event_action", - "source_event_key", - "pr_number", - "comment_id", - "comment_class", - "has_non_command_text", - "source_body_digest", - "source_created_at", - } - missing = sorted(required - set(payload)) - if missing: - raise RuntimeError("Deferred review-comment artifact missing required fields: " + ", ".join(missing)) - if payload.get("schema_version") != 2: - raise RuntimeError("Deferred review-comment artifact schema_version is not accepted by V18 reconcile") - if not isinstance(payload.get("comment_id"), int) or not isinstance(payload.get("pr_number"), int): - raise RuntimeError("Deferred review-comment artifact comment_id and pr_number must be integers") - if not isinstance(payload.get("comment_class"), str) or not isinstance(payload.get("has_non_command_text"), bool): - raise RuntimeError("Deferred review-comment artifact parse fields are malformed") - if not isinstance(payload.get("source_body_digest"), str) or not isinstance(payload.get("source_created_at"), str): - raise RuntimeError("Deferred review-comment artifact source digest or timestamp is malformed") - - -def _load_deferred_context() -> dict: - path = os.environ.get("DEFERRED_CONTEXT_PATH", "").strip() - if not path: - raise RuntimeError("Missing DEFERRED_CONTEXT_PATH for workflow_run reconcile") - with open(path, encoding="utf-8") as handle: - payload = json.load(handle) - if not isinstance(payload, dict): - raise RuntimeError("Deferred context payload must be a JSON object") - return payload - - -def _set_env_if_present(name: str, value) -> None: - if value is None: - return - os.environ[name] = str(value) - - -def _hydrate_reconcile_pr_context(bot, pr_number: int) -> dict: - pull_request = bot.github_api("GET", f"pulls/{pr_number}") - if not isinstance(pull_request, dict): - raise RuntimeError(f"Failed to fetch live PR #{pr_number} for reconcile context") - author = pull_request.get("user") - if not isinstance(author, dict): - raise RuntimeError(f"Live PR #{pr_number} is missing author metadata") - author_login = author.get("login") - if not isinstance(author_login, str) or not author_login.strip(): - raise RuntimeError(f"Live PR #{pr_number} is missing a valid author login") - labels = pull_request.get("labels") - if labels is None: - labels = [] - if not isinstance(labels, list): - raise RuntimeError(f"Live PR #{pr_number} labels are malformed") - label_names: list[str] = [] - for label in labels: - if not isinstance(label, dict): - raise RuntimeError(f"Live PR #{pr_number} contains malformed label metadata") - name = label.get("name") - if not isinstance(name, str): - raise RuntimeError(f"Live PR #{pr_number} contains a label without a valid name") - label_names.append(name) - os.environ["IS_PULL_REQUEST"] = "true" - os.environ["ISSUE_AUTHOR"] = author_login - os.environ["ISSUE_LABELS"] = json.dumps(label_names) - return pull_request - - -def _hydrate_reconcile_comment_context(live_comment: dict, payload: dict) -> None: - user = live_comment.get("user") - if not isinstance(user, dict): - raise RuntimeError("Live deferred comment user metadata is unavailable") - comment_author = user.get("login") or payload.get("actor_login") or "" - if not isinstance(comment_author, str) or not comment_author.strip(): - raise RuntimeError("Live deferred comment author login is unavailable") - comment_user_type = user.get("type") - if not isinstance(comment_user_type, str) or not comment_user_type.strip(): - raise RuntimeError("Live deferred comment user type is unavailable") - author_association = live_comment.get("author_association") - if not isinstance(author_association, str) or not author_association.strip(): - raise RuntimeError("Live deferred comment author association is unavailable") - _set_env_if_present("COMMENT_AUTHOR", comment_author) - _set_env_if_present("COMMENT_ID", payload.get("comment_id")) - _set_env_if_present("COMMENT_SOURCE_EVENT_KEY", payload.get("source_event_key")) - _set_env_if_present("COMMENT_CREATED_AT", payload.get("source_created_at")) - _set_env_if_present("COMMENT_USER_TYPE", comment_user_type) - _set_env_if_present("COMMENT_AUTHOR_ASSOCIATION", author_association) - _set_env_if_present("COMMENT_SENDER_TYPE", comment_user_type) - os.environ["COMMENT_INSTALLATION_ID"] = "" - os.environ["COMMENT_PERFORMED_VIA_GITHUB_APP"] = "true" if live_comment.get("performed_via_github_app") else "false" - - -def _validate_observer_noop_payload(payload: dict) -> None: - required = { - "schema_version", - "kind", - "reason", - "source_workflow_name", - "source_workflow_file", - "source_run_id", - "source_run_attempt", - "source_event_name", - "source_event_action", - "source_event_key", - "pr_number", - } - missing = sorted(required - set(payload)) - if missing: - raise RuntimeError("Observer no-op payload missing required fields: " + ", ".join(missing)) - if payload.get("schema_version") != 1: - raise RuntimeError("Observer no-op payload schema_version is not accepted") - if payload.get("kind") != "observer_noop": - raise RuntimeError("Observer no-op payload kind mismatch") - if not isinstance(payload.get("reason"), str) or not payload.get("reason"): - raise RuntimeError("Observer no-op payload reason must be a non-empty string") - if not isinstance(payload.get("pr_number"), int): - raise RuntimeError("Observer no-op payload pr_number must be an integer") - - -def _expected_observer_identity(payload: dict) -> tuple[str, str]: - event_name = payload.get("source_event_name") - event_action = payload.get("source_event_action") - if event_name == "issue_comment" and event_action == "created": - return ( - "Reviewer Bot PR Comment Observer", - ".github/workflows/reviewer-bot-pr-comment-observer.yml", - ) - if event_name == "pull_request_review" and event_action == "submitted": - return ( - "Reviewer Bot PR Review Submitted Observer", - ".github/workflows/reviewer-bot-pr-review-submitted-observer.yml", - ) - if event_name == "pull_request_review" and event_action == "dismissed": +def handle_rectify_command( + bot: ReconcileRectifyRuntimeContext, + state: dict, + issue_number: int, + comment_author: str, +) -> tuple[str, bool, bool]: + review_data = ensure_review_entry(state, issue_number) + current_reviewer = review_data.get("current_reviewer") if review_data else None + + is_current_reviewer = ( + isinstance(current_reviewer, str) + and current_reviewer.lower() == comment_author.lower() + ) + + triage_status = "denied" + if not is_current_reviewer: + triage_status = bot.github.get_user_permission_status(comment_author, "triage") + + if not is_current_reviewer and triage_status == "unavailable": return ( - "Reviewer Bot PR Review Dismissed Observer", - ".github/workflows/reviewer-bot-pr-review-dismissed-observer.yml", + "❌ Unable to verify triage permissions right now; refusing to continue.", + False, + False, ) - if event_name == "pull_request_review_comment" and event_action == "created": + + if not is_current_reviewer and triage_status != "granted": + if current_reviewer: + return ( + f"❌ Only the assigned reviewer (@{current_reviewer}) or a maintainer with triage+ " + "permission can run `/rectify`.", + False, + False, + ) return ( - "Reviewer Bot PR Review Comment Observer", - ".github/workflows/reviewer-bot-pr-review-comment-observer.yml", + "❌ Only maintainers with triage+ permission can run `/rectify` when no assigned " + "reviewer is tracked.", + False, + False, ) - raise RuntimeError("Unsupported deferred workflow identity") - - -def _validate_workflow_run_artifact_identity(payload: dict) -> None: - expected_name, expected_file = _expected_observer_identity(payload) - if payload.get("source_workflow_name") != expected_name: - raise RuntimeError("Deferred artifact workflow name mismatch") - if payload.get("source_workflow_file") != expected_file: - raise RuntimeError("Deferred artifact workflow file mismatch") - triggering_name = os.environ.get("WORKFLOW_RUN_TRIGGERING_NAME", "").strip() - if triggering_name and triggering_name != expected_name: - raise RuntimeError("Triggering workflow name mismatch") - triggering_id = os.environ.get("WORKFLOW_RUN_TRIGGERING_ID", "").strip() - if triggering_id and str(payload.get("source_run_id")) != triggering_id: - raise RuntimeError("Deferred artifact run_id mismatch") - triggering_attempt = os.environ.get("WORKFLOW_RUN_TRIGGERING_ATTEMPT", "").strip() - if triggering_attempt and str(payload.get("source_run_attempt")) != triggering_attempt: - raise RuntimeError("Deferred artifact run_attempt mismatch") - if os.environ.get("WORKFLOW_RUN_TRIGGERING_CONCLUSION", "").strip() != "success": - raise RuntimeError("Triggering observer workflow did not conclude successfully") - - -def _artifact_expected_name(payload: dict) -> str: - event_name = payload.get("source_event_name") - event_action = payload.get("source_event_action") - run_id = payload.get("source_run_id") - run_attempt = payload.get("source_run_attempt") - if event_name == "issue_comment" and event_action == "created": - return f"reviewer-bot-comment-context-{run_id}-attempt-{run_attempt}" - if event_name == "pull_request_review" and event_action == "submitted": - return f"reviewer-bot-review-submitted-context-{run_id}-attempt-{run_attempt}" - if event_name == "pull_request_review" and event_action == "dismissed": - return f"reviewer-bot-review-dismissed-context-{run_id}-attempt-{run_attempt}" - if event_name == "pull_request_review_comment" and event_action == "created": - return f"reviewer-bot-review-comment-context-{run_id}-attempt-{run_attempt}" - raise RuntimeError("Unsupported deferred artifact naming") - - -def _artifact_expected_payload_name(payload: dict) -> str: - event_name = payload.get("source_event_name") - event_action = payload.get("source_event_action") - if event_name == "issue_comment" and event_action == "created": - return "deferred-comment.json" - if event_name == "pull_request_review" and event_action == "submitted": - return "deferred-review-submitted.json" - if event_name == "pull_request_review" and event_action == "dismissed": - return "deferred-review-dismissed.json" - if event_name == "pull_request_review_comment" and event_action == "created": - return "deferred-review-comment.json" - raise RuntimeError("Unsupported deferred payload path") + + return reconcile_active_review_entry(bot, state, issue_number) + + +def _load_deferred_context(bot: ReconcileWorkflowRuntimeContext) -> dict: + return bot.load_deferred_payload() + + +def _classify_deferred_comment_payload(payload: DeferredCommentPayload) -> dict: + normalized_body = "\n".join(line.rstrip() for line in payload.comment_body.replace("\r\n", "\n").split("\n")).strip() + if not normalized_body: + comment_class = ObserverCommentClassification.PLAIN_TEXT + command_lines: list[str] = [] + non_command_lines: list[str] = [] + else: + lines = [line for line in normalized_body.splitlines() if line.strip()] + command_lines = [line for line in lines if line.strip().startswith("@guidelines-bot /")] + non_command_lines = [line for line in lines if not line.strip().startswith("@guidelines-bot /")] + if command_lines and not non_command_lines: + comment_class = ObserverCommentClassification.COMMAND_ONLY + elif command_lines and non_command_lines: + comment_class = ObserverCommentClassification.COMMAND_PLUS_TEXT + else: + comment_class = ObserverCommentClassification.PLAIN_TEXT + return { + "comment_class": comment_class, + "has_non_command_text": bool(non_command_lines), + "command_count": len(command_lines), + } def _reconcile_deferred_comment( - bot, + bot: ReconcileWorkflowRuntimeContext, state: dict, - pr_number: int, review_data: dict, - payload: dict, - *, - expected_event_name: str, - live_comment_endpoint: str, + context: DeferredCommentReplayContext, ) -> bool: - comment_id_value = payload.get("comment_id") - if not isinstance(comment_id_value, int): - raise RuntimeError("Deferred comment artifact comment_id must be an integer") - comment_id = comment_id_value - if str(payload.get("source_event_key", "")) != f"{expected_event_name}:{comment_id}": - raise RuntimeError("Deferred comment artifact source_event_key mismatch") - _set_env_if_present("COMMENT_SOURCE_EVENT_KEY", payload.get("source_event_key")) - _hydrate_reconcile_pr_context(bot, pr_number) - comment_author = str(payload.get("actor_login", "")) - comment_created_at = str(payload.get("source_created_at")) - classified = payload.get("comment_class") - source_freshness_eligible = classified in {"plain_text", "command_plus_text"} and bool(payload.get("has_non_command_text")) - live_comment = bot.github_api("GET", live_comment_endpoint) - if not isinstance(live_comment, dict): - changed = False - if source_freshness_eligible: - changed = _record_conversation_freshness(bot, state, pr_number, comment_author, comment_id, comment_created_at) - _update_deferred_gap(bot, review_data, payload, "reconcile_failed_closed", f"Deferred comment {comment_id} is no longer visible; source-time freshness only may be preserved. See {bot.REVIEW_FRESHNESS_RUNBOOK_PATH}.") - return changed - _hydrate_reconcile_comment_context(live_comment, payload) - live_body = live_comment.get("body") - if not isinstance(live_body, str): - raise RuntimeError("Live deferred comment body is unavailable") - if _digest_body(live_body) != payload.get("source_body_digest"): - changed = False - if source_freshness_eligible: - changed = _record_conversation_freshness(bot, state, pr_number, comment_author, comment_id, comment_created_at) - _update_deferred_gap(bot, review_data, payload, "reconcile_failed_closed", f"Deferred comment {comment_id} body digest changed; command execution suppressed. See {bot.REVIEW_FRESHNESS_RUNBOOK_PATH}.") - return changed - changed = False - if source_freshness_eligible: - changed = _record_conversation_freshness(bot, state, pr_number, comment_author, comment_id, comment_created_at) or changed - live_classified = _validate_live_comment_replay_contract(bot, review_data, payload, live_body) - if live_classified is None: - return changed - if classified in {"command_only", "command_plus_text"}: - changed = _handle_command(bot, state, pr_number, comment_author, live_classified) or changed - _mark_reconciled_source_event(review_data, str(payload.get("source_event_key", ""))) - _clear_source_event_key(review_data, str(payload.get("source_event_key", ""))) - return changed - - -def _update_deferred_gap(bot, review_data: dict, payload: dict, reason: str, diagnostic_summary: str) -> None: - source_event_key = str(payload.get("source_event_key", "")) - if not source_event_key: - return - review_data.setdefault("deferred_gaps", {}) - existing = review_data["deferred_gaps"].get(source_event_key, {}) - if not isinstance(existing, dict): - existing = {} - existing.update( - { - "source_event_key": source_event_key, - "source_event_kind": f"{payload.get('source_event_name')}:{payload.get('source_event_action')}", - "pr_number": payload.get("pr_number"), - "reason": reason, - "source_event_created_at": payload.get("source_created_at") or payload.get("source_submitted_at"), - "source_run_id": payload.get("source_run_id"), - "source_run_attempt": payload.get("source_run_attempt"), - "source_workflow_file": payload.get("source_workflow_file"), - "source_artifact_name": payload.get("source_artifact_name"), - "first_noted_at": existing.get("first_noted_at") or _now_iso(bot), - "last_checked_at": _now_iso(bot), - "operator_action_required": True, - "diagnostic_summary": diagnostic_summary, + payload = context.payload.raw_payload + comment_id = context.comment_id + pr_number = context.pr_number + _read_live_pr_replay_context(bot, pr_number) + source_freshness_eligible = context.source_freshness_eligible + source_classified = ( + _classify_deferred_comment_payload(context.payload) + if isinstance(context.payload, DeferredCommentPayload) + else { + "comment_class": context.payload.comment_class, + "has_non_command_text": context.payload.has_non_command_text, + "command_count": 1, } ) - review_data["deferred_gaps"][source_event_key] = existing - -def _validate_live_comment_replay_contract(bot, review_data: dict, payload: dict, live_body: str) -> dict | None: - source_comment_class = str(payload.get("comment_class", "")) - live_classified = classify_comment_payload(bot, live_body) - live_comment_class = str(live_classified.get("comment_class", "")) - source_has_non_command_text = bool(payload.get("has_non_command_text")) - live_has_non_command_text = bool(live_classified.get("has_non_command_text")) + def replay_request(comment_context: LiveCommentReplayContext | None = None, *, comment_body: str = "") -> CommentEventRequest: + return build_replay_comment_event_request( + context.payload, + live_comment=comment_context, + comment_body=comment_body, + ) - if live_comment_class != source_comment_class: - _update_deferred_gap( + def record_artifact_invalid(problem: InvalidEventInput) -> bool: + return gap_bookkeeping._update_deferred_gap( bot, review_data, payload, - "reconcile_failed_closed", - ( - f"Deferred comment {payload['comment_id']} classification changed from " - f"{source_comment_class} to {live_comment_class}; replay suppressed. " - f"See {bot.REVIEW_FRESHNESS_RUNBOOK_PATH}." - ), + "artifact_invalid", + str(problem), ) - return None - if live_has_non_command_text != source_has_non_command_text: - _update_deferred_gap( + try: + live_comment = _read_reconcile_object(bot, context.live_comment_endpoint, label=f"deferred comment {comment_id}") + except ReconcileReadError as exc: + decision = reconcile_replay_policy.decide_comment_replay( + comment_id=comment_id, + source_comment_class=str(source_classified.get("comment_class", ObserverCommentClassification.PLAIN_TEXT)), + source_has_non_command_text=bool(source_classified.get("has_non_command_text")), + source_freshness_eligible=source_freshness_eligible, + live_comment_found=False, + live_body_digest_matches=False, + live_classified=None, + live_failure_kind=exc.failure_kind, + runbook_path=bot.REVIEW_FRESHNESS_RUNBOOK_PATH, + ) + changed = False + if decision.record_source_freshness: + try: + changed = record_conversation_freshness(bot, state, replay_request()) + except InvalidEventInput as exc: + return record_artifact_invalid(exc) + gap_changed = gap_bookkeeping._update_deferred_gap( bot, review_data, payload, - "reconcile_failed_closed", - ( - f"Deferred comment {payload['comment_id']} non-command text classification drifted; " - f"replay suppressed. See {bot.REVIEW_FRESHNESS_RUNBOOK_PATH}." - ), + str(decision.failed_closed_reason), + str(decision.diagnostic_summary), + failure_kind=decision.failure_kind, ) - return None - - if source_comment_class in {"command_only", "command_plus_text"} and int(live_classified.get("command_count", 0)) != 1: - _update_deferred_gap( + return changed or gap_changed + comment_context = _read_live_comment_replay_context(live_comment, payload) + live_body = live_comment.get("body") + if not isinstance(live_body, str): + raise RuntimeError("Live deferred comment body is unavailable") + live_classified = classify_comment_payload(bot, live_body) + if digest_comment_body(live_body) != digest_comment_body(context.payload.comment_body): + decision = reconcile_replay_policy.decide_comment_replay( + comment_id=comment_id, + source_comment_class=str(source_classified.get("comment_class", ObserverCommentClassification.PLAIN_TEXT)), + source_has_non_command_text=bool(source_classified.get("has_non_command_text")), + source_freshness_eligible=source_freshness_eligible, + live_comment_found=True, + live_body_digest_matches=False, + live_classified={ + "comment_class": str(live_classified.get("comment_class", "plain_text")), + "has_non_command_text": bool(live_classified.get("has_non_command_text")), + "command_count": 1, + }, + live_failure_kind=None, + runbook_path=bot.REVIEW_FRESHNESS_RUNBOOK_PATH, + ) + changed = False + if decision.record_source_freshness: + try: + changed = record_conversation_freshness(bot, state, replay_request(comment_context, comment_body=live_body)) + except InvalidEventInput as exc: + return record_artifact_invalid(exc) + gap_changed = gap_bookkeeping._update_deferred_gap( bot, review_data, payload, - "reconcile_failed_closed", - ( - f"Deferred comment {payload['comment_id']} no longer resolves to exactly one command; " - f"replay suppressed. See {bot.REVIEW_FRESHNESS_RUNBOOK_PATH}." - ), + str(decision.failed_closed_reason), + str(decision.diagnostic_summary), ) - return None + return changed or gap_changed + decision = reconcile_replay_policy.decide_comment_replay( + comment_id=int(payload["comment_id"]), + source_comment_class=str(source_classified.get("comment_class", ObserverCommentClassification.PLAIN_TEXT)), + source_has_non_command_text=bool(source_classified.get("has_non_command_text")), + source_freshness_eligible=source_freshness_eligible, + live_comment_found=True, + live_body_digest_matches=True, + live_classified=live_classified, + live_failure_kind=None, + runbook_path=bot.REVIEW_FRESHNESS_RUNBOOK_PATH, + ) + changed = False + if decision.record_source_freshness: + try: + changed = record_conversation_freshness(bot, state, replay_request(comment_context, comment_body=live_body)) or changed + except InvalidEventInput as exc: + return record_artifact_invalid(exc) + if decision.failed_closed_reason is not None: + gap_changed = gap_bookkeeping._update_deferred_gap( + bot, + review_data, + payload, + decision.failed_closed_reason, + str(decision.diagnostic_summary), + failure_kind=decision.failure_kind, + ) + return changed or gap_changed + if decision.replay_comment_command: + try: + changed = process_comment_event( + bot, + state, + replay_request(comment_context, comment_body=live_body), + classify_comment_payload=lambda _bot, _body: live_classified, + classify_issue_comment_actor=classify_issue_comment_actor, + ) or changed + except InvalidEventInput as exc: + return record_artifact_invalid(exc) + reconciled_changed = False + if decision.mark_reconciled: + reconciled_changed = gap_bookkeeping._mark_reconciled_source_event(review_data, str(payload.get("source_event_key", ""))) + gap_cleared_changed = False + if decision.clear_gap: + gap_cleared_changed = gap_bookkeeping._clear_source_event_key(review_data, str(payload.get("source_event_key", ""))) + return changed or reconciled_changed or gap_cleared_changed + + +def _handle_observer_noop_workflow_run( + bot: ReconcileWorkflowRuntimeContext, + state: dict, + review_data: dict, + parsed_payload: ObserverNoopPayload, +) -> bool: + del state, review_data + _reconcile_payloads.validate_triggering_run_identity(bot, parsed_payload.raw_payload) + decision = reconcile_replay_policy.decide_observer_noop( + source_event_key=parsed_payload.identity.source_event_key, + reason=parsed_payload.reason, + ) + _log( + bot, + "info", + f"Observer workflow produced explicit no-op payload for {decision.source_event_key}: {decision.reason}", + source_event_key=decision.source_event_key, + reason=decision.reason, + ) + return False + + +def _handle_issue_comment_workflow_run( + bot: ReconcileWorkflowRuntimeContext, + state: dict, + review_data: dict, + parsed_payload: DeferredCommentPayload, +) -> bool: + _reconcile_payloads.validate_triggering_run_identity(bot, parsed_payload.raw_payload) + context = build_deferred_comment_replay_context( + parsed_payload, + expected_event_name="issue_comment", + live_comment_endpoint=f"issues/comments/{parsed_payload.comment_id}", + ) + return _reconcile_deferred_comment(bot, state, review_data, context) + + +def _handle_review_comment_workflow_run( + bot: ReconcileWorkflowRuntimeContext, + state: dict, + review_data: dict, + parsed_payload: DeferredCommentPayload, +) -> bool: + _reconcile_payloads.validate_triggering_run_identity(bot, parsed_payload.raw_payload) + context = build_deferred_comment_replay_context( + parsed_payload, + expected_event_name="pull_request_review_comment", + live_comment_endpoint=f"pulls/comments/{parsed_payload.comment_id}", + ) + return _reconcile_deferred_comment(bot, state, review_data, context) + + +def _handle_review_submitted_workflow_run( + bot: ReconcileWorkflowRuntimeContext, + state: dict, + review_data: dict, + parsed_payload: DeferredReviewPayload, +) -> bool: + _reconcile_payloads.validate_triggering_run_identity(bot, parsed_payload.raw_payload) + context = build_deferred_review_replay_context( + parsed_payload, + expected_event_action="submitted", + ) + pr_number = context.pr_number + source_event_key = context.source_event_key + review_id = context.review_id + live_review = _read_optional_reconcile_object(bot, f"pulls/{pr_number}/reviews/{review_id}", label=f"live review #{review_id}") + _read_reconcile_object(bot, f"pulls/{pr_number}", label=f"live PR #{pr_number}") + live_commit_id = None + live_submitted_at = parsed_payload.source_submitted_at + live_state = parsed_payload.source_review_state + if isinstance(live_review, dict): + live_commit_id = live_review.get("commit_id") + live_submitted_at = live_review.get("submitted_at") or live_submitted_at + live_state = live_review.get("state") or live_state + else: + live_commit_id = parsed_payload.source_commit_id + actor = context.actor_login + state_changed = bot.adapters.review_state.maybe_record_head_observation_repair(pr_number, review_data).changed + decision = reconcile_replay_policy.decide_review_submitted_replay( + source_event_key=source_event_key, + actor_login=actor, + current_reviewer=review_data.get("current_reviewer"), + live_commit_id=live_commit_id if isinstance(live_commit_id, str) else None, + live_submitted_at=live_submitted_at if isinstance(live_submitted_at, str) else None, + ) + if decision.accept_reviewer_review: + accept_channel_event( + review_data, + "reviewer_review", + semantic_key=source_event_key, + timestamp=str(decision.replay_timestamp), + actor=str(decision.actor_login), + reviewed_head_sha=str(decision.reviewed_head_sha), + source_precedence=1, + ) + record_reviewer_activity(review_data, str(decision.replay_timestamp)) + state_changed = True + if _record_review_rebuild(bot, state, pr_number, review_data): + state_changed = True + reconciled_changed = gap_bookkeeping._mark_reconciled_source_event(review_data, source_event_key) + gap_cleared_changed = gap_bookkeeping._clear_source_event_key(review_data, source_event_key) + return state_changed or reconciled_changed or gap_cleared_changed - return live_classified + +def _handle_review_dismissed_workflow_run( + bot: ReconcileWorkflowRuntimeContext, + state: dict, + review_data: dict, + parsed_payload: DeferredReviewPayload, +) -> bool: + _reconcile_payloads.validate_triggering_run_identity(bot, parsed_payload.raw_payload) + context = build_deferred_review_replay_context( + parsed_payload, + expected_event_action="dismissed", + ) + source_event_key = context.source_event_key + decision = reconcile_replay_policy.decide_review_dismissed_replay( + source_event_key=source_event_key, + timestamp=_now_iso(bot), + ) + if decision.accept_review_dismissal: + accept_channel_event( + review_data, + "review_dismissal", + semantic_key=source_event_key, + timestamp=str(decision.replay_timestamp), + dismissal_only=True, + ) + bot.adapters.review_state.maybe_record_head_observation_repair(context.pr_number, review_data) + _record_review_rebuild(bot, state, context.pr_number, review_data) + gap_bookkeeping._mark_reconciled_source_event(review_data, source_event_key) + gap_bookkeeping._clear_source_event_key(review_data, source_event_key) + return True + + +_WORKFLOW_RUN_HANDLER_MATRIX: dict[tuple[str, str], tuple[type[DeferredCommentPayload] | type[DeferredReviewPayload], object]] = { + ("issue_comment", "created"): (DeferredCommentPayload, _handle_issue_comment_workflow_run), + ("pull_request_review_comment", "created"): (DeferredCommentPayload, _handle_review_comment_workflow_run), + ("pull_request_review", "submitted"): (DeferredReviewPayload, _handle_review_submitted_workflow_run), + ("pull_request_review", "dismissed"): (DeferredReviewPayload, _handle_review_dismissed_workflow_run), +} + + +def _workflow_run_handler_for_payload(parsed_payload: ParsedWorkflowRunPayload): + if isinstance(parsed_payload, ObserverNoopPayload): + return _handle_observer_noop_workflow_run + if isinstance(parsed_payload, _LegacyDeferredIssueCommentPayloadV2): + return _handle_issue_comment_workflow_run + if isinstance(parsed_payload, _LegacyDeferredReviewCommentPayloadV2): + return _handle_review_comment_workflow_run + entry = _WORKFLOW_RUN_HANDLER_MATRIX.get( + ( + parsed_payload.identity.source_event_name, + parsed_payload.identity.source_event_action, + ) + ) + if entry is None: + return None + payload_type, handler = entry + if not isinstance(parsed_payload, payload_type): + return None + return handler -def handle_workflow_run_event(bot, state: dict) -> bool: +def handle_workflow_run_event_result(bot: ReconcileWorkflowRuntimeContext, state: dict) -> WorkflowRunHandlerResult: bot.assert_lock_held("handle_workflow_run_event") if str(state.get("freshness_runtime_epoch", "")).strip() != "freshness_v15": - print("V18 workflow_run reconcile safe-noop before epoch flip") - return False - payload = _load_deferred_context() - pr_number = int(payload.get("pr_number", 0) or 0) + _log(bot, "info", "V18 workflow_run reconcile safe-noop before epoch flip") + return WorkflowRunHandlerResult(False, []) + + def _build_result(state_changed: bool, pr_number: int) -> WorkflowRunHandlerResult: + touched_items = bot.drain_touched_items() + return WorkflowRunHandlerResult( + state_changed=state_changed, + touched_items=touched_items, + ) + + try: + payload = _load_deferred_context(bot) + except RuntimeError: + if build_event_context(bot).workflow_artifact_contract == "artifact_optional_router": + return WorkflowRunHandlerResult(False, []) + raise + parsed_payload = parse_deferred_context_payload(payload) + pr_number = parsed_payload.pr_number if pr_number <= 0: raise RuntimeError("Deferred context is missing a valid PR number") bot.collect_touched_item(pr_number) - review_data = bot.ensure_review_entry(state, pr_number, create=True) + review_data = ensure_review_entry(state, pr_number, create=True) if review_data is None: raise RuntimeError(f"No review entry available for PR #{pr_number}") - event_name = payload.get("source_event_name") - event_action = payload.get("source_event_action") - source_event_key = str(payload.get("source_event_key", "")) try: - if payload.get("kind") == "observer_noop": - _validate_observer_noop_payload(payload) - _validate_workflow_run_artifact_identity(payload) - print( - "Observer workflow produced explicit no-op payload for " - f"{source_event_key}: {payload.get('reason')}" - ) - return False - - if event_name == "issue_comment": - _validate_deferred_comment_artifact(payload) - _validate_workflow_run_artifact_identity(payload) - return _reconcile_deferred_comment( - bot, - state, - pr_number, - review_data, - payload, - expected_event_name="issue_comment", - live_comment_endpoint=f"issues/comments/{payload['comment_id']}", - ) - - if event_name == "pull_request_review_comment" and event_action == "created": - _validate_deferred_review_comment_artifact(payload) - _validate_workflow_run_artifact_identity(payload) - return _reconcile_deferred_comment( - bot, - state, - pr_number, - review_data, - payload, - expected_event_name="pull_request_review_comment", - live_comment_endpoint=f"pulls/comments/{payload['comment_id']}", - ) - - if event_name == "pull_request_review" and event_action == "submitted": - _validate_deferred_review_artifact(payload) - _validate_workflow_run_artifact_identity(payload) - review_id_value = payload.get("review_id") - if not isinstance(review_id_value, int): - raise RuntimeError("Deferred review artifact review_id must be an integer") - review_id = review_id_value - if source_event_key != f"pull_request_review:{review_id}": - raise RuntimeError("Deferred review-submitted artifact source_event_key mismatch") - live_review = bot.github_api("GET", f"pulls/{pr_number}/reviews/{review_id}") - live_pr = bot.github_api("GET", f"pulls/{pr_number}") - if not isinstance(live_pr, dict): - raise RuntimeError(f"Failed to fetch live PR #{pr_number}") - live_commit_id = None - live_submitted_at = payload.get("source_submitted_at") - live_state = payload.get("source_review_state") - if isinstance(live_review, dict): - live_commit_id = live_review.get("commit_id") - live_submitted_at = live_review.get("submitted_at") or live_submitted_at - live_state = live_review.get("state") or live_state - else: - live_commit_id = payload.get("source_commit_id") - actor = str(payload.get("actor_login", "")) - changed = bot.maybe_record_head_observation_repair(pr_number, review_data) - if isinstance(review_data.get("current_reviewer"), str) and review_data.get("current_reviewer", "").lower() == actor.lower() and isinstance(live_commit_id, str) and isinstance(live_submitted_at, str): - bot.reviews_module.accept_channel_event( - review_data, - "reviewer_review", - semantic_key=source_event_key, - timestamp=live_submitted_at, - actor=actor, - reviewed_head_sha=live_commit_id, - source_precedence=1, - ) - bot.reviews_module.record_reviewer_activity(review_data, live_submitted_at) - _record_review_rebuild(bot, state, pr_number, review_data) - _mark_reconciled_source_event(review_data, source_event_key) - _clear_source_event_key(review_data, source_event_key) - return changed or True - - if event_name == "pull_request_review" and event_action == "dismissed": - _validate_deferred_review_artifact(payload) - _validate_workflow_run_artifact_identity(payload) - review_id_value = payload.get("review_id") - if not isinstance(review_id_value, int): - raise RuntimeError("Deferred review artifact review_id must be an integer") - if source_event_key != f"pull_request_review_dismissed:{review_id_value}": - raise RuntimeError("Deferred review-dismissed artifact source_event_key mismatch") - bot.reviews_module.accept_channel_event( - review_data, - "review_dismissal", - semantic_key=source_event_key, - timestamp=_now_iso(bot), - dismissal_only=True, - ) - bot.maybe_record_head_observation_repair(pr_number, review_data) - _record_review_rebuild(bot, state, pr_number, review_data) - _mark_reconciled_source_event(review_data, source_event_key) - _clear_source_event_key(review_data, source_event_key) - return True + handler = _workflow_run_handler_for_payload(parsed_payload) + if handler is None: + raise RuntimeError("Unsupported deferred workflow_run payload") + return _build_result(handler(bot, state, review_data, parsed_payload), pr_number) except RuntimeError as exc: - _update_deferred_gap(bot, review_data, payload, "reconcile_failed_closed", f"{exc} See {bot.REVIEW_FRESHNESS_RUNBOOK_PATH}.") + failure_kind = exc.failure_kind if isinstance(exc, ReconcileReadError) else None + gap_changed = gap_bookkeeping._update_deferred_gap( + bot, + review_data, + payload, + "reconcile_failed_closed", + f"{exc} See {bot.REVIEW_FRESHNESS_RUNBOOK_PATH}.", + failure_kind=failure_kind, + ) + if gap_changed: + return _build_result(True, pr_number) raise - raise RuntimeError("Unsupported deferred workflow_run payload") diff --git a/scripts/reviewer_bot_lib/reconcile_payloads.py b/scripts/reviewer_bot_lib/reconcile_payloads.py new file mode 100644 index 000000000..ca9c71685 --- /dev/null +++ b/scripts/reviewer_bot_lib/reconcile_payloads.py @@ -0,0 +1,535 @@ +"""Deferred reconcile payload and identity helpers.""" + +from __future__ import annotations + +from dataclasses import dataclass +from enum import StrEnum + + +class DeferredPayloadKind(StrEnum): + DEFERRED_COMMENT = "deferred_comment" + DEFERRED_REVIEW_COMMENT = "deferred_review_comment" + DEFERRED_REVIEW_SUBMITTED = "deferred_review_submitted" + DEFERRED_REVIEW_DISMISSED = "deferred_review_dismissed" + + +@dataclass(frozen=True) +class DeferredArtifactIdentity: + payload_kind: DeferredPayloadKind + schema_version: int + source_run_id: int + source_run_attempt: int + source_event_name: str + source_event_action: str + source_event_key: str + pr_number: int + + +@dataclass(frozen=True) +class DeferredReviewPayload: + identity: DeferredArtifactIdentity + review_id: int + source_submitted_at: str | None + source_review_state: str | None + source_commit_id: str | None + actor_login: str | None + raw_payload: dict + + @property + def pr_number(self) -> int: + return self.identity.pr_number + + +@dataclass(frozen=True) +class DeferredCommentPayload: + identity: DeferredArtifactIdentity + comment_id: int + comment_body: str + comment_created_at: str + comment_author: str + comment_author_id: int + comment_user_type: str + comment_sender_type: str + comment_installation_id: str | None + comment_performed_via_github_app: bool + issue_author: str + issue_state: str + issue_labels: tuple[str, ...] + raw_payload: dict + + @property + def pr_number(self) -> int: + return self.identity.pr_number + + +@dataclass(frozen=True) +class _LegacyDeferredIssueCommentPayloadV2: + identity: DeferredArtifactIdentity + comment_id: int + comment_class: str + has_non_command_text: bool + source_body_digest: str + source_created_at: str + actor_login: str | None + raw_payload: dict + + @property + def pr_number(self) -> int: + return self.identity.pr_number + + +@dataclass(frozen=True) +class _LegacyDeferredReviewCommentPayloadV2: + identity: DeferredArtifactIdentity + comment_id: int + comment_class: str + has_non_command_text: bool + source_body_digest: str + source_created_at: str + actor_login: str | None + raw_payload: dict + + @property + def pr_number(self) -> int: + return self.identity.pr_number + + +@dataclass(frozen=True) +class ObserverNoopPayload: + identity: DeferredArtifactIdentity + reason: str + raw_payload: dict + + @property + def pr_number(self) -> int: + return self.identity.pr_number + + +@dataclass(frozen=True) +class DeferredCommentReplayContext: + payload: DeferredCommentPayload | _LegacyDeferredIssueCommentPayloadV2 | _LegacyDeferredReviewCommentPayloadV2 + expected_event_name: str + live_comment_endpoint: str + + @property + def source_event_key(self) -> str: + return self.payload.identity.source_event_key + + @property + def comment_id(self) -> int: + return self.payload.comment_id + + @property + def pr_number(self) -> int: + return self.payload.identity.pr_number + + @property + def actor_login(self) -> str: + if hasattr(self.payload, "comment_author"): + return self.payload.comment_author + return self.payload.actor_login or "" + + @property + def source_created_at(self) -> str: + if hasattr(self.payload, "comment_created_at"): + return self.payload.comment_created_at + return self.payload.source_created_at + + @property + def source_freshness_eligible(self) -> bool: + if hasattr(self.payload, "comment_body"): + return True + return self.payload.comment_class in {"plain_text", "command_plus_text"} and self.payload.has_non_command_text + + +@dataclass(frozen=True) +class DeferredReviewReplayContext: + payload: DeferredReviewPayload + + @property + def source_event_key(self) -> str: + return self.payload.identity.source_event_key + + @property + def review_id(self) -> int: + return self.payload.review_id + + @property + def pr_number(self) -> int: + return self.payload.identity.pr_number + + @property + def actor_login(self) -> str: + return self.payload.actor_login or "" + + +def _build_deferred_identity(payload: dict) -> DeferredArtifactIdentity: + payload_kind = payload.get("payload_kind") + if payload_kind is None: + event_name = payload.get("source_event_name") + event_action = payload.get("source_event_action") + if event_name == "issue_comment" and event_action == "created": + payload_kind = DeferredPayloadKind.DEFERRED_COMMENT.value + elif event_name == "pull_request_review_comment" and event_action == "created": + payload_kind = DeferredPayloadKind.DEFERRED_REVIEW_COMMENT.value + elif event_name == "pull_request_review" and event_action == "submitted": + payload_kind = DeferredPayloadKind.DEFERRED_REVIEW_SUBMITTED.value + elif event_name == "pull_request_review" and event_action == "dismissed": + payload_kind = DeferredPayloadKind.DEFERRED_REVIEW_DISMISSED.value + try: + resolved_payload_kind = DeferredPayloadKind(str(payload_kind)) + except ValueError as exc: + raise RuntimeError("Unsupported deferred workflow_run payload") from exc + return DeferredArtifactIdentity( + payload_kind=resolved_payload_kind, + schema_version=int(payload["schema_version"]), + source_run_id=int(payload["source_run_id"]), + source_run_attempt=int(payload["source_run_attempt"]), + source_event_name=str(payload["source_event_name"]), + source_event_action=str(payload["source_event_action"]), + source_event_key=str(payload["source_event_key"]), + pr_number=int(payload["pr_number"]), + ) + + +def build_deferred_comment_replay_context( + payload: DeferredCommentPayload | _LegacyDeferredIssueCommentPayloadV2 | _LegacyDeferredReviewCommentPayloadV2, + *, + expected_event_name: str, + live_comment_endpoint: str, +) -> DeferredCommentReplayContext: + if payload.identity.source_event_key != f"{expected_event_name}:{payload.comment_id}": + raise RuntimeError("Deferred comment artifact source_event_key mismatch") + return DeferredCommentReplayContext( + payload=payload, + expected_event_name=expected_event_name, + live_comment_endpoint=live_comment_endpoint, + ) + + +def build_deferred_review_replay_context( + payload: DeferredReviewPayload, + *, + expected_event_action: str, +) -> DeferredReviewReplayContext: + expected_prefix = "pull_request_review:" if expected_event_action == "submitted" else "pull_request_review_dismissed:" + if payload.identity.source_event_action != expected_event_action: + raise RuntimeError("Deferred review artifact action mismatch") + if payload.identity.source_event_key != f"{expected_prefix}{payload.review_id}": + raise RuntimeError(f"Deferred review-{expected_event_action} artifact source_event_key mismatch") + return DeferredReviewReplayContext(payload=payload) + + +def _validate_deferred_comment_artifact(payload: dict) -> None: + if payload.get("schema_version") == 3: + required = { + "payload_kind", + "schema_version", + "source_run_id", + "source_run_attempt", + "source_event_name", + "source_event_action", + "source_event_key", + "pr_number", + "comment_id", + "comment_body", + "comment_created_at", + "comment_author", + "comment_author_id", + "comment_user_type", + "comment_sender_type", + "comment_performed_via_github_app", + "issue_author", + "issue_state", + "issue_labels", + } + missing = sorted(required - set(payload)) + if missing: + raise RuntimeError("Deferred comment artifact missing required fields: " + ", ".join(missing)) + return + if payload.get("schema_version") != 2: + raise RuntimeError("Deferred comment artifact schema_version is not accepted by V18 reconcile") + required = { + "schema_version", + "source_run_id", + "source_run_attempt", + "source_event_name", + "source_event_action", + "source_event_key", + "pr_number", + "comment_id", + "comment_class", + "has_non_command_text", + "source_body_digest", + "source_created_at", + } + missing = sorted(required - set(payload)) + if missing: + raise RuntimeError("Deferred comment artifact missing required fields: " + ", ".join(missing)) + if not isinstance(payload.get("comment_id"), int) or not isinstance(payload.get("pr_number"), int): + raise RuntimeError("Deferred comment artifact comment_id and pr_number must be integers") + if not isinstance(payload.get("comment_class"), str) or not isinstance(payload.get("has_non_command_text"), bool): + raise RuntimeError("Deferred comment artifact parse fields are malformed") + if not isinstance(payload.get("source_body_digest"), str) or not isinstance(payload.get("source_created_at"), str): + raise RuntimeError("Deferred comment artifact source digest or timestamp is malformed") + + +def _validate_deferred_review_artifact(payload: dict) -> None: + if payload.get("schema_version") == 3: + required = { + "payload_kind", + "schema_version", + "source_run_id", + "source_run_attempt", + "source_event_name", + "source_event_action", + "source_event_key", + "pr_number", + "review_id", + } + missing = sorted(required - set(payload)) + if missing: + raise RuntimeError("Deferred review artifact missing required fields: " + ", ".join(missing)) + return + required = { + "schema_version", + "source_run_id", + "source_run_attempt", + "source_event_name", + "source_event_action", + "source_event_key", + "pr_number", + "review_id", + } + missing = sorted(required - set(payload)) + if missing: + raise RuntimeError("Deferred review artifact missing required fields: " + ", ".join(missing)) + if payload.get("schema_version") != 2: + raise RuntimeError("Deferred review artifact schema_version is not accepted by V18 reconcile") + if not isinstance(payload.get("review_id"), int) or not isinstance(payload.get("pr_number"), int): + raise RuntimeError("Deferred review artifact review_id and pr_number must be integers") + + +def _validate_deferred_review_comment_artifact(payload: dict) -> None: + if payload.get("schema_version") == 3: + _validate_deferred_comment_artifact(payload) + return + required = { + "schema_version", + "source_run_id", + "source_run_attempt", + "source_event_name", + "source_event_action", + "source_event_key", + "pr_number", + "comment_id", + "comment_class", + "has_non_command_text", + "source_body_digest", + "source_created_at", + } + missing = sorted(required - set(payload)) + if missing: + raise RuntimeError("Deferred review-comment artifact missing required fields: " + ", ".join(missing)) + if payload.get("schema_version") != 2: + raise RuntimeError("Deferred review-comment artifact schema_version is not accepted by V18 reconcile") + if not isinstance(payload.get("comment_id"), int) or not isinstance(payload.get("pr_number"), int): + raise RuntimeError("Deferred review-comment artifact comment_id and pr_number must be integers") + if not isinstance(payload.get("comment_class"), str) or not isinstance(payload.get("has_non_command_text"), bool): + raise RuntimeError("Deferred review-comment artifact parse fields are malformed") + if not isinstance(payload.get("source_body_digest"), str) or not isinstance(payload.get("source_created_at"), str): + raise RuntimeError("Deferred review-comment artifact source digest or timestamp is malformed") + + +def _validate_observer_noop_payload(payload: dict) -> None: + required = { + "schema_version", + "kind", + "reason", + "source_run_id", + "source_run_attempt", + "source_event_name", + "source_event_action", + "source_event_key", + "pr_number", + } + missing = sorted(required - set(payload)) + if missing: + raise RuntimeError("Observer no-op payload missing required fields: " + ", ".join(missing)) + if payload.get("schema_version") != 1: + raise RuntimeError("Observer no-op payload schema_version is not accepted") + if payload.get("kind") != "observer_noop": + raise RuntimeError("Observer no-op payload kind mismatch") + if not isinstance(payload.get("reason"), str) or not payload.get("reason"): + raise RuntimeError("Observer no-op payload reason must be a non-empty string") + if not isinstance(payload.get("pr_number"), int): + raise RuntimeError("Observer no-op payload pr_number must be an integer") + + +def parse_deferred_context_payload(payload: dict) -> DeferredReviewPayload | DeferredCommentPayload | ObserverNoopPayload | _LegacyDeferredIssueCommentPayloadV2 | _LegacyDeferredReviewCommentPayloadV2: + if not isinstance(payload, dict): + raise RuntimeError("Deferred context payload must be a JSON object") + + if payload.get("kind") == "observer_noop": + _validate_observer_noop_payload(payload) + return ObserverNoopPayload( + identity=_build_deferred_identity(payload), + reason=str(payload["reason"]), + raw_payload=payload, + ) + identity = _build_deferred_identity(payload) + if identity.payload_kind == DeferredPayloadKind.DEFERRED_COMMENT or identity.payload_kind == DeferredPayloadKind.DEFERRED_REVIEW_COMMENT: + if identity.schema_version == 3: + _validate_deferred_comment_artifact(payload) + return DeferredCommentPayload( + identity=identity, + comment_id=int(payload["comment_id"]), + comment_body=str(payload["comment_body"]), + comment_created_at=str(payload["comment_created_at"]), + comment_author=str(payload["comment_author"]), + comment_author_id=int(payload["comment_author_id"]), + comment_user_type=str(payload["comment_user_type"]), + comment_sender_type=str(payload["comment_sender_type"]), + comment_installation_id=(str(payload["comment_installation_id"]) if payload.get("comment_installation_id") else None), + comment_performed_via_github_app=bool(payload["comment_performed_via_github_app"]), + issue_author=str(payload["issue_author"]), + issue_state=str(payload["issue_state"]), + issue_labels=tuple(str(label) for label in payload["issue_labels"]), + raw_payload=payload, + ) + if identity.payload_kind is DeferredPayloadKind.DEFERRED_COMMENT: + _validate_deferred_comment_artifact(payload) + return _LegacyDeferredIssueCommentPayloadV2( + identity=identity, + comment_id=int(payload["comment_id"]), + comment_class=str(payload["comment_class"]), + has_non_command_text=bool(payload["has_non_command_text"]), + source_body_digest=str(payload["source_body_digest"]), + source_created_at=str(payload["source_created_at"]), + actor_login=(str(payload["actor_login"]) if payload.get("actor_login") is not None else None), + raw_payload=payload, + ) + _validate_deferred_review_comment_artifact(payload) + return _LegacyDeferredReviewCommentPayloadV2( + identity=identity, + comment_id=int(payload["comment_id"]), + comment_class=str(payload["comment_class"]), + has_non_command_text=bool(payload["has_non_command_text"]), + source_body_digest=str(payload["source_body_digest"]), + source_created_at=str(payload["source_created_at"]), + actor_login=(str(payload["actor_login"]) if payload.get("actor_login") is not None else None), + raw_payload=payload, + ) + if identity.payload_kind == DeferredPayloadKind.DEFERRED_REVIEW_SUBMITTED or identity.payload_kind == DeferredPayloadKind.DEFERRED_REVIEW_DISMISSED: + _validate_deferred_review_artifact(payload) + if identity.schema_version != 3: + raise RuntimeError("Deferred review artifact schema_version is not accepted by V18 reconcile") + return DeferredReviewPayload( + identity=identity, + review_id=int(payload["review_id"]), + source_submitted_at=(str(payload["source_submitted_at"]) if payload.get("source_submitted_at") is not None else None), + source_review_state=(str(payload["source_review_state"]) if payload.get("source_review_state") is not None else None), + source_commit_id=(str(payload["source_commit_id"]) if payload.get("source_commit_id") is not None else None), + actor_login=(str(payload["actor_login"]) if payload.get("actor_login") is not None else None), + raw_payload=payload, + ) + raise RuntimeError("Unsupported deferred workflow_run payload") + + +def expected_observer_identity(payload: dict) -> tuple[str, str] | None: + if payload.get("schema_version") == 3: + if payload.get("payload_kind") == DeferredPayloadKind.DEFERRED_COMMENT.value: + return ("Reviewer Bot PR Comment Router", ".github/workflows/reviewer-bot-pr-comment-router.yml") + if payload.get("payload_kind") == DeferredPayloadKind.DEFERRED_REVIEW_COMMENT.value: + return ( + "Reviewer Bot PR Review Comment Observer", + ".github/workflows/reviewer-bot-pr-review-comment-observer.yml", + ) + if payload.get("payload_kind") == DeferredPayloadKind.DEFERRED_REVIEW_SUBMITTED.value: + return ( + "Reviewer Bot PR Review Submitted Observer", + ".github/workflows/reviewer-bot-pr-review-submitted-observer.yml", + ) + if payload.get("payload_kind") == DeferredPayloadKind.DEFERRED_REVIEW_DISMISSED.value: + return ( + "Reviewer Bot PR Review Dismissed Observer", + ".github/workflows/reviewer-bot-pr-review-dismissed-observer.yml", + ) + event_name = payload.get("source_event_name") + event_action = payload.get("source_event_action") + if event_name == "issue_comment" and event_action == "created": + return ( + "Reviewer Bot PR Comment Observer", + ".github/workflows/reviewer-bot-pr-comment-observer.yml", + ) + if event_name == "pull_request_review" and event_action == "submitted": + return ( + "Reviewer Bot PR Review Submitted Observer", + ".github/workflows/reviewer-bot-pr-review-submitted-observer.yml", + ) + if event_name == "pull_request_review" and event_action == "dismissed": + return ( + "Reviewer Bot PR Review Dismissed Observer", + ".github/workflows/reviewer-bot-pr-review-dismissed-observer.yml", + ) + if event_name == "pull_request_review_comment" and event_action == "created": + return ( + "Reviewer Bot PR Review Comment Observer", + ".github/workflows/reviewer-bot-pr-review-comment-observer.yml", + ) + raise RuntimeError("Unsupported deferred workflow identity") + + +def validate_triggering_run_identity(bot, payload: dict) -> None: + triggering_id = bot.get_config_value("WORKFLOW_RUN_TRIGGERING_ID").strip() + if triggering_id and str(payload.get("source_run_id")) != triggering_id: + raise RuntimeError("Deferred artifact run_id mismatch") + triggering_attempt = bot.get_config_value("WORKFLOW_RUN_TRIGGERING_ATTEMPT").strip() + if triggering_attempt and str(payload.get("source_run_attempt")) != triggering_attempt: + raise RuntimeError("Deferred artifact run_attempt mismatch") + if bot.get_config_value("WORKFLOW_RUN_TRIGGERING_CONCLUSION").strip() != "success": + raise RuntimeError("Triggering observer workflow did not conclude successfully") + + +def validate_workflow_run_artifact_identity(bot, payload: dict) -> None: + validate_triggering_run_identity(bot, payload) + + +def artifact_expected_name(payload: dict) -> str: + payload_kind = payload.get("payload_kind") + run_id = payload.get("source_run_id") + run_attempt = payload.get("source_run_attempt") + if payload_kind == DeferredPayloadKind.DEFERRED_COMMENT.value: + return f"reviewer-bot-comment-context-{run_id}-attempt-{run_attempt}" + if payload_kind == DeferredPayloadKind.DEFERRED_REVIEW_SUBMITTED.value: + return f"reviewer-bot-review-submitted-context-{run_id}-attempt-{run_attempt}" + if payload_kind == DeferredPayloadKind.DEFERRED_REVIEW_DISMISSED.value: + return f"reviewer-bot-review-dismissed-context-{run_id}-attempt-{run_attempt}" + if payload_kind == DeferredPayloadKind.DEFERRED_REVIEW_COMMENT.value: + return f"reviewer-bot-review-comment-context-{run_id}-attempt-{run_attempt}" + event_name = payload.get("source_event_name") + event_action = payload.get("source_event_action") + if event_name == "issue_comment" and event_action == "created": + return f"reviewer-bot-comment-context-{run_id}-attempt-{run_attempt}" + if event_name == "pull_request_review_comment" and event_action == "created": + return f"reviewer-bot-review-comment-context-{run_id}-attempt-{run_attempt}" + raise RuntimeError("Unsupported deferred artifact naming") + + +def artifact_expected_payload_name(payload: dict) -> str: + payload_kind = payload.get("payload_kind") + if payload_kind == DeferredPayloadKind.DEFERRED_COMMENT.value: + return "deferred-comment.json" + if payload_kind == DeferredPayloadKind.DEFERRED_REVIEW_SUBMITTED.value: + return "deferred-review-submitted.json" + if payload_kind == DeferredPayloadKind.DEFERRED_REVIEW_DISMISSED.value: + return "deferred-review-dismissed.json" + if payload_kind == DeferredPayloadKind.DEFERRED_REVIEW_COMMENT.value: + return "deferred-review-comment.json" + event_name = payload.get("source_event_name") + event_action = payload.get("source_event_action") + if event_name == "issue_comment" and event_action == "created": + return "deferred-comment.json" + if event_name == "pull_request_review_comment" and event_action == "created": + return "deferred-review-comment.json" + raise RuntimeError("Unsupported deferred payload path") diff --git a/scripts/reviewer_bot_lib/reconcile_reads.py b/scripts/reviewer_bot_lib/reconcile_reads.py new file mode 100644 index 000000000..ccc3c9b36 --- /dev/null +++ b/scripts/reviewer_bot_lib/reconcile_reads.py @@ -0,0 +1,105 @@ +"""Live read helpers for deferred reconcile flows.""" + +from __future__ import annotations + +from dataclasses import dataclass + + +class ReconcileReadError(RuntimeError): + def __init__(self, message: str, *, failure_kind: str | None = None): + super().__init__(message) + self.failure_kind = failure_kind + + +@dataclass(frozen=True) +class LivePrReplayContext: + issue_author: str + issue_labels: tuple[str, ...] + + +@dataclass(frozen=True) +class LiveCommentReplayContext: + comment_author: str + comment_user_type: str + comment_sender_type: str + comment_installation_id: str + comment_performed_via_github_app: bool + + +def read_reconcile_object(bot, endpoint: str, *, label: str) -> dict: + try: + response = bot.github_api_request("GET", endpoint, retry_policy="idempotent_read") + except SystemExit: + payload = bot.github_api("GET", endpoint) + if not isinstance(payload, dict): + raise ReconcileReadError(f"{label} unavailable", failure_kind="unavailable") + return payload + if not response.ok: + failure_kind = response.failure_kind + if failure_kind == "not_found": + raise ReconcileReadError(f"{label} not found", failure_kind=failure_kind) + raise ReconcileReadError(f"{label} unavailable", failure_kind=failure_kind) + if not isinstance(response.payload, dict): + raise ReconcileReadError(f"{label} payload invalid", failure_kind="invalid_payload") + return response.payload + + +def read_optional_reconcile_object(bot, endpoint: str, *, label: str) -> dict | None: + try: + return read_reconcile_object(bot, endpoint, label=label) + except ReconcileReadError as exc: + if exc.failure_kind == "not_found": + return None + raise + + +def read_reconcile_reviews(bot, issue_number: int) -> list[dict]: + reviews = bot.github.get_pull_request_reviews(issue_number) + if reviews is None: + raise ReconcileReadError(f"live reviews for PR #{issue_number} unavailable", failure_kind="unavailable") + if not isinstance(reviews, list): + raise ReconcileReadError(f"live reviews for PR #{issue_number} payload invalid", failure_kind="invalid_payload") + return reviews + + +def read_live_pr_replay_context(bot, pr_number: int) -> LivePrReplayContext: + pull_request = read_reconcile_object(bot, f"pulls/{pr_number}", label=f"live PR #{pr_number} for reconcile context") + author = pull_request.get("user") + if not isinstance(author, dict): + raise RuntimeError(f"Live PR #{pr_number} is missing author metadata") + author_login = author.get("login") + if not isinstance(author_login, str) or not author_login.strip(): + raise RuntimeError(f"Live PR #{pr_number} is missing a valid author login") + labels = pull_request.get("labels") + if labels is None: + labels = [] + if not isinstance(labels, list): + raise RuntimeError(f"Live PR #{pr_number} labels are malformed") + label_names: list[str] = [] + for label in labels: + if not isinstance(label, dict): + raise RuntimeError(f"Live PR #{pr_number} contains malformed label metadata") + name = label.get("name") + if not isinstance(name, str): + raise RuntimeError(f"Live PR #{pr_number} contains a label without a valid name") + label_names.append(name) + return LivePrReplayContext(issue_author=author_login, issue_labels=tuple(label_names)) + + +def read_live_comment_replay_context(live_comment: dict, payload: dict) -> LiveCommentReplayContext: + user = live_comment.get("user") + if not isinstance(user, dict): + raise RuntimeError("Live deferred comment user metadata is unavailable") + comment_author = user.get("login") or payload.get("actor_login") or "" + if not isinstance(comment_author, str) or not comment_author.strip(): + raise RuntimeError("Live deferred comment author login is unavailable") + comment_user_type = user.get("type") + if not isinstance(comment_user_type, str) or not comment_user_type.strip(): + raise RuntimeError("Live deferred comment user type is unavailable") + return LiveCommentReplayContext( + comment_author=comment_author, + comment_user_type=comment_user_type, + comment_sender_type=comment_user_type, + comment_installation_id="", + comment_performed_via_github_app=bool(live_comment.get("performed_via_github_app")), + ) diff --git a/scripts/reviewer_bot_lib/repair_records.py b/scripts/reviewer_bot_lib/repair_records.py new file mode 100644 index 000000000..c026d8102 --- /dev/null +++ b/scripts/reviewer_bot_lib/repair_records.py @@ -0,0 +1,76 @@ +"""Repair-marker sidecar storage helpers.""" + +from __future__ import annotations + +from copy import deepcopy + +_REPAIR_MARKER_KEYS = ( + "review_repair", + "head_observation_repair", + "status_label_projection", +) + + +def _sidecars(review_data: dict) -> dict: + sidecars = review_data.get("sidecars") + if not isinstance(sidecars, dict): + sidecars = {} + review_data["sidecars"] = sidecars + return sidecars + + +def repair_markers(review_data: dict) -> dict: + markers = _sidecars(review_data).get("repair_markers") + canonical = dict.fromkeys(_REPAIR_MARKER_KEYS) + if isinstance(markers, dict): + for key in _REPAIR_MARKER_KEYS: + if isinstance(markers.get(key), dict): + canonical[key] = deepcopy(markers[key]) + _sidecars(review_data)["repair_markers"] = canonical + markers = canonical + return markers + + +def projection_repair_marker(reason: str, recorded_at: str) -> dict: + return { + "kind": "projection_failure", + "reason": reason, + "recorded_at": recorded_at, + } + + +def maintenance_repair_marker(*, reason: str, failure_kind: str | None, recorded_at: str) -> dict: + return { + "kind": "live_read_failure", + "reason": reason, + "failure_kind": failure_kind, + "recorded_at": recorded_at, + } + + +def store_repair_marker(review_data: dict, key: str, marker: dict) -> bool: + markers = repair_markers(review_data) + existing = markers.get(key) + if not isinstance(marker, dict): + return False + if isinstance(existing, dict) and { + name: value for name, value in existing.items() if name != "recorded_at" + } == { + name: value for name, value in marker.items() if name != "recorded_at" + }: + return False + markers[key] = deepcopy(marker) + return True + + +def load_repair_marker(review_data: dict, key: str) -> dict | None: + marker = repair_markers(review_data).get(key) + return marker if isinstance(marker, dict) else None + + +def clear_repair_marker(review_data: dict, key: str) -> bool: + markers = repair_markers(review_data) + if key not in markers: + return False + markers[key] = None + return True diff --git a/scripts/reviewer_bot_lib/retrying.py b/scripts/reviewer_bot_lib/retrying.py new file mode 100644 index 000000000..9b65ed04e --- /dev/null +++ b/scripts/reviewer_bot_lib/retrying.py @@ -0,0 +1,48 @@ +"""Shared retry helpers for reviewer-bot transport and state workflows.""" + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Protocol + +RETRY_POLICY_NONE = "none" +RETRY_POLICY_IDEMPOTENT_READ = "idempotent_read" + + +class JitterSource(Protocol): + def uniform(self, lower: float, upper: float) -> float: ... + + +@dataclass(frozen=True) +class RetrySpec: + retry_policy: str + max_attempts: int + base_delay_seconds: float + max_delay_seconds: float = 8.0 + + +def is_retryable_status(status_code: int | None) -> bool: + return status_code == 429 or (status_code is not None and status_code >= 500) + + +def additional_attempts_for_policy(retry_policy: str, retry_limit: int) -> int: + if retry_policy == RETRY_POLICY_NONE: + return 0 + if retry_policy == RETRY_POLICY_IDEMPOTENT_READ: + return retry_limit + raise ValueError(f"Unsupported retry policy: {retry_policy}") + + +def max_attempts_for_policy(retry_policy: str, retry_limit: int) -> int: + return 1 + additional_attempts_for_policy(retry_policy, retry_limit) + + +def bounded_exponential_delay( + base_delay_seconds: float, + retry_attempt: int, + *, + jitter: JitterSource, + max_delay_seconds: float = 8.0, +) -> float: + bounded_base = min(base_delay_seconds * (2 ** max(retry_attempt - 1, 0)), max_delay_seconds) + return bounded_base + jitter.uniform(0, bounded_base) diff --git a/scripts/reviewer_bot_lib/review_state.py b/scripts/reviewer_bot_lib/review_state.py new file mode 100644 index 000000000..04d8e9b8f --- /dev/null +++ b/scripts/reviewer_bot_lib/review_state.py @@ -0,0 +1,143 @@ +"""Public mutable review-state operations shared across reviewer-bot modules.""" + +from __future__ import annotations + +from datetime import datetime, timezone + +from scripts.reviewer_bot_core import ( + live_review_support, + review_state_live_repair, + review_state_machine, +) + + +def _now_iso() -> str: + return datetime.now(timezone.utc).isoformat() + + +def ensure_review_entry(state: dict, issue_number: int, create: bool = False) -> dict | None: + return review_state_machine.ensure_review_entry(state, issue_number, create=create) + + +def clear_transition_timers(review_data: dict) -> None: + review_state_machine.clear_transition_timers(review_data) + + +def record_reviewer_activity(review_data: dict, timestamp: str) -> bool: + return review_state_machine.record_reviewer_activity(review_data, timestamp) + + +def record_transition_notice_sent(review_data: dict, timestamp: str) -> None: + review_state_machine.record_transition_notice_sent(review_data, timestamp) + + +def set_current_reviewer( + state: dict, + issue_number: int, + reviewer: str, + assignment_method: str = "round-robin", +) -> None: + review_state_machine.set_current_reviewer( + state, + issue_number, + reviewer, + now=_now_iso(), + assignment_method=assignment_method, + ) + + +def semantic_key_seen(review_data: dict, channel_name: str, semantic_key: str) -> bool: + return review_state_machine.semantic_key_seen(review_data, channel_name, semantic_key) + + +def accept_channel_event( + review_data: dict, + channel_name: str, + *, + semantic_key: str, + timestamp: str, + actor: str | None = None, + reviewed_head_sha: str | None = None, + source_precedence: int = 0, + payload: dict | None = None, + dismissal_only: bool = False, +) -> bool: + return review_state_machine.accept_channel_event( + review_data, + channel_name, + semantic_key=semantic_key, + timestamp=timestamp, + actor=actor, + reviewed_head_sha=reviewed_head_sha, + source_precedence=source_precedence, + payload=payload, + dismissal_only=dismissal_only, + ) + + +def update_reviewer_activity(state: dict, issue_number: int, reviewer: str) -> bool: + return review_state_machine.update_reviewer_activity(state, issue_number, reviewer, now=_now_iso()) + + +def mark_review_complete(state: dict, issue_number: int, reviewer: str | None, source: str) -> bool: + return review_state_machine.mark_review_complete(state, issue_number, reviewer, source, completed_at=_now_iso()) + + +def get_current_cycle_boundary(bot, review_data: dict) -> datetime | None: + return live_review_support.get_current_cycle_boundary(review_data, parse_timestamp=bot.parse_iso8601_timestamp) + + +def accept_reviewer_review_from_live_review(review_data: dict, review: dict, *, actor: str | None = None) -> bool: + return review_state_live_repair.accept_reviewer_review_from_live_review( + review_data, + review, + actor=actor, + ) + + +def refresh_reviewer_review_from_live_preferred_review( + bot, + issue_number: int, + review_data: dict, + *, + pull_request: dict | None = None, + reviews: list[dict] | None = None, + actor: str | None = None, +) -> tuple[bool, dict | None]: + return review_state_live_repair.refresh_reviewer_review_from_live_preferred_review( + bot, + issue_number, + review_data, + pull_request=pull_request, + reviews=reviews, + actor=actor, + ) + + +def repair_missing_reviewer_review_state(bot, issue_number: int, review_data: dict, *, reviews: list[dict] | None = None) -> bool: + return review_state_live_repair.repair_missing_reviewer_review_state( + bot, + issue_number, + review_data, + reviews=reviews, + ) + + +def list_open_tracked_review_items(state: dict) -> list[int]: + numbers: set[int] = set() + active_reviews = state.get("active_reviews") + if not isinstance(active_reviews, dict): + return [] + for issue_key, review_data in active_reviews.items(): + if not isinstance(review_data, dict): + continue + current_reviewer = review_data.get("current_reviewer") + if not isinstance(current_reviewer, str) or not current_reviewer.strip(): + continue + try: + issue_number = int(issue_key) + except (TypeError, ValueError): + continue + if issue_number > 0: + numbers.add(issue_number) + return sorted(numbers) diff --git a/scripts/reviewer_bot_lib/reviews.py b/scripts/reviewer_bot_lib/reviews.py index 661d8718e..89d883ba3 100644 --- a/scripts/reviewer_bot_lib/reviews.py +++ b/scripts/reviewer_bot_lib/reviews.py @@ -3,31 +3,32 @@ from __future__ import annotations from datetime import datetime, timezone -from typing import Any, Iterable +from typing import Iterable from urllib.parse import quote +from scripts.reviewer_bot_core import live_review_support + +from . import review_state from .config import ( MANDATORY_TRIAGE_APPROVER_LABEL, MANDATORY_TRIAGE_ESCALATION_TEMPLATE, MANDATORY_TRIAGE_SATISFIED_TEMPLATE, - STATUS_AWAITING_CONTRIBUTOR_RESPONSE_LABEL, - STATUS_AWAITING_REVIEWER_RESPONSE_LABEL, - STATUS_AWAITING_WRITE_APPROVAL_LABEL, STATUS_LABELS, ) +from .reviews_projection import ( + desired_labels_from_response_state, +) + + +def _log(bot, level: str, message: str, **fields) -> None: + bot.logger.event(level, message, **fields) def _now_iso() -> str: return datetime.now(timezone.utc).isoformat() -def parse_github_timestamp(value: str | None) -> datetime | None: - if not isinstance(value, str) or not value: - return None - try: - return datetime.fromisoformat(value.replace("Z", "+00:00")) - except ValueError: - return None +parse_github_timestamp = live_review_support.parse_github_timestamp def get_latest_review_by_reviewer(bot, reviews: list[dict], reviewer: str) -> dict | None: @@ -37,7 +38,7 @@ def get_latest_review_by_reviewer(bot, reviews: list[dict], reviewer: str) -> di author = review.get("user", {}).get("login") if not isinstance(author, str) or author.lower() != reviewer.lower(): continue - submitted_at = bot.parse_github_timestamp(review.get("submitted_at")) + submitted_at = parse_github_timestamp(review.get("submitted_at")) if submitted_at is None: continue review_id = str(review.get("id", "")) @@ -58,11 +59,11 @@ def get_latest_valid_current_reviewer_review_for_cycle( current_reviewer = review_data.get("current_reviewer") if not isinstance(current_reviewer, str) or not current_reviewer.strip(): return None - boundary = get_current_cycle_boundary(bot, review_data) + boundary = review_state.get_current_cycle_boundary(bot, review_data) if boundary is None: return None if reviews is None: - reviews = bot.get_pull_request_reviews(issue_number) + reviews = bot.github.get_pull_request_reviews(issue_number) if reviews is None: return None latest_review = None @@ -90,483 +91,114 @@ def get_latest_valid_current_reviewer_review_for_cycle( return latest_review -def get_valid_current_reviewer_reviews_for_cycle( - bot, - issue_number: int, - review_data: dict, - *, - reviews: list[dict] | None = None, -) -> list[dict]: - current_reviewer = review_data.get("current_reviewer") - if not isinstance(current_reviewer, str) or not current_reviewer.strip(): - return [] - boundary = get_current_cycle_boundary(bot, review_data) - if boundary is None: - return [] - if reviews is None: - reviews = bot.get_pull_request_reviews(issue_number) - if reviews is None: - return [] - valid_reviews: list[dict] = [] - for review in reviews: - if not isinstance(review, dict): - continue - author = review.get("user", {}).get("login") - if not isinstance(author, str) or author.lower() != current_reviewer.lower(): - continue - state = str(review.get("state", "")).upper() - if state not in {"APPROVED", "COMMENTED", "CHANGES_REQUESTED"}: - continue - submitted_at = parse_github_timestamp(review.get("submitted_at")) - if submitted_at is None or submitted_at < boundary: - continue - commit_id = review.get("commit_id") - if not isinstance(commit_id, str) or not commit_id.strip(): - continue - valid_reviews.append(review) - return valid_reviews - - -def _review_sort_key(review: dict) -> tuple[datetime, str]: - return ( - parse_github_timestamp(review.get("submitted_at")) or datetime.min.replace(tzinfo=timezone.utc), - str(review.get("id", "")), - ) - - -def _review_matches_head(review: dict, current_head: str | None) -> bool: - commit_id = review.get("commit_id") if isinstance(review, dict) else None - return isinstance(commit_id, str) and isinstance(current_head, str) and commit_id.strip() == current_head.strip() - - -def get_preferred_current_reviewer_review_for_cycle( - bot, - issue_number: int, - review_data: dict, - *, - pull_request: dict | None = None, - reviews: list[dict] | None = None, -) -> dict | None: - valid_reviews = get_valid_current_reviewer_reviews_for_cycle(bot, issue_number, review_data, reviews=reviews) - if not valid_reviews: - return None - if len(valid_reviews) == 1: - return valid_reviews[0] - if pull_request is None: - pull_request = bot.github_api("GET", f"pulls/{issue_number}") - head = pull_request.get("head") if isinstance(pull_request, dict) else None - current_head = head.get("sha") if isinstance(head, dict) else None - current_head_reviews = [review for review in valid_reviews if _review_matches_head(review, current_head)] - candidates = current_head_reviews or valid_reviews - return max(candidates, key=_review_sort_key, default=None) - - -def build_reviewer_review_record_from_live_review(review: dict, *, actor: str | None = None) -> dict | None: - if not isinstance(review, dict): - return None - review_id = review.get("id") - submitted_at = review.get("submitted_at") - commit_id = review.get("commit_id") - author = actor if isinstance(actor, str) and actor.strip() else review.get("user", {}).get("login") - if not isinstance(review_id, int) or not isinstance(submitted_at, str) or not isinstance(commit_id, str): - return None - if not isinstance(author, str) or not author.strip(): - return None - return { - "semantic_key": f"pull_request_review:{review_id}", - "timestamp": submitted_at, - "actor": author, - "reviewed_head_sha": commit_id, - "source_precedence": 1, - "payload": {}, - } - - -def accept_reviewer_review_from_live_review(review_data: dict, review: dict, *, actor: str | None = None) -> bool: - record = build_reviewer_review_record_from_live_review(review, actor=actor) - if record is None: - return False - return accept_channel_event( - review_data, - "reviewer_review", - semantic_key=record["semantic_key"], - timestamp=record["timestamp"], - actor=record["actor"], - reviewed_head_sha=record["reviewed_head_sha"], - source_precedence=record["source_precedence"], - payload=record["payload"], - ) - - -def refresh_reviewer_review_from_live_preferred_review( +def resolve_pr_approval_state( bot, issue_number: int, review_data: dict, *, pull_request: dict | None = None, reviews: list[dict] | None = None, - actor: str | None = None, -) -> tuple[bool, dict | None]: - preferred_review = get_preferred_current_reviewer_review_for_cycle( - bot, +) -> tuple[dict | None, dict | None, str | None]: + completion, write_approval = bot.rebuild_pr_approval_state( issue_number, review_data, pull_request=pull_request, reviews=reviews, ) - if preferred_review is None: - return False, None - record = build_reviewer_review_record_from_live_review(preferred_review, actor=actor or review_data.get("current_reviewer")) - if record is None: - return False, None - channel = _ensure_channel_map(review_data, "reviewer_review") - changed = False - if record["semantic_key"] not in channel["seen_keys"]: - channel["seen_keys"].append(record["semantic_key"]) - changed = True - if channel.get("accepted") != record: - channel["accepted"] = record - changed = True - submitted_at = preferred_review.get("submitted_at") - if isinstance(submitted_at, str): - record_reviewer_activity(review_data, submitted_at) - return changed, preferred_review - - -def repair_missing_reviewer_review_state(bot, issue_number: int, review_data: dict, *, reviews: list[dict] | None = None) -> bool: - changed, _ = refresh_reviewer_review_from_live_preferred_review( - bot, - issue_number, - review_data, - reviews=reviews, - actor=review_data.get("current_reviewer"), - ) - return changed - - -def find_triage_approval_after(bot, reviews: list[dict], since: datetime | None) -> tuple[str, datetime] | None: - permission_cache: dict[str, bool] = {} - approvals: list[tuple[datetime, str, str]] = [] - for review in reviews: - state = str(review.get("state", "")).upper() - if state != "APPROVED": - continue - author = review.get("user", {}).get("login") - if not isinstance(author, str) or not author: - continue - submitted_at = bot.parse_github_timestamp(review.get("submitted_at")) - if submitted_at is None: - continue - if since is not None and submitted_at <= since: - continue - approvals.append((submitted_at, str(review.get("id", "")), author)) - approvals.sort(key=lambda item: (item[0], item[1])) - for submitted_at, _, author in approvals: - cache_key = author.lower() - if cache_key not in permission_cache: - permission_cache[cache_key] = bot.is_triage_or_higher(author) - if permission_cache[cache_key]: - return author, submitted_at - return None - - -def _ensure_channel_map(review_entry: dict, name: str) -> dict: - value = review_entry.get(name) - if not isinstance(value, dict): - value = {"accepted": None, "seen_keys": []} - review_entry[name] = value - if not isinstance(value.get("seen_keys"), list): - value["seen_keys"] = [] - return value - - -def _ensure_dict(review_entry: dict, name: str) -> dict: - value = review_entry.get(name) - if not isinstance(value, dict): - value = {} - review_entry[name] = value - return value - - -def ensure_review_entry(state: dict, issue_number: int, create: bool = False) -> dict | None: - issue_key = str(issue_number) - if "active_reviews" not in state or not isinstance(state.get("active_reviews"), dict): - state["active_reviews"] = {} - review_entry = state["active_reviews"].get(issue_key) - if review_entry is None: - if not create: - return None - review_entry = {} - state["active_reviews"][issue_key] = review_entry - elif isinstance(review_entry, list): - review_entry = {"skipped": review_entry} - state["active_reviews"][issue_key] = review_entry - if not isinstance(review_entry, dict): - return None - - defaults: dict[str, Any] = { - "skipped": [], - "current_reviewer": None, - "cycle_started_at": None, - "active_cycle_started_at": None, - "assigned_at": None, - "active_head_sha": None, - "last_reviewer_activity": None, - "transition_warning_sent": None, - "transition_notice_sent_at": None, - "assignment_method": None, - "review_completed_at": None, - "review_completed_by": None, - "review_completion_source": None, - "mandatory_approver_required": False, - "mandatory_approver_label_applied_at": None, - "mandatory_approver_pinged_at": None, - "mandatory_approver_satisfied_by": None, - "mandatory_approver_satisfied_at": None, - "repair_needed": None, - "overdue_anchor": None, - } - for field, default in defaults.items(): - if field not in review_entry: - review_entry[field] = default - if not isinstance(review_entry.get("skipped"), list): - review_entry["skipped"] = [] - - for channel in ( - "reviewer_comment", - "reviewer_review", - "contributor_comment", - "contributor_revision", - "review_dismissal", - ): - _ensure_channel_map(review_entry, channel) - for mapping in ( - "deferred_gaps", - "observer_discovery_watermarks", - "pending_privileged_commands", - "current_cycle_completion", - "current_cycle_write_approval", - ): - _ensure_dict(review_entry, mapping) - reconciled_source_events = review_entry.get("reconciled_source_events") - if not isinstance(reconciled_source_events, list): - review_entry["reconciled_source_events"] = [] - return review_entry - - -def _reset_cycle_state(review_data: dict) -> None: - for channel in ( - "reviewer_comment", - "reviewer_review", - "contributor_comment", - "contributor_revision", - "review_dismissal", - ): - review_data[channel] = {"accepted": None, "seen_keys": []} - review_data["current_cycle_completion"] = {} - review_data["current_cycle_write_approval"] = {} - review_data["overdue_anchor"] = None - if isinstance(review_data.get("pending_privileged_commands"), dict): - review_data["pending_privileged_commands"] = {} - - -def clear_transition_timers(review_data: dict) -> None: - review_data["transition_warning_sent"] = None - review_data["transition_notice_sent_at"] = None - - -def record_reviewer_activity(review_data: dict, timestamp: str) -> None: - current = parse_github_timestamp(review_data.get("last_reviewer_activity")) - candidate = parse_github_timestamp(timestamp) - if current is None or candidate is None or candidate >= current: - review_data["last_reviewer_activity"] = timestamp - clear_transition_timers(review_data) - - -def record_transition_notice_sent(review_data: dict, timestamp: str) -> None: - review_data["transition_notice_sent_at"] = timestamp - - -def set_current_reviewer( - state: dict, - issue_number: int, - reviewer: str, - assignment_method: str = "round-robin", -) -> None: - now = _now_iso() - review_data = ensure_review_entry(state, issue_number, create=True) - if review_data is None: - return - review_data["current_reviewer"] = reviewer - review_data["cycle_started_at"] = now - review_data["active_cycle_started_at"] = now - review_data["assigned_at"] = now - record_reviewer_activity(review_data, now) - review_data["assignment_method"] = assignment_method - review_data["review_completed_at"] = None - review_data["review_completed_by"] = None - review_data["review_completion_source"] = None - review_data["mandatory_approver_required"] = False - review_data["mandatory_approver_label_applied_at"] = None - review_data["mandatory_approver_pinged_at"] = None - review_data["mandatory_approver_satisfied_by"] = None - review_data["mandatory_approver_satisfied_at"] = None - review_data["active_head_sha"] = None - _reset_cycle_state(review_data) - - -def _semantic_key_seen(review_data: dict, channel_name: str, semantic_key: str) -> bool: - channel = _ensure_channel_map(review_data, channel_name) - return semantic_key in channel["seen_keys"] - - -def _compare_records(left: dict | None, right: dict | None) -> int: - if right is None: - return 1 - if left is None: - return -1 - left_time = parse_github_timestamp(left.get("timestamp")) or datetime.min.replace(tzinfo=timezone.utc) - right_time = parse_github_timestamp(right.get("timestamp")) or datetime.min.replace(tzinfo=timezone.utc) - left_rank = int(left.get("source_precedence", 0)) - right_rank = int(right.get("source_precedence", 0)) - left_key = str(left.get("semantic_key", "")) - right_key = str(right.get("semantic_key", "")) - left_tuple = (left_time, left_rank, left_key) - right_tuple = (right_time, right_rank, right_key) - if left_tuple > right_tuple: - return 1 - if left_tuple < right_tuple: - return -1 - return 0 + if completion is None or write_approval is None: + return None, None, "live_review_state_unknown" + return completion, write_approval, None -def accept_channel_event( +def apply_pr_approval_state( review_data: dict, - channel_name: str, *, - semantic_key: str, - timestamp: str, - actor: str | None = None, - reviewed_head_sha: str | None = None, - source_precedence: int = 0, - payload: dict | None = None, - dismissal_only: bool = False, -) -> bool: - channel = _ensure_channel_map(review_data, channel_name) - if semantic_key in channel["seen_keys"]: - return False - channel["seen_keys"].append(semantic_key) - if dismissal_only: - channel["accepted"] = channel.get("accepted") or { - "semantic_key": semantic_key, - "timestamp": timestamp, - } - return True - candidate = { - "semantic_key": semantic_key, - "timestamp": timestamp, - "actor": actor, - "reviewed_head_sha": reviewed_head_sha, - "source_precedence": source_precedence, - "payload": payload or {}, - } - current = channel.get("accepted") - if _compare_records(candidate, current) >= 0: - channel["accepted"] = candidate - return True - - -def update_reviewer_activity(state: dict, issue_number: int, reviewer: str) -> bool: - review_data = ensure_review_entry(state, issue_number) - if review_data is None: - return False - current_reviewer = review_data.get("current_reviewer") - if not isinstance(current_reviewer, str) or current_reviewer.lower() != reviewer.lower(): - return False - record_reviewer_activity(review_data, _now_iso()) - return True - - -def mark_review_complete(state: dict, issue_number: int, reviewer: str | None, source: str) -> bool: - review_data = ensure_review_entry(state, issue_number, create=True) - if review_data is None: - return False - now = _now_iso() - review_data["review_completed_at"] = now - review_data["review_completed_by"] = reviewer or None - review_data["review_completion_source"] = source - record_reviewer_activity(review_data, now) - review_data["current_cycle_completion"] = { - "completed": True, - "completed_at": now, - "source": source, - "reviewer": reviewer, - } - return True + completion: dict, + write_approval: dict, + current_head_sha: str, +) -> None: + review_data["active_head_sha"] = current_head_sha + review_data["current_cycle_completion"] = completion + review_data["current_cycle_write_approval"] = write_approval + if completion.get("completed"): + review_data["review_completed_at"] = _now_iso() + review_data["review_completed_by"] = None + review_data["review_completion_source"] = "live_review_rebuild" + else: + review_data["review_completed_at"] = None + review_data["review_completed_by"] = None + review_data["review_completion_source"] = None def is_triage_or_higher(bot, username: str) -> bool: - return bot.check_user_permission(username, "triage") + status = live_review_support.permission_status(bot, username, "triage") + if status == "unavailable": + raise RuntimeError(f"Unable to determine triage permission for @{username}") + return status == "granted" def trigger_mandatory_approver_escalation(bot, state: dict, issue_number: int) -> bool: - review_data = ensure_review_entry(state, issue_number, create=True) + from scripts.reviewer_bot_core import mandatory_approver_policy + + review_data = review_state.ensure_review_entry(state, issue_number, create=True) if review_data is None: return False - now = _now_iso() + decision = mandatory_approver_policy.decide_mandatory_approver_escalation( + review_data, + now=_now_iso(), + label_exists=bot.github.ensure_label_exists(MANDATORY_TRIAGE_APPROVER_LABEL), + ) state_changed = False - if not review_data.get("mandatory_approver_required"): + if decision["require_escalation"]: review_data["mandatory_approver_required"] = True review_data["mandatory_approver_satisfied_by"] = None review_data["mandatory_approver_satisfied_at"] = None state_changed = True - if bot.ensure_label_exists(MANDATORY_TRIAGE_APPROVER_LABEL): + if decision["attempt_label_apply"]: try: if bot.add_label_with_status(issue_number, MANDATORY_TRIAGE_APPROVER_LABEL): - if review_data.get("mandatory_approver_label_applied_at") is None: - review_data["mandatory_approver_label_applied_at"] = now + if decision["record_label_applied_at"]: + review_data["mandatory_approver_label_applied_at"] = str(decision["now"]) state_changed = True except RuntimeError as exc: - print(f"WARNING: Unable to apply escalation label on #{issue_number}: {exc}", file=bot.sys.stderr) - if review_data.get("mandatory_approver_pinged_at") is None: - if bot.post_comment(issue_number, MANDATORY_TRIAGE_ESCALATION_TEMPLATE): - review_data["mandatory_approver_pinged_at"] = now + _log(bot, "warning", f"Unable to apply escalation label on #{issue_number}: {exc}", issue_number=issue_number, error=str(exc)) + if decision["post_ping"]: + if bot.github.post_comment(issue_number, MANDATORY_TRIAGE_ESCALATION_TEMPLATE): + review_data["mandatory_approver_pinged_at"] = str(decision["now"]) state_changed = True return state_changed def satisfy_mandatory_approver_requirement(bot, state: dict, issue_number: int, approver: str) -> bool: - review_data = ensure_review_entry(state, issue_number, create=True) - if review_data is None or not review_data.get("mandatory_approver_required"): + from scripts.reviewer_bot_core import mandatory_approver_policy + + review_data = review_state.ensure_review_entry(state, issue_number, create=True) + if review_data is None: return False - if review_data.get("mandatory_approver_satisfied_at"): + decision = mandatory_approver_policy.decide_mandatory_approver_satisfaction( + review_data, + approver=approver, + now=_now_iso(), + ) + if not decision["allow"]: return False - now = _now_iso() review_data["mandatory_approver_required"] = False - review_data["mandatory_approver_satisfied_by"] = approver - review_data["mandatory_approver_satisfied_at"] = now + review_data["mandatory_approver_satisfied_by"] = str(decision["approver"]) + review_data["mandatory_approver_satisfied_at"] = str(decision["now"]) try: bot.remove_label_with_status(issue_number, MANDATORY_TRIAGE_APPROVER_LABEL) except RuntimeError as exc: - print(f"WARNING: Unable to remove escalation label on #{issue_number}: {exc}", file=bot.sys.stderr) - bot.post_comment(issue_number, MANDATORY_TRIAGE_SATISFIED_TEMPLATE.format(approver=approver)) + _log(bot, "warning", f"Unable to remove escalation label on #{issue_number}: {exc}", issue_number=issue_number, error=str(exc)) + bot.github.post_comment(issue_number, MANDATORY_TRIAGE_SATISFIED_TEMPLATE.format(approver=approver)) return True def get_pull_request_reviews(bot, issue_number: int) -> list[dict] | None: - reviews: list[dict] = [] - page = 1 - while True: - result = bot.github_api("GET", f"pulls/{issue_number}/reviews?per_page=100&page={page}") - if result is None: - return None - if not isinstance(result, list): - return reviews - page_reviews = [review for review in result if isinstance(review, dict)] - reviews.extend(page_reviews) - if len(result) < 100: - return reviews - page += 1 + result = live_review_support.read_pull_request_reviews_result(bot, issue_number) + if not result.get("ok"): + return None + reviews = result.get("reviews") + return reviews if isinstance(reviews, list) else None def collapse_latest_reviews_by_login(reviews: list[dict]) -> dict[str, dict]: @@ -585,14 +217,36 @@ def collapse_latest_reviews_by_login(reviews: list[dict]) -> dict[str, dict]: if current is None or review_key >= (current[0], current[1]): latest_by_login[key] = (submitted_at, review_id, review) return {login: item[2] for login, item in latest_by_login.items()} +def rebuild_pr_approval_state_result( + bot, + issue_number: int, + review_data: dict, + *, + pull_request: dict | None = None, + reviews: list[dict] | None = None, +) -> dict[str, object]: + from scripts.reviewer_bot_core import approval_policy - -def get_current_cycle_boundary(bot, review_data: dict) -> datetime | None: - for field in ("active_cycle_started_at", "cycle_started_at", "assigned_at"): - boundary = bot.parse_iso8601_timestamp(review_data.get(field)) - if boundary is not None: - return boundary - return None + result = approval_policy.compute_pr_approval_state_result( + bot, + issue_number, + review_data, + pull_request=pull_request, + reviews=reviews, + ) + if not result.get("ok"): + return result + apply_pr_approval_state( + review_data, + completion=result["completion"], + write_approval=result["write_approval"], + current_head_sha=str(result["current_head_sha"]), + ) + return { + "ok": True, + "completion": result["completion"], + "write_approval": result["write_approval"], + } def rebuild_pr_approval_state( @@ -603,92 +257,16 @@ def rebuild_pr_approval_state( pull_request: dict | None = None, reviews: list[dict] | None = None, ) -> tuple[dict | None, dict | None]: - boundary = get_current_cycle_boundary(bot, review_data) - if boundary is None: - return None, None - if pull_request is None: - pull_request = bot.github_api("GET", f"pulls/{issue_number}") - if not isinstance(pull_request, dict): - return None, None - head = pull_request.get("head") - current_head = head.get("sha") if isinstance(head, dict) else None - if not isinstance(current_head, str) or not current_head.strip(): - return None, None - if reviews is None: - reviews = bot.get_pull_request_reviews(issue_number) - if reviews is None: + result = rebuild_pr_approval_state_result( + bot, + issue_number, + review_data, + pull_request=pull_request, + reviews=reviews, + ) + if not result.get("ok"): return None, None - - survivors: dict[str, dict] = {} - for review in reviews: - if not isinstance(review, dict): - continue - state = str(review.get("state", "")).upper() - if state == "DISMISSED": - continue - submitted_at = parse_github_timestamp(review.get("submitted_at")) - if submitted_at is None or submitted_at < boundary: - continue - commit_id = review.get("commit_id") - if not isinstance(commit_id, str) or not commit_id.strip(): - continue - if commit_id.strip() != current_head: - continue - author = review.get("user", {}).get("login") - if not isinstance(author, str) or not author.strip(): - continue - review_id = str(review.get("id", "")) - key = author.lower() - candidate_key = (submitted_at, review_id) - current = survivors.get(key) - if current is None: - survivors[key] = review - continue - current_key = ( - parse_github_timestamp(current.get("submitted_at")) or datetime.min.replace(tzinfo=timezone.utc), - str(current.get("id", "")), - ) - if candidate_key >= current_key: - survivors[key] = review - - approvals = [review for review in survivors.values() if str(review.get("state", "")).upper() == "APPROVED"] - completion = { - "completed": bool(approvals), - "current_head_sha": current_head, - "qualifying_review_ids": [review.get("id") for review in approvals], - } - - permission_cache: dict[str, bool] = {} - has_write_approval = False - write_approvers: list[str] = [] - for review in approvals: - author = review.get("user", {}).get("login") - if not isinstance(author, str) or not author.strip(): - continue - cache_key = author.lower() - if cache_key not in permission_cache: - permission_cache[cache_key] = bot.check_user_permission(author, "push") - if permission_cache[cache_key]: - has_write_approval = True - write_approvers.append(author) - write_approval = { - "has_write_approval": has_write_approval, - "write_approvers": write_approvers, - "current_head_sha": current_head, - } - - review_data["active_head_sha"] = current_head - review_data["current_cycle_completion"] = completion - review_data["current_cycle_write_approval"] = write_approval - if completion["completed"]: - review_data["review_completed_at"] = _now_iso() - review_data["review_completed_by"] = None - review_data["review_completion_source"] = "live_review_rebuild" - else: - review_data["review_completed_at"] = None - review_data["review_completed_by"] = None - review_data["review_completion_source"] = None - return completion, write_approval + return result.get("completion"), result.get("write_approval") def pr_has_current_write_approval( @@ -705,51 +283,6 @@ def pr_has_current_write_approval( return bool(write_approval.get("has_write_approval")) -def _record_timestamp(record: dict | None) -> datetime | None: - if not isinstance(record, dict): - return None - return parse_github_timestamp(record.get("timestamp")) - - -def _compare_cross_channel_conversation(contributor: dict | None, reviewer: dict | None) -> int: - contributor_time = _record_timestamp(contributor) or datetime.min.replace(tzinfo=timezone.utc) - reviewer_time = _record_timestamp(reviewer) or datetime.min.replace(tzinfo=timezone.utc) - contributor_key = str((contributor or {}).get("semantic_key", "")) - reviewer_key = str((reviewer or {}).get("semantic_key", "")) - if (contributor_time, contributor_key) == (reviewer_time, reviewer_key): - return 0 - if contributor_time > reviewer_time: - return 1 - if contributor_time < reviewer_time: - return -1 - if contributor_key >= reviewer_key: - return 1 - return -1 - - -def _initial_reviewer_anchor(review_data: dict) -> str | None: - for field in ("active_cycle_started_at", "cycle_started_at", "assigned_at"): - value = review_data.get(field) - if isinstance(value, str) and value: - return value - return None - - -def _contributor_revision_handoff_record(review_data: dict, current_head: str | None, reviewer_review: dict | None) -> dict | None: - contributor_revision = review_data.get("contributor_revision", {}).get("accepted") - if not isinstance(contributor_revision, dict): - return None - revision_head = contributor_revision.get("reviewed_head_sha") - if not isinstance(revision_head, str) or not isinstance(current_head, str): - return None - if revision_head != current_head: - return None - reviewer_head = reviewer_review.get("reviewed_head_sha") if isinstance(reviewer_review, dict) else None - if isinstance(reviewer_head, str) and reviewer_head == current_head: - return None - return contributor_revision - - def compute_reviewer_response_state( bot, issue_number: int, @@ -759,157 +292,16 @@ def compute_reviewer_response_state( pull_request: dict | None = None, reviews: list[dict] | None = None, ) -> dict[str, object]: - if issue_snapshot is None: - issue_snapshot = bot.get_issue_or_pr_snapshot(issue_number) - if not isinstance(issue_snapshot, dict): - return {"state": "projection_failed", "reason": "issue_snapshot_unavailable"} - is_pr = isinstance(issue_snapshot.get("pull_request"), dict) - current_reviewer = review_data.get("current_reviewer") - if not isinstance(current_reviewer, str) or not current_reviewer.strip(): - return {"state": "untracked", "reason": "no_current_reviewer"} - - reviewer_comment = review_data.get("reviewer_comment", {}).get("accepted") - reviewer_review = review_data.get("reviewer_review", {}).get("accepted") - contributor_comment = review_data.get("contributor_comment", {}).get("accepted") - - if not is_pr: - if not reviewer_comment and not reviewer_review: - return { - "state": "awaiting_reviewer_response", - "reason": "no_reviewer_activity", - "anchor_timestamp": _initial_reviewer_anchor(review_data), - "reviewer_comment": reviewer_comment, - "reviewer_review": reviewer_review, - "contributor_comment": contributor_comment, - "contributor_handoff": None, - } - latest_reviewer_response = reviewer_comment - if _compare_records(reviewer_review, latest_reviewer_response) > 0: - latest_reviewer_response = reviewer_review - completion = review_data.get("current_cycle_completion") - if not isinstance(completion, dict) or not completion.get("completed"): - if review_data.get("review_completed_at"): - return {"state": "done", "reason": None} - return { - "state": "awaiting_contributor_response", - "reason": "completion_missing", - "anchor_timestamp": latest_reviewer_response.get("timestamp") if isinstance(latest_reviewer_response, dict) else None, - } - return {"state": "done", "reason": None} - - if not reviewer_comment and not reviewer_review: - preferred_live_review = get_preferred_current_reviewer_review_for_cycle( - bot, - issue_number, - review_data, - pull_request=pull_request, - reviews=reviews, - ) - if preferred_live_review is not None: - reviewer_review = build_reviewer_review_record_from_live_review(preferred_live_review, actor=current_reviewer) - else: - return { - "state": "awaiting_reviewer_response", - "reason": "no_reviewer_activity", - "anchor_timestamp": _initial_reviewer_anchor(review_data), - "reviewer_comment": reviewer_comment, - "reviewer_review": reviewer_review, - "contributor_comment": contributor_comment, - "contributor_handoff": None, - } + from scripts.reviewer_bot_core import reviewer_response_policy - if pull_request is None: - pull_request = bot.github_api("GET", f"pulls/{issue_number}") - if not isinstance(pull_request, dict): - return {"state": "projection_failed", "reason": "pull_request_unavailable"} - head = pull_request.get("head") - current_head = head.get("sha") if isinstance(head, dict) else None - if not isinstance(current_head, str) or not current_head.strip(): - return {"state": "projection_failed", "reason": "pull_request_head_unavailable"} - review_data["active_head_sha"] = current_head - - preferred_live_review = get_preferred_current_reviewer_review_for_cycle( + return reviewer_response_policy.compute_reviewer_response_state( bot, issue_number, review_data, + issue_snapshot=issue_snapshot, pull_request=pull_request, reviews=reviews, ) - if preferred_live_review is not None: - reviewer_review = build_reviewer_review_record_from_live_review(preferred_live_review, actor=current_reviewer) - - latest_reviewer_response = reviewer_comment - if _compare_records(reviewer_review, latest_reviewer_response) > 0: - latest_reviewer_response = reviewer_review - - contributor_handoff = contributor_comment - contributor_revision = _contributor_revision_handoff_record(review_data, current_head, reviewer_review if isinstance(reviewer_review, dict) else None) - if _compare_records(contributor_revision, contributor_handoff) > 0: - contributor_handoff = contributor_revision - - latest_review_head = reviewer_review.get("reviewed_head_sha") if isinstance(reviewer_review, dict) else None - if not isinstance(latest_review_head, str) or latest_review_head != current_head: - return { - "state": "awaiting_reviewer_response", - "reason": "review_head_stale", - "anchor_timestamp": contributor_handoff.get("timestamp") if isinstance(contributor_handoff, dict) else _initial_reviewer_anchor(review_data), - "current_head_sha": current_head, - "reviewer_comment": reviewer_comment, - "reviewer_review": reviewer_review, - "contributor_comment": contributor_comment, - "contributor_handoff": contributor_handoff, - } - - if _compare_cross_channel_conversation(contributor_handoff, latest_reviewer_response) > 0: - reason = "contributor_comment_newer" - if isinstance(contributor_handoff, dict) and str(contributor_handoff.get("semantic_key", "")).startswith("pull_request_"): - reason = "contributor_revision_newer" - return { - "state": "awaiting_reviewer_response", - "reason": reason, - "anchor_timestamp": contributor_handoff.get("timestamp") if isinstance(contributor_handoff, dict) else None, - "current_head_sha": current_head, - "reviewer_comment": reviewer_comment, - "reviewer_review": reviewer_review, - "contributor_comment": contributor_comment, - "contributor_handoff": contributor_handoff, - } - - completion, write_approval = rebuild_pr_approval_state(bot, issue_number, review_data, pull_request=pull_request, reviews=reviews) - if completion is None or write_approval is None: - return {"state": "projection_failed", "reason": "live_review_state_unknown"} - if not completion.get("completed"): - return { - "state": "awaiting_contributor_response", - "reason": "completion_missing", - "anchor_timestamp": latest_reviewer_response.get("timestamp") if isinstance(latest_reviewer_response, dict) else None, - "current_head_sha": current_head, - "reviewer_comment": reviewer_comment, - "reviewer_review": reviewer_review, - "contributor_comment": contributor_comment, - "contributor_handoff": contributor_handoff, - } - if not write_approval.get("has_write_approval"): - return { - "state": "awaiting_write_approval", - "reason": "write_approval_missing", - "anchor_timestamp": latest_reviewer_response.get("timestamp") if isinstance(latest_reviewer_response, dict) else None, - "current_head_sha": current_head, - "reviewer_comment": reviewer_comment, - "reviewer_review": reviewer_review, - "contributor_comment": contributor_comment, - "contributor_handoff": contributor_handoff, - } - return { - "state": "done", - "reason": "write_approval_present", - "anchor_timestamp": latest_reviewer_response.get("timestamp") if isinstance(latest_reviewer_response, dict) else None, - "current_head_sha": current_head, - "reviewer_comment": reviewer_comment, - "reviewer_review": reviewer_review, - "contributor_comment": contributor_comment, - "contributor_handoff": contributor_handoff, - } def project_status_labels_for_item( @@ -920,13 +312,13 @@ def project_status_labels_for_item( issue_snapshot: dict | None = None, ) -> tuple[set[str] | None, dict[str, str | None]]: if issue_snapshot is None: - issue_snapshot = bot.get_issue_or_pr_snapshot(issue_number) + issue_snapshot = bot.github.get_issue_or_pr_snapshot(issue_number) if not isinstance(issue_snapshot, dict): return None, {"state": "projection_failed", "reason": "issue_snapshot_unavailable"} if str(issue_snapshot.get("state", "")).lower() == "closed": return set(), {"state": "closed", "reason": None} - review_data = bot.ensure_review_entry(state, issue_number) + review_data = review_state.ensure_review_entry(state, issue_number) if review_data is None: return set(), {"state": "untracked", "reason": "no_review_entry"} current_reviewer = review_data.get("current_reviewer") @@ -936,15 +328,7 @@ def project_status_labels_for_item( response_state = compute_reviewer_response_state(bot, issue_number, review_data, issue_snapshot=issue_snapshot) state_name = response_state.get("state") reason = response_state.get("reason") - if state_name == "projection_failed": - return None, {"state": str(state_name), "reason": str(reason)} - if state_name == "awaiting_reviewer_response": - return ({STATUS_AWAITING_REVIEWER_RESPONSE_LABEL}, {"state": str(state_name), "reason": str(reason)}) - if state_name == "awaiting_contributor_response": - return ({STATUS_AWAITING_CONTRIBUTOR_RESPONSE_LABEL}, {"state": str(state_name), "reason": str(reason)}) - if state_name == "awaiting_write_approval": - return ({STATUS_AWAITING_WRITE_APPROVAL_LABEL}, {"state": str(state_name), "reason": str(reason)}) - return set(), {"state": str(state_name), "reason": None if reason is None else str(reason)} + return desired_labels_from_response_state(str(state_name), None if reason is None else str(reason)) def sync_status_labels(bot, issue_number: int, desired_labels: set[str], actual_labels: Iterable[str]) -> bool: @@ -954,7 +338,7 @@ def sync_status_labels(bot, issue_number: int, desired_labels: set[str], actual_ if not to_add and not to_remove: return False for label in STATUS_LABELS: - if not bot.ensure_label_exists(label): + if not bot.github.ensure_label_exists(label): raise RuntimeError(f"Unable to ensure reviewer-bot status label exists: {label}") changed = False for label in sorted(to_remove): @@ -971,7 +355,7 @@ def sync_status_labels(bot, issue_number: int, desired_labels: set[str], actual_ def sync_status_labels_for_items(bot, state: dict, issue_numbers: Iterable[int]) -> bool: changed = False for issue_number in sorted({n for n in issue_numbers if isinstance(n, int) and n > 0}): - issue_snapshot = bot.get_issue_or_pr_snapshot(issue_number) + issue_snapshot = bot.github.get_issue_or_pr_snapshot(issue_number) desired_labels, metadata = bot.project_status_labels_for_item(issue_number, state, issue_snapshot=issue_snapshot) if desired_labels is None: reason = metadata.get("reason") if isinstance(metadata, dict) else "unknown" @@ -997,11 +381,20 @@ def list_open_items_with_status_labels(bot) -> list[int]: page = 1 encoded_label = quote(label, safe="") while True: - result = bot.github_api("GET", f"issues?state=open&labels={encoded_label}&per_page=100&page={page}") - if result is None: - raise RuntimeError(f"Failed to list open items for status label '{label}'") + response = bot.github_api_request( + "GET", + f"issues?state=open&labels={encoded_label}&per_page=100&page={page}", + retry_policy="idempotent_read", + ) + if not response.ok: + raise RuntimeError( + f"Failed to list open items for status label '{label}': {response.failure_kind or 'unavailable'}" + ) + result = response.payload if not isinstance(result, list): - break + raise RuntimeError( + f"Failed to list open items for status label '{label}': invalid_payload" + ) for item in result: if isinstance(item, dict): number = item.get("number") @@ -1031,27 +424,3 @@ def list_open_tracked_review_items(state: dict) -> list[int]: if issue_number > 0: numbers.add(issue_number) return sorted(numbers) - - -def handle_pr_approved_review(bot, state: dict, issue_number: int, review_author: str, completion_source: str) -> bool: - review_data = ensure_review_entry(state, issue_number) - if review_data is None: - return False - current_reviewer = review_data.get("current_reviewer") - author_is_designated = isinstance(current_reviewer, str) and current_reviewer.lower() == review_author.lower() - author_is_triage = is_triage_or_higher(bot, review_author) - state_changed = False - if author_is_designated: - if mark_review_complete(state, issue_number, review_author, completion_source): - state_changed = True - if author_is_triage: - if satisfy_mandatory_approver_requirement(bot, state, issue_number, review_author): - state_changed = True - return state_changed - if trigger_mandatory_approver_escalation(bot, state, issue_number): - state_changed = True - return state_changed - if review_data.get("mandatory_approver_required") and author_is_triage: - if satisfy_mandatory_approver_requirement(bot, state, issue_number, review_author): - state_changed = True - return state_changed diff --git a/scripts/reviewer_bot_lib/reviews_projection.py b/scripts/reviewer_bot_lib/reviews_projection.py new file mode 100644 index 000000000..734f03c20 --- /dev/null +++ b/scripts/reviewer_bot_lib/reviews_projection.py @@ -0,0 +1,28 @@ +"""Pure projection helpers for reviewer-bot review state.""" + +from __future__ import annotations + +from .config import ( + STATUS_AWAITING_CONTRIBUTOR_RESPONSE_LABEL, + STATUS_AWAITING_REVIEWER_RESPONSE_LABEL, + STATUS_AWAITING_WRITE_APPROVAL_LABEL, +) + +__all__ = [ + "desired_labels_from_response_state", +] + + +def desired_labels_from_response_state( + state_name: str, + reason: str | None, +) -> tuple[set[str] | None, dict[str, str | None]]: + if state_name == "projection_failed": + return None, {"state": state_name, "reason": reason} + if state_name == "awaiting_reviewer_response": + return {STATUS_AWAITING_REVIEWER_RESPONSE_LABEL}, {"state": state_name, "reason": reason} + if state_name == "awaiting_contributor_response": + return {STATUS_AWAITING_CONTRIBUTOR_RESPONSE_LABEL}, {"state": state_name, "reason": reason} + if state_name == "awaiting_write_approval": + return {STATUS_AWAITING_WRITE_APPROVAL_LABEL}, {"state": state_name, "reason": reason} + return set(), {"state": state_name, "reason": reason} diff --git a/scripts/reviewer_bot_lib/runtime.py b/scripts/reviewer_bot_lib/runtime.py new file mode 100644 index 000000000..c292be9b1 --- /dev/null +++ b/scripts/reviewer_bot_lib/runtime.py @@ -0,0 +1,470 @@ +"""Explicit runtime service composition for reviewer-bot orchestration.""" + +from __future__ import annotations + +import json +import os +import uuid +from datetime import datetime, timezone +from pathlib import Path +from typing import Any + +from .config import ( + AUTHOR_ASSOCIATION_TRUST_ALLOWLIST, + BOT_MENTION, + BOT_NAME, + COMMANDS, + DEFERRED_DISCOVERY_BOOTSTRAP_WINDOW_SECONDS, + DEFERRED_DISCOVERY_OVERLAP_SECONDS, + EVENT_INTENT_MUTATING, + EVENT_INTENT_NON_MUTATING_DEFER, + EVENT_INTENT_NON_MUTATING_READONLY, + FLS_AUDIT_LABEL, + LOCK_API_RETRY_LIMIT, + LOCK_API_RETRY_LIMIT_ENV, + LOCK_LEASE_TTL_SECONDS, + LOCK_LEASE_TTL_SECONDS_ENV, + LOCK_MAX_WAIT_SECONDS, + LOCK_MAX_WAIT_SECONDS_ENV, + LOCK_REF_BOOTSTRAP_BRANCH, + LOCK_REF_BOOTSTRAP_BRANCH_ENV, + LOCK_REF_NAME, + LOCK_REF_NAME_ENV, + LOCK_RENEWAL_WINDOW_SECONDS, + LOCK_RENEWAL_WINDOW_SECONDS_ENV, + LOCK_RETRY_BASE_SECONDS, + LOCK_RETRY_BASE_SECONDS_ENV, + REVIEW_DEADLINE_DAYS, + REVIEW_FRESHNESS_RUNBOOK_PATH, + REVIEW_LABELS, + REVIEWER_REQUEST_422_TEMPLATE, + STATE_ISSUE_NUMBER, + STATE_ISSUE_NUMBER_ENV, + STATE_READ_RETRY_BASE_SECONDS, + STATE_READ_RETRY_BASE_SECONDS_ENV, + STATE_READ_RETRY_LIMIT, + STATE_READ_RETRY_LIMIT_ENV, + STATUS_PROJECTION_EPOCH, + TRANSITION_PERIOD_DAYS, +) + + +class _EnvConfig: + def get(self, name: str, default: str = "") -> str: + return os.environ.get(name, default) + + def set(self, name: str, value: Any) -> None: + os.environ[name] = str(value) + + +class _FileOutputSink: + def __init__(self, config: _EnvConfig): + self._config = config + + def write(self, name: str, value: str) -> None: + output_path = self._config.get("GITHUB_OUTPUT", "/dev/null") + with open(output_path, "a", encoding="utf-8") as output_file: + output_file.write(f"{name}={value}\n") + + +class _JsonDeferredPayloadLoader: + def __init__(self, config: _EnvConfig): + self._config = config + + def load(self) -> dict: + path = self._config.get("DEFERRED_CONTEXT_PATH", "").strip() + if not path: + raise RuntimeError("Missing DEFERRED_CONTEXT_PATH for workflow_run reconcile") + with open(Path(path), encoding="utf-8") as handle: + payload = json.load(handle) + if not isinstance(payload, dict): + raise RuntimeError("Deferred context payload must be a JSON object") + return payload + + +class _TouchTracker: + def __init__(self): + self._touched: set[int] = set() + + def collect(self, issue_number: int | None) -> None: + if isinstance(issue_number, int) and issue_number > 0: + self._touched.add(issue_number) + + def drain(self) -> list[int]: + touched = sorted(self._touched) + self._touched.clear() + return touched + + +class SystemClock: + def now(self) -> datetime: + return datetime.now(timezone.utc) + + +class SystemSleeper: + def __init__(self, time_module: Any): + self._time = time_module + + def sleep(self, seconds: float) -> None: + self._time.sleep(seconds) + + +class RandomJitterSource: + def __init__(self, random_module: Any): + self._random = random_module + + def uniform(self, lower: float, upper: float) -> float: + return self._random.uniform(lower, upper) + + +class Uuid4Source: + def uuid4_hex(self) -> str: + return uuid.uuid4().hex + + +class StdErrLogger: + def __init__(self, sys_module: Any): + self._sys = sys_module + + def event(self, level: str, message: str, **fields: Any) -> None: + rendered_fields = " ".join(f"{key}={value}" for key, value in sorted(fields.items())) + suffix = f" {rendered_fields}" if rendered_fields else "" + self._sys.stderr.write(f"[{level}] {message}{suffix}\n") + + +class RequestsRestTransport: + def __init__(self, requests_module: Any): + self._requests = requests_module + + def request( + self, + method: str, + url: str, + *, + headers: dict[str, str] | None = None, + json_data: dict[str, Any] | None = None, + timeout_seconds: float | None = None, + ) -> Any: + return self._requests.request(method, url, headers=headers, json=json_data, timeout=timeout_seconds) + + +class RequestsGraphQLTransport: + def __init__(self, requests_module: Any): + self._requests = requests_module + + def query( + self, + url: str, + *, + headers: dict[str, str] | None = None, + query: str, + variables: dict[str, Any] | None = None, + timeout_seconds: float | None = None, + ) -> Any: + return self._requests.post( + url, + headers=headers, + json={"query": query, "variables": variables or {}}, + timeout=timeout_seconds, + ) + + +class RequestsArtifactDownloadTransport: + def __init__(self, requests_module: Any): + self._requests = requests_module + + def download( + self, + url: str, + *, + headers: dict[str, str] | None = None, + timeout_seconds: float | None = None, + ) -> Any: + return self._requests.request("GET", url, headers=headers, timeout=timeout_seconds) + + +class RuntimeInfraServices: + def __init__( + self, + *, + config: Any, + outputs: Any, + deferred_payloads: Any, + rest_transport: Any, + graphql_transport: Any, + artifact_download_transport: Any, + clock: Any, + sleeper: Any, + jitter: Any, + uuid_source: Any, + logger: Any, + touch_tracker: Any, + ): + self.config = config + self.outputs = outputs + self.deferred_payloads = deferred_payloads + self.rest_transport = rest_transport + self.graphql_transport = graphql_transport + self.artifact_download_transport = artifact_download_transport + self.clock = clock + self.sleeper = sleeper + self.jitter = jitter + self.uuid_source = uuid_source + self.logger = logger + self.touch_tracker = touch_tracker + + +class RuntimeDomainServices: + def __init__(self, *, state_store: Any, github: Any, locks: Any, handlers: Any, adapters: Any): + self.state_store = state_store + self.github = github + self.locks = locks + self.handlers = handlers + self.adapters = adapters + + +class ReviewerBotRuntime: + """Runtime object built from explicit services and named adapters.""" + + BOT_NAME = BOT_NAME + BOT_MENTION = BOT_MENTION + COMMANDS = COMMANDS + FLS_AUDIT_LABEL = FLS_AUDIT_LABEL + AUTHOR_ASSOCIATION_TRUST_ALLOWLIST = AUTHOR_ASSOCIATION_TRUST_ALLOWLIST + REVIEWER_REQUEST_422_TEMPLATE = REVIEWER_REQUEST_422_TEMPLATE + REVIEW_FRESHNESS_RUNBOOK_PATH = REVIEW_FRESHNESS_RUNBOOK_PATH + REVIEW_DEADLINE_DAYS = REVIEW_DEADLINE_DAYS + TRANSITION_PERIOD_DAYS = TRANSITION_PERIOD_DAYS + REVIEW_LABELS = REVIEW_LABELS + DEFERRED_DISCOVERY_OVERLAP_SECONDS = DEFERRED_DISCOVERY_OVERLAP_SECONDS + DEFERRED_DISCOVERY_BOOTSTRAP_WINDOW_SECONDS = DEFERRED_DISCOVERY_BOOTSTRAP_WINDOW_SECONDS + EVENT_INTENT_MUTATING = EVENT_INTENT_MUTATING + EVENT_INTENT_NON_MUTATING_DEFER = EVENT_INTENT_NON_MUTATING_DEFER + EVENT_INTENT_NON_MUTATING_READONLY = EVENT_INTENT_NON_MUTATING_READONLY + STATUS_PROJECTION_EPOCH = STATUS_PROJECTION_EPOCH + datetime = datetime + timezone = timezone + + def __init__( + self, + *, + requests: Any, + sys: Any, + random: Any, + time: Any, + config: Any | None = None, + outputs: Any | None = None, + deferred_payloads: Any | None = None, + rest_transport: Any | None = None, + graphql_transport: Any | None = None, + artifact_download_transport: Any | None = None, + clock: Any | None = None, + sleeper: Any | None = None, + jitter: Any | None = None, + uuid_source: Any | None = None, + logger: Any | None = None, + state_store: Any, + github: Any, + locks: Any, + handlers: Any, + adapters: Any, + touch_tracker: Any | None = None, + active_lease_context: Any | None = None, + ): + self.requests = requests + self.sys = sys + self.random = random + self.time = time + resolved_config = config or _EnvConfig() + resolved_touch_tracker = touch_tracker or _TouchTracker() + self.infra = RuntimeInfraServices( + config=resolved_config, + outputs=outputs or _FileOutputSink(resolved_config), + deferred_payloads=deferred_payloads or _JsonDeferredPayloadLoader(resolved_config), + rest_transport=rest_transport or RequestsRestTransport(requests), + graphql_transport=graphql_transport or RequestsGraphQLTransport(requests), + artifact_download_transport=artifact_download_transport or RequestsArtifactDownloadTransport(requests), + clock=clock or SystemClock(), + sleeper=sleeper or SystemSleeper(time), + jitter=jitter or RandomJitterSource(random), + uuid_source=uuid_source or Uuid4Source(), + logger=logger or StdErrLogger(sys), + touch_tracker=resolved_touch_tracker, + ) + self.domain = RuntimeDomainServices( + state_store=state_store, + github=github, + locks=locks, + handlers=handlers, + adapters=adapters, + ) + self.config = self.infra.config + self.outputs = self.infra.outputs + self.deferred_payloads = self.infra.deferred_payloads + self.rest_transport = self.infra.rest_transport + self.graphql_transport = self.infra.graphql_transport + self.artifact_download_transport = self.infra.artifact_download_transport + self.clock = self.infra.clock + self.sleeper = self.infra.sleeper + self.jitter = self.infra.jitter + self.uuid_source = self.infra.uuid_source + self.logger = self.infra.logger + self.touch_tracker = self.infra.touch_tracker + self.state_store = self.domain.state_store + self.github = self.domain.github + self.locks = self.domain.locks + self.handlers = self.domain.handlers + self.adapters = self.domain.adapters + self.ACTIVE_LEASE_CONTEXT = active_lease_context + + def get_config_value(self, name: str, default: str = "") -> str: + return self.config.get(name, default) + + def set_config_value(self, name: str, value: Any) -> None: + self.config.set(name, value) + + def state_issue_number(self) -> int: + return int(self.get_config_value(STATE_ISSUE_NUMBER_ENV, str(STATE_ISSUE_NUMBER)) or 0) + + def lock_api_retry_limit(self) -> int: + return int(self.get_config_value(LOCK_API_RETRY_LIMIT_ENV, str(LOCK_API_RETRY_LIMIT)) or 0) + + def lock_retry_base_seconds(self) -> float: + return float(self.get_config_value(LOCK_RETRY_BASE_SECONDS_ENV, str(LOCK_RETRY_BASE_SECONDS)) or 0.0) + + def lock_max_wait_seconds(self) -> int: + return int(self.get_config_value(LOCK_MAX_WAIT_SECONDS_ENV, str(LOCK_MAX_WAIT_SECONDS)) or 0) + + def lock_lease_ttl_seconds(self) -> int: + return int(self.get_config_value(LOCK_LEASE_TTL_SECONDS_ENV, str(LOCK_LEASE_TTL_SECONDS)) or 0) + + def lock_renewal_window_seconds(self) -> int: + return int(self.get_config_value(LOCK_RENEWAL_WINDOW_SECONDS_ENV, str(LOCK_RENEWAL_WINDOW_SECONDS)) or 0) + + def lock_ref_name(self) -> str: + return self.get_config_value(LOCK_REF_NAME_ENV, LOCK_REF_NAME) + + def lock_ref_bootstrap_branch(self) -> str: + return self.get_config_value(LOCK_REF_BOOTSTRAP_BRANCH_ENV, LOCK_REF_BOOTSTRAP_BRANCH) + + def state_read_retry_limit(self) -> int: + return int(self.get_config_value(STATE_READ_RETRY_LIMIT_ENV, str(STATE_READ_RETRY_LIMIT)) or 0) + + def state_read_retry_base_seconds(self) -> float: + return float(self.get_config_value(STATE_READ_RETRY_BASE_SECONDS_ENV, str(STATE_READ_RETRY_BASE_SECONDS)) or 0.0) + + def write_output(self, name: str, value: str) -> None: + self.outputs.write(name, value) + + def load_deferred_payload(self) -> dict: + return self.deferred_payloads.load() + + def github_api_request(self, *args, **kwargs): + return self.github.github_api_request(*args, **kwargs) + + def github_api(self, *args, **kwargs): + return self.github.github_api(*args, **kwargs) + + def collect_touched_item(self, issue_number: int | None) -> None: + self.touch_tracker.collect(issue_number) + + def drain_touched_items(self) -> list[int]: + return self.touch_tracker.drain() + + def assert_lock_held(self, context: str) -> None: + return self.adapters.state_lock.assert_lock_held(context) + + def get_github_token(self) -> str: + return self.adapters.github.get_github_token() + + def get_github_graphql_token(self, *, prefer_board_token: bool = False) -> str: + return self.adapters.github.get_github_graphql_token(prefer_board_token=prefer_board_token) + + def github_graphql(self, query: str, variables=None, *, token=None): + return self.adapters.github.github_graphql(query, variables, token=token) + + def get_pull_request_reviews(self, issue_number: int): + return self.github.get_pull_request_reviews(issue_number) + + def rebuild_pr_approval_state( + self, + issue_number: int, + review_data: dict, + *, + pull_request: dict | None = None, + reviews: list[dict] | None = None, + ): + return self.adapters.review_state.rebuild_pr_approval_state( + issue_number, + review_data, + pull_request=pull_request, + reviews=reviews, + ) + + def parse_iso8601_timestamp(self, value: Any): + return self.adapters.state_lock.parse_iso8601_timestamp(value) + + def satisfy_mandatory_approver_requirement(self, current_state: dict, issue_number: int, approver: str) -> bool: + return self.adapters.review_state.satisfy_mandatory_approver_requirement(current_state, issue_number, approver) + + # Adapter-only mutable review-state compatibility surface. + def normalize_lock_metadata(self, lock_meta: dict | None): + return self.adapters.state_lock.normalize_lock_metadata(lock_meta) + + def get_state_issue(self): + return self.adapters.state_lock.get_state_issue() + + def clear_lock_metadata(self): + return self.adapters.state_lock.clear_lock_metadata() + + def get_state_issue_snapshot(self): + return self.adapters.state_lock.get_state_issue_snapshot() + + def conditional_patch_state_issue(self, body: str, etag: str | None = None): + return self.adapters.state_lock.conditional_patch_state_issue(body, etag) + + def render_state_issue_body(self, state: dict, base_body: str | None = None, *, preserve_state_block: bool = False): + return self.adapters.state_lock.render_state_issue_body( + state, + base_body, + preserve_state_block=preserve_state_block, + ) + + def get_state_issue_html_url(self): + return self.adapters.state_lock.get_state_issue_html_url() + + def get_lock_ref_display(self): + return self.adapters.state_lock.get_lock_ref_display() + + def get_lock_ref_snapshot(self): + return self.adapters.state_lock.get_lock_ref_snapshot() + + def build_lock_metadata(self, *args, **kwargs): + return self.adapters.state_lock.build_lock_metadata(*args, **kwargs) + + def create_lock_commit(self, parent_sha: str, tree_sha: str, lock_meta: dict): + return self.adapters.state_lock.create_lock_commit(parent_sha, tree_sha, lock_meta) + + def cas_update_lock_ref(self, new_sha: str): + return self.adapters.state_lock.cas_update_lock_ref(new_sha) + + def lock_is_currently_valid(self, lock_meta: dict, now: datetime | None = None): + return self.adapters.state_lock.lock_is_currently_valid(lock_meta, now) + + def renew_state_issue_lease_lock(self, context): + result = self.adapters.state_lock.renew_state_issue_lease_lock(context) + self.ACTIVE_LEASE_CONTEXT = self.adapters.state_lock.get_active_lease_context() + return result + + def ensure_state_issue_lease_lock_fresh(self) -> bool: + return self.adapters.state_lock.ensure_state_issue_lease_lock_fresh() + + def acquire_state_issue_lease_lock(self): + context = self.adapters.state_lock.acquire_state_issue_lease_lock() + self.ACTIVE_LEASE_CONTEXT = self.adapters.state_lock.get_active_lease_context() + return context + + def release_state_issue_lease_lock(self) -> bool: + result = self.adapters.state_lock.release_state_issue_lease_lock() + self.ACTIVE_LEASE_CONTEXT = self.adapters.state_lock.get_active_lease_context() + return result diff --git a/scripts/reviewer_bot_lib/runtime_protocols.py b/scripts/reviewer_bot_lib/runtime_protocols.py new file mode 100644 index 000000000..7e9bb84ab --- /dev/null +++ b/scripts/reviewer_bot_lib/runtime_protocols.py @@ -0,0 +1,505 @@ +"""Runtime and consumer protocol homes for reviewer-bot modules.""" + +from __future__ import annotations + +from datetime import datetime +from typing import Any, Protocol, runtime_checkable + +from .config import AssignmentAttempt, GitHubApiResult, LeaseContext, StateIssueSnapshot +from .lifecycle import HeadObservationRepairResult + + +@runtime_checkable +class ConfigProvider(Protocol): + def get(self, name: str, default: str = "") -> str: ... + + def set(self, name: str, value: Any) -> None: ... + + +@runtime_checkable +class Clock(Protocol): + def now(self) -> datetime: ... + + +@runtime_checkable +class Sleeper(Protocol): + def sleep(self, seconds: float) -> None: ... + + +@runtime_checkable +class JitterSource(Protocol): + def uniform(self, lower: float, upper: float) -> float: ... + + +@runtime_checkable +class UuidSource(Protocol): + def uuid4_hex(self) -> str: ... + + +@runtime_checkable +class Logger(Protocol): + def event(self, level: str, message: str, **fields: Any) -> None: ... + + +@runtime_checkable +class RestTransport(Protocol): + def request( + self, + method: str, + url: str, + *, + headers: dict[str, str] | None = None, + json_data: dict[str, Any] | None = None, + timeout_seconds: float | None = None, + ) -> Any: ... + + +@runtime_checkable +class GraphQLTransport(Protocol): + def query( + self, + url: str, + *, + headers: dict[str, str] | None = None, + query: str, + variables: dict[str, Any] | None = None, + timeout_seconds: float | None = None, + ) -> Any: ... + + +@runtime_checkable +class ArtifactDownloadTransport(Protocol): + def download( + self, + url: str, + *, + headers: dict[str, str] | None = None, + timeout_seconds: float | None = None, + ) -> Any: ... + + +@runtime_checkable +class EventInputsContext(Protocol): + def get_config_value(self, name: str, default: str = "") -> str: ... + + +@runtime_checkable +class ProjectBoardMetadataContext(Protocol): + def get_config_value(self, name: str, default: str = "") -> str: ... + + def get_github_graphql_token(self, *, prefer_board_token: bool = False) -> str: ... + + def github_graphql( + self, + query: str, + variables: dict | None = None, + *, + token: str | None = None, + ) -> Any | None: ... + + +@runtime_checkable +class ProjectBoardProjectionContext(Protocol): + github: Any + adapters: Any + + +@runtime_checkable +class AppEventContextRuntime(Protocol): + EVENT_INTENT_MUTATING: str + EVENT_INTENT_NON_MUTATING_DEFER: str + EVENT_INTENT_NON_MUTATING_READONLY: str + + def get_config_value(self, name: str, default: str = "") -> str: ... + + +@runtime_checkable +class AppExecutionRuntime(AppEventContextRuntime, Protocol): + datetime: type[datetime] + timezone: Any + state_store: Any + locks: Any + handlers: Any + adapters: Any + + def write_output(self, name: str, value: str) -> None: ... + + def drain_touched_items(self) -> list[int]: ... + + +@runtime_checkable +class GitHubApiContext(Protocol): + """Minimal runtime surface required by github_api helpers.""" + + LOCK_API_RETRY_LIMIT: int + LOCK_RETRY_BASE_SECONDS: float + REVIEWER_REQUEST_422_TEMPLATE: str + AssignmentAttempt: type[AssignmentAttempt] + GitHubApiResult: type[GitHubApiResult] + logger: Logger + sleeper: Sleeper + jitter: JitterSource + rest_transport: RestTransport + graphql_transport: GraphQLTransport + + def get_config_value(self, name: str, default: str = "") -> str: ... + + def get_github_token(self) -> str: ... + + def get_github_graphql_token(self, *, prefer_board_token: bool = False) -> str: ... + + def lock_api_retry_limit(self) -> int: ... + + def lock_retry_base_seconds(self) -> float: ... + + +@runtime_checkable +class GitHubTransportContext(GitHubApiContext, Protocol): + """Compatibility transport surface expected by call sites.""" + + def github_api_request( + self, + method: str, + endpoint: str, + data: dict | None = None, + extra_headers: dict[str, str] | None = None, + *, + retry_policy: str = "none", + timeout_seconds: float | None = None, + suppress_error_log: bool = False, + ) -> GitHubApiResult: ... + + def github_api(self, method: str, endpoint: str, data: dict | None = None) -> Any | None: ... + + def github_graphql_request( + self, + query: str, + variables: dict | None = None, + *, + token: str | None = None, + retry_policy: str = "none", + timeout_seconds: float | None = None, + suppress_error_log: bool = False, + ) -> GitHubApiResult: ... + + def github_graphql( + self, + query: str, + variables: dict | None = None, + *, + token: str | None = None, + ) -> Any | None: ... + + def request_pr_reviewer_assignment(self, issue_number: int, username: str) -> AssignmentAttempt: ... + + def assign_issue_assignee(self, issue_number: int, username: str) -> AssignmentAttempt: ... + + def get_user_permission_status(self, username: str, required_permission: str = "triage") -> str: ... + + def remove_issue_assignee(self, issue_number: int, username: str) -> bool: ... + + def remove_pr_reviewer(self, issue_number: int, username: str) -> bool: ... + + +@runtime_checkable +class StateStoreRuntimeContext(Protocol): + """Minimal runtime surface required by state_store helpers.""" + + ACTIVE_LEASE_CONTEXT: LeaseContext | None + GitHubApiResult: type[GitHubApiResult] + logger: Logger + sleeper: Sleeper + jitter: JitterSource + clock: Clock + + def get_config_value(self, name: str, default: str = "") -> str: ... + + def github_api_request( + self, + method: str, + endpoint: str, + data: dict | None = None, + extra_headers: dict[str, str] | None = None, + *, + retry_policy: str = "none", + timeout_seconds: float | None = None, + suppress_error_log: bool = False, + ) -> GitHubApiResult: ... + + def render_state_issue_body( + self, + state: dict, + base_body: str | None = None, + *, + preserve_state_block: bool = False, + ) -> str: ... + + def assert_lock_held(self, operation: str) -> None: ... + + def parse_iso8601_timestamp(self, value: Any) -> datetime | None: ... + + def normalize_lock_metadata(self, lock_meta: dict | None) -> dict: ... + + def ensure_state_issue_lease_lock_fresh(self) -> bool: ... + + def state_issue_number(self) -> int: ... + + def state_read_retry_limit(self) -> int: ... + + def state_read_retry_base_seconds(self) -> float: ... + + def lock_api_retry_limit(self) -> int: ... + + def lock_retry_base_seconds(self) -> float: ... + + +@runtime_checkable +class StateStoreContext(StateStoreRuntimeContext, Protocol): + """Compatibility state-store surface expected by current runtime.""" + + def get_state_issue(self) -> dict | None: ... + + def get_state_issue_snapshot(self) -> StateIssueSnapshot | None: ... + + def conditional_patch_state_issue(self, body: str, etag: str | None = None) -> GitHubApiResult: ... + + +@runtime_checkable +class LeaseLockRuntimeContext(Protocol): + """Minimal runtime surface required by lease_lock helpers.""" + + ACTIVE_LEASE_CONTEXT: LeaseContext | None + LOCK_COMMIT_MARKER: str + LOCK_SCHEMA_VERSION: int + LeaseContext: type[LeaseContext] + logger: Logger + sleeper: Sleeper + jitter: JitterSource + clock: Clock + uuid_source: UuidSource + time: Any + + def get_config_value(self, name: str, default: str = "") -> str: ... + + def parse_iso8601_timestamp(self, value: Any) -> datetime | None: ... + + def normalize_lock_metadata(self, lock_meta: dict | None) -> dict: ... + + def clear_lock_metadata(self) -> dict: ... + + def get_state_issue_snapshot(self) -> StateIssueSnapshot | None: ... + + def github_api_request( + self, + method: str, + endpoint: str, + data: dict | None = None, + extra_headers: dict[str, str] | None = None, + *, + retry_policy: str = "none", + timeout_seconds: float | None = None, + suppress_error_log: bool = False, + ) -> GitHubApiResult: ... + + def get_lock_ref_display(self) -> str: ... + + def get_state_issue_html_url(self) -> str: ... + + def build_lock_metadata( + self, + lock_token: str, + lock_owner_run_id: str, + lock_owner_workflow: str, + lock_owner_job: str, + ) -> dict: ... + + def lock_is_currently_valid(self, lock_meta: dict, now: datetime | None = None) -> bool: ... + + def lock_api_retry_limit(self) -> int: ... + + def lock_retry_base_seconds(self) -> float: ... + + def lock_lease_ttl_seconds(self) -> int: ... + + def lock_max_wait_seconds(self) -> int: ... + + def lock_renewal_window_seconds(self) -> int: ... + + def lock_ref_name(self) -> str: ... + + def lock_ref_bootstrap_branch(self) -> str: ... + + +@runtime_checkable +class LeaseLockContext(LeaseLockRuntimeContext, Protocol): + """Compatibility lock surface expected by current runtime.""" + + def get_lock_ref_snapshot(self) -> tuple[str, str, dict]: ... + + def create_lock_commit(self, parent_sha: str, tree_sha: str, lock_meta: dict) -> GitHubApiResult: ... + + def cas_update_lock_ref(self, new_sha: str) -> GitHubApiResult: ... + + def renew_state_issue_lease_lock(self, context: LeaseContext) -> bool: ... + + +@runtime_checkable +class SweeperContext(Protocol): + STATUS_PROJECTION_EPOCH: str + TRANSITION_PERIOD_DAYS: int + DEFERRED_DISCOVERY_OVERLAP_SECONDS: int + DEFERRED_DISCOVERY_BOOTSTRAP_WINDOW_SECONDS: int + REVIEW_FRESHNESS_RUNBOOK_PATH: str + GitHubApiResult: type[GitHubApiResult] + artifact_download_transport: ArtifactDownloadTransport + sleeper: Sleeper + jitter: JitterSource + logger: Logger + + def get_config_value(self, name: str, default: str = "") -> str: ... + + def get_github_token(self) -> str: ... + + def github_api_request( + self, + method: str, + endpoint: str, + data: dict | None = None, + extra_headers: dict[str, str] | None = None, + *, + retry_policy: str = "none", + timeout_seconds: float | None = None, + suppress_error_log: bool = False, + ) -> GitHubApiResult: ... + + def github_api(self, method: str, endpoint: str, data: dict | None = None) -> Any | None: ... + + def get_pull_request_reviews(self, issue_number: int) -> list[dict] | None: ... + + def get_issue_or_pr_snapshot(self, issue_number: int) -> dict | None: ... + + def maybe_record_head_observation_repair( + self, issue_number: int, review_data: dict + ) -> HeadObservationRepairResult: ... + + def compute_reviewer_response_state( + self, + issue_number: int, + review_data: dict, + *, + issue_snapshot: dict | None = None, + pull_request: dict | None = None, + reviews: list[dict] | None = None, + ) -> dict[str, object]: ... + + def lock_api_retry_limit(self) -> int: ... + + def lock_retry_base_seconds(self) -> float: ... + + +@runtime_checkable +class ReconcileReviewStateAdapterContext(Protocol): + def maybe_record_head_observation_repair( + self, + issue_number: int, + review_data: dict, + ) -> HeadObservationRepairResult: ... + + +@runtime_checkable +class ReconcileAdaptersContext(Protocol): + review_state: ReconcileReviewStateAdapterContext + + +@runtime_checkable +class ReconcileWorkflowRuntimeContext(Protocol): + REVIEW_FRESHNESS_RUNBOOK_PATH: str + logger: Logger + clock: Clock + adapters: ReconcileAdaptersContext + + def assert_lock_held(self, context: str) -> None: ... + + def load_deferred_payload(self) -> dict: ... + + def get_config_value(self, name: str, default: str = "") -> str: ... + + def collect_touched_item(self, issue_number: int | None) -> None: ... + + def drain_touched_items(self) -> list[int]: ... + + def github_api_request(self, *args, **kwargs) -> Any: ... + + def github_api(self, *args, **kwargs) -> Any | None: ... + + +@runtime_checkable +class ReconcileRectifyGitHubContext(Protocol): + def get_pull_request_reviews(self, issue_number: int) -> list[dict] | None: ... + + def get_user_permission_status(self, username: str, required_permission: str = "triage") -> str: ... + + +@runtime_checkable +class ReconcileRectifyRuntimeContext(Protocol): + github: ReconcileRectifyGitHubContext + adapters: ReconcileAdaptersContext + + def get_config_value(self, name: str, default: str = "") -> str: ... + + def parse_iso8601_timestamp(self, value: Any) -> datetime | None: ... + + def github_api_request(self, *args, **kwargs) -> Any: ... + + def github_api(self, *args, **kwargs) -> Any | None: ... + + def satisfy_mandatory_approver_requirement( + self, + state: dict, + issue_number: int, + approver: str, + ) -> bool: ... + + +@runtime_checkable +class CommentGitHubWriteContext(Protocol): + def get_user_permission_status(self, username: str, required_permission: str = "triage") -> str: ... + + def post_comment(self, issue_number: int, body: str) -> bool: ... + + def add_reaction(self, comment_id: int, reaction: str) -> bool: ... + + +@runtime_checkable +class CommentCommandsAdapterContext(Protocol): + def strip_code_blocks(self, comment_body: str) -> str: ... + + def parse_command(self, comment_body: str): ... + + +@runtime_checkable +class CommentRoutingAdaptersContext(Protocol): + commands: CommentCommandsAdapterContext + + +@runtime_checkable +class CommentApplicationRuntimeContext(Protocol): + BOT_MENTION: str + github: CommentGitHubWriteContext + + def get_config_value(self, name: str, default: str = "") -> str: ... + + +@runtime_checkable +class CommentRoutingRuntimeContext(Protocol): + BOT_NAME: str + BOT_MENTION: str + AUTHOR_ASSOCIATION_TRUST_ALLOWLIST: tuple[str, ...] + logger: Logger + adapters: CommentRoutingAdaptersContext + + def assert_lock_held(self, context: str) -> None: ... + + def collect_touched_item(self, issue_number: int | None) -> None: ... + + def github_api(self, *args, **kwargs) -> Any | None: ... diff --git a/scripts/reviewer_bot_lib/state_store.py b/scripts/reviewer_bot_lib/state_store.py index b65d74b26..d18f6747c 100644 --- a/scripts/reviewer_bot_lib/state_store.py +++ b/scripts/reviewer_bot_lib/state_store.py @@ -1,45 +1,79 @@ """State issue parsing, loading, and saving helpers.""" -import json -import random import re -import sys -import time from datetime import datetime, timezone from typing import Any import yaml +from . import lock_codec, retrying from .config import ( FRESHNESS_RUNTIME_EPOCH_LEGACY, - LOCK_API_RETRY_LIMIT, - LOCK_BLOCK_END_MARKER, - LOCK_BLOCK_START_MARKER, - LOCK_METADATA_KEYS, - LOCK_RETRY_BASE_SECONDS, - LOCK_SCHEMA_VERSION, STATE_BLOCK_END_MARKER, STATE_BLOCK_START_MARKER, - STATE_ISSUE_NUMBER, - STATE_READ_RETRY_BASE_SECONDS, - STATE_READ_RETRY_LIMIT, STATE_SCHEMA_VERSION, StateIssueBodyParts, StateIssueSnapshot, ) -from .context import StateStoreContext +from .runtime_protocols import StateStoreContext, StateStoreRuntimeContext + + +def _log(bot: StateStoreRuntimeContext, level: str, message: str, **fields: Any) -> None: + bot.logger.event(level, message, **fields) + + +def _sleep(bot: StateStoreRuntimeContext, seconds: float) -> None: + bot.sleeper.sleep(seconds) + + +def _jitter(bot: StateStoreRuntimeContext, lower: float, upper: float) -> float: + return bot.jitter.uniform(lower, upper) + + +def _retry_delay(bot: StateStoreRuntimeContext, base_seconds: float, retry_attempt: int) -> float: + class _BotJitter: + def uniform(self, lower: float, upper: float) -> float: + return _jitter(bot, lower, upper) + + return retrying.bounded_exponential_delay( + base_seconds, + retry_attempt, + jitter=_BotJitter(), + ) + + +def _now_iso(bot: StateStoreRuntimeContext) -> str: + return bot.clock.now().isoformat() + + +def _state_issue_number(bot: StateStoreRuntimeContext) -> int: + return bot.state_issue_number() + + +def _lock_api_retry_limit(bot: StateStoreRuntimeContext) -> int: + return bot.lock_api_retry_limit() + + +def _lock_retry_base_seconds(bot: StateStoreRuntimeContext) -> float: + return bot.lock_retry_base_seconds() + + +def _state_read_retry_limit(bot: StateStoreRuntimeContext) -> int: + return bot.state_read_retry_limit() + + +def _state_read_retry_base_seconds(bot: StateStoreRuntimeContext) -> float: + return bot.state_read_retry_base_seconds() def get_state_issue(bot: StateStoreContext) -> dict | None: """Fetch the state issue from GitHub with retry for transient failures.""" - state_issue_number = getattr(bot, "STATE_ISSUE_NUMBER", STATE_ISSUE_NUMBER) - state_read_retry_limit = getattr(bot, "STATE_READ_RETRY_LIMIT", STATE_READ_RETRY_LIMIT) - state_read_retry_base_seconds = getattr( - bot, "STATE_READ_RETRY_BASE_SECONDS", STATE_READ_RETRY_BASE_SECONDS - ) + state_issue_number = _state_issue_number(bot) + state_read_retry_limit = _state_read_retry_limit(bot) + state_read_retry_base_seconds = _state_read_retry_base_seconds(bot) if not state_issue_number: - print("ERROR: STATE_ISSUE_NUMBER not set", file=sys.stderr) + _log(bot, "error", "STATE_ISSUE_NUMBER not set") return None for attempt in range(1, state_read_retry_limit + 1): @@ -51,43 +85,52 @@ def get_state_issue(bot: StateStoreContext) -> dict | None: if response.status_code == 200: if not isinstance(response.payload, dict): - print("ERROR: State issue response payload was not an object", file=sys.stderr) + _log(bot, "error", "State issue response payload was not an object") return None return response.payload if response.status_code in {401, 403, 404}: - print( - "ERROR: Failed to fetch state issue " - f"#{state_issue_number} (status {response.status_code}): {response.text}", - file=sys.stderr, + _log( + bot, + "error", + f"Failed to fetch state issue #{state_issue_number} (status {response.status_code}): {response.text}", + state_issue_number=state_issue_number, + status_code=response.status_code, ) return None - if response.status_code == 429 or response.status_code >= 500: + if retrying.is_retryable_status(response.status_code): if attempt < state_read_retry_limit: - delay = state_read_retry_base_seconds + random.uniform(0, state_read_retry_base_seconds) - print( - "WARNING: Retryable state issue read failure " - f"(status {response.status_code}); retrying ({attempt}/{state_read_retry_limit})", - file=sys.stderr, + delay = _retry_delay(bot, state_read_retry_base_seconds, attempt) + _log( + bot, + "warning", + f"Retryable state issue read failure (status {response.status_code}); retrying ({attempt}/{state_read_retry_limit})", + state_issue_number=state_issue_number, + status_code=response.status_code, + retry_attempt=attempt, ) - time.sleep(delay) + _sleep(bot, delay) continue - print( - "ERROR: Exhausted retries while fetching state issue " - f"#{state_issue_number}; last status {response.status_code}: {response.text}", - file=sys.stderr, + _log( + bot, + "error", + f"Exhausted retries while fetching state issue #{state_issue_number}; last status {response.status_code}: {response.text}", + state_issue_number=state_issue_number, + status_code=response.status_code, ) return None - print( - "ERROR: Unexpected status while fetching state issue " - f"#{state_issue_number}: {response.status_code} {response.text}", - file=sys.stderr, + _log( + bot, + "error", + f"Unexpected status while fetching state issue #{state_issue_number}: {response.status_code} {response.text}", + state_issue_number=state_issue_number, + status_code=response.status_code, ) return None - print(f"ERROR: Failed to fetch state issue #{state_issue_number} after retries", file=sys.stderr) + _log(bot, "error", f"Failed to fetch state issue #{state_issue_number} after retries", state_issue_number=state_issue_number) return None @@ -117,27 +160,22 @@ def split_state_issue_body(body: str) -> StateIssueBodyParts: state_start = body.find(STATE_BLOCK_START_MARKER) state_end = body.find(STATE_BLOCK_END_MARKER) - lock_start = body.find(LOCK_BLOCK_START_MARKER) - lock_end = body.find(LOCK_BLOCK_END_MARKER) - has_state_markers = state_start >= 0 and state_end > state_start - has_lock_markers = lock_start >= 0 and lock_end > lock_start - - if has_state_markers and has_lock_markers and state_end < lock_start: + if has_state_markers: return StateIssueBodyParts( prefix=body[:state_start], state_block_inner=body[state_start + len(STATE_BLOCK_START_MARKER) : state_end], - between_state_and_lock=body[state_end + len(STATE_BLOCK_END_MARKER) : lock_start], - lock_block_inner=body[lock_start + len(LOCK_BLOCK_START_MARKER) : lock_end], - suffix=body[lock_end + len(LOCK_BLOCK_END_MARKER) :], + between_state_and_lock="", + lock_block_inner=None, + suffix=body[state_end + len(STATE_BLOCK_END_MARKER) :], has_state_markers=True, - has_lock_markers=True, + has_lock_markers=False, ) return StateIssueBodyParts( - prefix=default_state_issue_prefix(), + prefix=body, state_block_inner=None, - between_state_and_lock="\n\n", + between_state_and_lock="", lock_block_inner=None, suffix="\n", has_state_markers=False, @@ -156,22 +194,7 @@ def extract_fenced_block(inner_block: str, language_pattern: str) -> str | None: def normalize_lock_metadata(lock_meta: dict | None) -> dict: - normalized: dict[str, Any] = dict.fromkeys(LOCK_METADATA_KEYS) - normalized["schema_version"] = LOCK_SCHEMA_VERSION - - if not isinstance(lock_meta, dict): - return normalized - - for key in LOCK_METADATA_KEYS: - if key == "schema_version": - schema_value = lock_meta.get("schema_version") - if isinstance(schema_value, int): - normalized["schema_version"] = schema_value - continue - if key in lock_meta: - normalized[key] = lock_meta.get(key) - - return normalized + return lock_codec.normalize_lock_metadata(lock_meta) def parse_state_yaml_from_issue_body(body: str) -> dict: @@ -182,44 +205,16 @@ def parse_state_yaml_from_issue_body(body: str) -> dict: yaml_content = extract_fenced_block(parts.state_block_inner, "ya?ml") if yaml_content is None: - yaml_match = re.search(r"```ya?ml\n(.*?)\n```", body, re.DOTALL) - if yaml_match: - yaml_content = yaml_match.group(1) - else: - yaml_content = body + return {} try: state = yaml.safe_load(yaml_content) or {} - except yaml.YAMLError as exc: - print(f"WARNING: Failed to parse state YAML: {exc}", file=sys.stderr) + except yaml.YAMLError: state = {} if not isinstance(state, dict): return {} return state - - -def parse_lock_metadata_from_issue_body(body: str) -> dict: - parts = split_state_issue_body(body) - if not parts.has_lock_markers or parts.lock_block_inner is None: - return normalize_lock_metadata(None) - - lock_json = extract_fenced_block(parts.lock_block_inner, "json") - if lock_json is None: - return normalize_lock_metadata(None) - - try: - parsed = json.loads(lock_json) - except json.JSONDecodeError as exc: - print(f"WARNING: Failed to parse lock metadata JSON: {exc}", file=sys.stderr) - return normalize_lock_metadata(None) - - if not isinstance(parsed, dict): - return normalize_lock_metadata(None) - - return normalize_lock_metadata(parsed) - - def render_marked_fenced_block(start_marker: str, end_marker: str, language: str, content: str) -> str: normalized = content.rstrip("\n") return f"{start_marker}\n```{language}\n{normalized}\n```\n{end_marker}" @@ -227,7 +222,6 @@ def render_marked_fenced_block(start_marker: str, end_marker: str, language: str def render_state_issue_body( state: dict, - lock_meta: dict, base_body: str | None = None, *, preserve_state_block: bool = False, @@ -250,19 +244,10 @@ def render_state_issue_body( yaml_content, ) - lock_json = json.dumps(normalize_lock_metadata(lock_meta), indent=2, sort_keys=False) - lock_section = render_marked_fenced_block( - LOCK_BLOCK_START_MARKER, - LOCK_BLOCK_END_MARKER, - "json", - lock_json, - ) - prefix = parts.prefix or default_state_issue_prefix() - between = parts.between_state_and_lock if parts.has_state_markers and parts.has_lock_markers else "\n\n" - suffix = parts.suffix if parts.has_lock_markers else "\n" + suffix = parts.suffix or "\n" - return f"{prefix}{state_section}{between}{lock_section}{suffix}" + return f"{prefix}{state_section}{suffix}" def parse_state_from_issue(issue: dict) -> dict: @@ -271,26 +256,29 @@ def parse_state_from_issue(issue: dict) -> dict: def get_state_issue_snapshot(bot: StateStoreContext) -> StateIssueSnapshot | None: - state_issue_number = getattr(bot, "STATE_ISSUE_NUMBER", STATE_ISSUE_NUMBER) + state_issue_number = _state_issue_number(bot) if not state_issue_number: - print("ERROR: STATE_ISSUE_NUMBER not set", file=sys.stderr) + _log(bot, "error", "STATE_ISSUE_NUMBER not set") return None response = bot.github_api_request( "GET", f"issues/{state_issue_number}", + retry_policy="idempotent_read", suppress_error_log=True, ) if response.status_code != 200: - print( - "ERROR: Failed to fetch state issue " - f"#{state_issue_number} (status {response.status_code}): {response.text}", - file=sys.stderr, + _log( + bot, + "error", + f"Failed to fetch state issue #{state_issue_number} (status {response.status_code}): {response.text}", + state_issue_number=state_issue_number, + status_code=response.status_code, ) return None if not isinstance(response.payload, dict): - print("ERROR: State issue response payload was not an object", file=sys.stderr) + _log(bot, "error", "State issue response payload was not an object") return None body = response.payload.get("body") @@ -299,18 +287,20 @@ def get_state_issue_snapshot(bot: StateStoreContext) -> StateIssueSnapshot | Non html_url = response.payload.get("html_url") if not isinstance(html_url, str) or not html_url: - repo = f"{__import__('os').environ.get('REPO_OWNER', '')}/{__import__('os').environ.get('REPO_NAME', '')}".strip("/") + repo = f"{bot.get_config_value('REPO_OWNER', '')}/{bot.get_config_value('REPO_NAME', '')}".strip("/") html_url = f"https://github.com/{repo}/issues/{state_issue_number}" if repo else "" return StateIssueSnapshot(body=body, etag=response.headers.get("etag"), html_url=html_url) def conditional_patch_state_issue(bot: StateStoreContext, body: str, etag: str | None = None): - state_issue_number = getattr(bot, "STATE_ISSUE_NUMBER", STATE_ISSUE_NUMBER) + state_issue_number = _state_issue_number(bot) + extra_headers = {"If-Match": etag} if isinstance(etag, str) and etag else None return bot.github_api_request( "PATCH", f"issues/{state_issue_number}", {"body": body}, + extra_headers=extra_headers, suppress_error_log=True, ) @@ -340,7 +330,7 @@ def load_state(bot: StateStoreContext, *, fail_on_unavailable: bool = False) -> "State issue is unavailable for a mutating event; refusing to continue " "with fallback defaults." ) - print("WARNING: Could not fetch state issue, using defaults", file=sys.stderr) + _log(bot, "warning", "Could not fetch state issue, using defaults") return default_state state = parse_state_from_issue(issue) @@ -368,82 +358,91 @@ def load_state(bot: StateStoreContext, *, fail_on_unavailable: bool = False) -> def save_state(bot: StateStoreContext, state: dict) -> bool: assert_lock_held(bot, "save_state") - state_issue_number = getattr(bot, "STATE_ISSUE_NUMBER", STATE_ISSUE_NUMBER) - lock_api_retry_limit = getattr(bot, "LOCK_API_RETRY_LIMIT", LOCK_API_RETRY_LIMIT) - lock_retry_base_seconds = getattr(bot, "LOCK_RETRY_BASE_SECONDS", LOCK_RETRY_BASE_SECONDS) + state_issue_number = _state_issue_number(bot) + lock_api_retry_limit = _lock_api_retry_limit(bot) + lock_retry_base_seconds = _lock_retry_base_seconds(bot) if not state_issue_number: - print("ERROR: STATE_ISSUE_NUMBER not set", file=sys.stderr) + _log(bot, "error", "STATE_ISSUE_NUMBER not set") return False - state["last_updated"] = datetime.now(timezone.utc).isoformat() + state["last_updated"] = _now_iso(bot) for attempt in range(1, lock_api_retry_limit + 1): if not bot.ensure_state_issue_lease_lock_fresh(): - print("ERROR: Failed to refresh reviewer-bot lease lock before save", file=sys.stderr) + _log(bot, "error", "Failed to refresh reviewer-bot lease lock before save") return False snapshot = bot.get_state_issue_snapshot() if snapshot is None: return False - lock_meta = bot.parse_lock_metadata_from_issue_body(snapshot.body) - body = bot.render_state_issue_body(state, lock_meta, snapshot.body) + body = bot.render_state_issue_body(state, snapshot.body) response = bot.conditional_patch_state_issue(body, snapshot.etag) if response.status_code == 200: - print(f"State saved to issue #{state_issue_number}") + _log(bot, "info", f"State saved to issue #{state_issue_number}", state_issue_number=state_issue_number) return True if response.status_code in {409, 412}: - print( - "WARNING: State save hit conflict " - f"(status {response.status_code}); retrying ({attempt}/{lock_api_retry_limit})", - file=sys.stderr, + _log( + bot, + "warning", + f"State save hit conflict (status {response.status_code}); retrying ({attempt}/{lock_api_retry_limit})", + state_issue_number=state_issue_number, + status_code=response.status_code, + retry_attempt=attempt, ) - delay = lock_retry_base_seconds + random.uniform(0, lock_retry_base_seconds) - time.sleep(delay) + delay = _retry_delay(bot, lock_retry_base_seconds, attempt) + _sleep(bot, delay) continue if response.status_code == 404: - print( - f"ERROR: State issue #{state_issue_number} not found during save_state", - file=sys.stderr, - ) + _log(bot, "error", f"State issue #{state_issue_number} not found during save_state", state_issue_number=state_issue_number) return False if response.status_code in {401, 403}: - print( - "ERROR: Permission failure while saving state issue " - f"#{state_issue_number} (status {response.status_code}): {response.text}", - file=sys.stderr, + _log( + bot, + "error", + f"Permission failure while saving state issue #{state_issue_number} (status {response.status_code}): {response.text}", + state_issue_number=state_issue_number, + status_code=response.status_code, ) return False - if response.status_code == 429 or response.status_code >= 500: + if retrying.is_retryable_status(response.status_code): if attempt < lock_api_retry_limit: - delay = lock_retry_base_seconds + random.uniform(0, lock_retry_base_seconds) - print( - "WARNING: Retryable state issue write failure " - f"(status {response.status_code}); retrying ({attempt}/{lock_api_retry_limit})", - file=sys.stderr, + delay = _retry_delay(bot, lock_retry_base_seconds, attempt) + _log( + bot, + "warning", + f"Retryable state issue write failure (status {response.status_code}); retrying ({attempt}/{lock_api_retry_limit})", + state_issue_number=state_issue_number, + status_code=response.status_code, + retry_attempt=attempt, ) - time.sleep(delay) + _sleep(bot, delay) continue - print( - "ERROR: Exhausted retries while saving state issue " - f"#{state_issue_number}; last status {response.status_code}: {response.text}", - file=sys.stderr, + _log( + bot, + "error", + f"Exhausted retries while saving state issue #{state_issue_number}; last status {response.status_code}: {response.text}", + state_issue_number=state_issue_number, + status_code=response.status_code, ) return False - print( - f"ERROR: Unexpected status {response.status_code} while saving state issue: {response.text}", - file=sys.stderr, + _log( + bot, + "error", + f"Unexpected status {response.status_code} while saving state issue: {response.text}", + state_issue_number=state_issue_number, + status_code=response.status_code, ) return False - print(f"ERROR: Failed to save state to issue #{state_issue_number} after retries", file=sys.stderr) + _log(bot, "error", f"Failed to save state to issue #{state_issue_number} after retries", state_issue_number=state_issue_number) return False diff --git a/scripts/reviewer_bot_lib/sweeper.py b/scripts/reviewer_bot_lib/sweeper.py index a04394927..22f451b39 100644 --- a/scripts/reviewer_bot_lib/sweeper.py +++ b/scripts/reviewer_bot_lib/sweeper.py @@ -2,22 +2,27 @@ from __future__ import annotations -import io import json -import os -import zipfile from datetime import datetime, timedelta, timezone +from pathlib import Path from typing import Any -from urllib.parse import quote -from .reconcile import ( - _artifact_expected_name, - _artifact_expected_payload_name, - _clear_source_event_key, - _mark_reconciled_source_event, - _update_deferred_gap, - _was_reconciled_source_event, +from scripts.reviewer_bot_core import deferred_gap_diagnosis + +from . import deferred_gap_bookkeeping as gap_bookkeeping +from . import retrying +from . import sweeper_observer_correlation as observer_correlation +from .config import REVIEW_FRESHNESS_RUNBOOK_PATH +from .reconcile_payloads import artifact_expected_name as _artifact_expected_name +from .review_state import ( + accept_reviewer_review_from_live_review, + get_current_cycle_boundary, + record_reviewer_activity, + refresh_reviewer_review_from_live_preferred_review, + semantic_key_seen, ) +from .reviews import rebuild_pr_approval_state +from .runtime_protocols import SweeperContext def _now() -> datetime: @@ -28,7 +33,32 @@ def _now_iso() -> str: return _now().isoformat() +def _retention_days(bot: SweeperContext) -> int: + return int(bot.get_config_value("DEFERRED_ARTIFACT_RETENTION_DAYS", "7") or 7) + + +def _github_repository(bot: SweeperContext) -> str: + return bot.get_config_value("GITHUB_REPOSITORY", "") + + def _approval_pending_signature_from_runbook() -> dict | None: + runbook_path = Path(REVIEW_FRESHNESS_RUNBOOK_PATH) + if not runbook_path.exists(): + return None + signature_prefix = "- exact accepted field/value signature:" + for line in runbook_path.read_text(encoding="utf-8").splitlines(): + if not line.startswith(signature_prefix): + continue + raw = line.split(":", 1)[1].strip() + if not raw: + return None + if raw.startswith("`") and raw.endswith("`"): + raw = raw[1:-1] + try: + parsed = json.loads(raw) + except json.JSONDecodeError: + return None + return parsed if isinstance(parsed, dict) else None return None @@ -41,377 +71,112 @@ def parse_timestamp(value: Any) -> datetime | None: return None -def observer_run_reason_from_details(run_details: dict, runbook_signature: dict | None) -> str: - status = str(run_details.get("status", "")).strip() - conclusion = run_details.get("conclusion") - if runbook_signature and all(run_details.get(key) == value for key, value in runbook_signature.items()): - return "awaiting_observer_approval" - if status in {"queued", "in_progress"}: - return "observer_in_progress" - if status == "completed": - if conclusion == "success": - return "completed_success" - if conclusion in {"failure", "timed_out", "action_required", "stale"}: - return "observer_failed" - if conclusion == "cancelled": - return "observer_cancelled" - return "observer_state_unknown" - - -def can_mark_observer_run_missing(gap: dict, now: datetime | None = None) -> bool: - now = now or _now() - created_at = gap.get("source_event_created_at") - created_dt = parse_timestamp(created_at) - if created_dt is None or now < created_dt + timedelta(hours=24): - return False - return bool(gap.get("full_scan_complete") and gap.get("later_recheck_complete") and not gap.get("correlated_run_found") and not gap.get("approval_pending_evidence_retained")) - - -def classify_artifact_gap_reason(gap: dict, now: datetime | None = None) -> str: - now = now or _now() - retention_days = int(os.environ.get("DEFERRED_ARTIFACT_RETENTION_DAYS", "7")) - run_created_at = parse_timestamp(gap.get("run_created_at")) - if gap.get("artifact_seen_at") or gap.get("artifact_last_downloadable_at"): - return "artifact_expired" - if run_created_at is not None and gap.get("retention_window_documented") and now >= run_created_at + timedelta(days=retention_days): - return "artifact_expired" - if gap.get("artifact_inspection_complete"): - return "artifact_missing" - return "observer_state_unknown" +def _read_api_payload(bot: SweeperContext, endpoint: str) -> tuple[Any | None, str | None]: + try: + response = bot.github_api_request("GET", endpoint, retry_policy="idempotent_read", suppress_error_log=True) + except SystemExit: + payload = bot.github_api("GET", endpoint) + return payload, None if payload is not None else "unavailable" + if not response.ok: + return None, response.failure_kind or "unavailable" + return response.payload, None -def correlate_candidate_observer_runs( - source_event_key: str, - *, - source_event_kind: str, - source_event_created_at: str, - pr_number: int, - workflow_file: str, - workflow_runs: list[dict] | None, -) -> dict: - created_at = parse_timestamp(source_event_created_at) - if created_at is None: - return { - "status": "observer_state_unknown", - "reason": "invalid_source_event_created_at", - "candidate_run_ids": [], - "full_scan_complete": False, - "later_recheck_complete": False, - "correlated_run": None, - } - if workflow_runs is None: - return { - "status": "observer_state_unknown", - "reason": "workflow_run_scan_unavailable", - "candidate_run_ids": [], - "full_scan_complete": False, - "later_recheck_complete": False, - "correlated_run": None, - } - expected_event_map = { - "issue_comment:created": "issue_comment", - "pull_request_review:submitted": "pull_request_review", - "pull_request_review:dismissed": "pull_request_review", - "pull_request_review_comment:created": "pull_request_review_comment", - } - expected_event = expected_event_map.get(source_event_kind) - if expected_event is None: - return { - "status": "observer_state_unknown", - "reason": "unsupported_source_event_kind", - "candidate_run_ids": [], - "full_scan_complete": False, - "later_recheck_complete": False, - "correlated_run": None, - } - window_start = created_at - timedelta(minutes=2) - window_end = created_at + timedelta(minutes=30) - candidates: list[dict] = [] - for run in workflow_runs: - if not isinstance(run, dict): - continue - if str(run.get("event", "")) != expected_event: - continue - created = parse_timestamp(run.get("created_at")) - if created is None or created < window_start or created > window_end: - continue - if str(run.get("path", "")) != workflow_file: - continue - repo = run.get("repository") - if isinstance(repo, dict): - full_name = repo.get("full_name") - if isinstance(full_name, str) and full_name != os.environ.get("GITHUB_REPOSITORY", ""): - continue - prs = run.get("pull_requests") - if isinstance(prs, list) and prs: - if not any(isinstance(pr, dict) and pr.get("number") == pr_number for pr in prs): - continue - candidates.append(run) - candidate_run_ids = [run.get("id") for run in candidates if isinstance(run.get("id"), int)] - return { - "status": "candidate_runs_found" if candidates else "no_candidate_runs", - "reason": None, - "candidate_runs": candidates, - "candidate_run_ids": candidate_run_ids, - "full_scan_complete": True, - "later_recheck_complete": False, - "correlated_run": None, - } +def _download_retry_delay(bot: SweeperContext, retry_attempt: int) -> float: + base = float(bot.lock_retry_base_seconds()) + return retrying.bounded_exponential_delay(base, retry_attempt, jitter=bot.jitter) -def correlate_run_artifacts_exact( - payloads_by_run: dict[int, list[dict]] | None, - source_event_key: str, - *, - pr_number: int, -) -> dict: - if payloads_by_run is None: - return {"status": "observer_state_unknown", "reason": "artifact_scan_unavailable", "correlated_run": None} - matches: list[tuple[int, dict]] = [] - candidate_run_ids: list[int] = [] - for run_id, payloads in payloads_by_run.items(): - if not isinstance(run_id, int): - continue - candidate_run_ids.append(run_id) - latest_by_attempt: dict[int, dict] = {} - for artifact_payload in payloads: - if not isinstance(artifact_payload, dict): - continue - attempt = artifact_payload.get("source_run_attempt") - if not isinstance(attempt, int): - continue - previous = latest_by_attempt.get(attempt) - if previous is None or artifact_payload.get("source_event_key") == source_event_key: - latest_by_attempt[attempt] = artifact_payload - for artifact_payload in latest_by_attempt.values(): - if artifact_payload.get("source_event_key") != source_event_key: - continue - if artifact_payload.get("source_run_id") != run_id: - continue - if artifact_payload.get("pr_number") != pr_number: - continue - matches.append((run_id, artifact_payload)) - if not matches: - return { - "status": "no_exact_artifact_match", - "reason": "no_exact_source_event_key_match", - "correlated_run": None, - "candidate_run_ids": sorted(candidate_run_ids), - } - distinct_run_ids = sorted({run_id for run_id, _ in matches}) - if len(distinct_run_ids) > 1: - return { - "status": "observer_state_unknown", - "reason": "ambiguous_exact_artifact_matches", - "correlated_run": None, - "candidate_run_ids": distinct_run_ids, - } - run_id, matched_payload = matches[-1] - return { - "status": "exact_artifact_match", - "reason": None, - "correlated_run": run_id, - "artifact_payload": matched_payload, - "candidate_run_ids": distinct_run_ids, - } +def _sleep(bot: SweeperContext, seconds: float) -> None: + bot.sleeper.sleep(seconds) -def _fetch_workflow_runs_for_file(bot, workflow_file: str, event_name: str) -> list[dict] | None: - runs: list[dict] = [] - page = 1 - encoded_workflow = quote(workflow_file, safe="") - while True: - response = bot.github_api( - "GET", - f"actions/workflows/{encoded_workflow}/runs?event={quote(event_name, safe='')}&per_page=100&page={page}", - ) - if response is None: - return None - workflow_runs = response.get("workflow_runs") if isinstance(response, dict) else None - if not isinstance(workflow_runs, list): - return None - runs.extend([run for run in workflow_runs if isinstance(run, dict)]) - if len(workflow_runs) < 100: - return runs - page += 1 +_fetch_workflow_runs_for_file = observer_correlation.fetch_workflow_runs_for_file +_fetch_run_detail = observer_correlation.fetch_run_detail +inspect_run_artifact_payloads = observer_correlation.inspect_run_artifact_payloads +_update_observer_watermark = observer_correlation.update_observer_watermark -def _fetch_run_detail(bot, run_id: int) -> dict | None: - response = bot.github_api("GET", f"actions/runs/{run_id}") - if isinstance(response, dict): - return response - return None +def _complete_surface_scan(bot, review_data: dict, surface: str, discovered: list[dict]) -> None: + observer_correlation.complete_surface_scan( + bot, + review_data, + surface, + discovered, + _load_surface_watermark, + ) -def _list_run_artifacts(bot, run_id: int) -> list[dict] | None: - artifacts: list[dict] = [] - page = 1 - while True: - response = bot.github_api("GET", f"actions/runs/{run_id}/artifacts?per_page=100&page={page}") - if response is None: - return None - page_artifacts = response.get("artifacts") if isinstance(response, dict) else None - if not isinstance(page_artifacts, list): - return None - artifacts.extend([artifact for artifact in page_artifacts if isinstance(artifact, dict)]) - if len(page_artifacts) < 100: - return artifacts - page += 1 -def _download_artifact_payload(bot, artifact: dict, expected_payload_name: str) -> tuple[str, dict | None]: - if artifact.get("expired") is True: - return "expired", None - download_url = artifact.get("archive_download_url") - if not isinstance(download_url, str) or not download_url: - return "missing_download_url", None - response = bot.requests.request( - "GET", - download_url, - headers={ - "Authorization": f"Bearer {bot.get_github_token()}", - "Accept": "application/vnd.github+json", - "X-GitHub-Api-Version": "2022-11-28", - }, +def _diagnose_deferred_event( + bot, + review_data: dict, + *, + source_event_key: str, + source_event_name: str, + source_event_action: str, + source_created_at: str, + issue_number: int, + workflow_file: str, + source_event_kind: str, + workflow_runs: list[dict] | None, +) -> None: + existing_gap = review_data.get("deferred_gaps", {}).get(source_event_key, {}) + run_correlation = deferred_gap_diagnosis.correlate_candidate_observer_runs( + source_event_key, + source_event_kind=source_event_kind, + source_event_created_at=source_created_at, + pr_number=issue_number, + workflow_file=workflow_file, + workflow_runs=workflow_runs, + github_repository=_github_repository(bot), ) - if response.status_code >= 400: - return "download_failed", None - try: - with zipfile.ZipFile(io.BytesIO(response.content)) as archive: - payload_files = [name for name in archive.namelist() if not name.endswith("/")] - if payload_files != [expected_payload_name]: - return "invalid_payload_layout", None - with archive.open(expected_payload_name) as handle: - payload = json.loads(handle.read().decode("utf-8")) - except (zipfile.BadZipFile, json.JSONDecodeError, OSError, UnicodeDecodeError): - return "invalid_payload_format", None - if not isinstance(payload, dict): - return "invalid_payload_format", None - return "ok", payload - - -def inspect_run_artifact_payloads(bot, workflow_runs: list[dict], source_event_key: str, *, pr_number: int, source_event_kind: str) -> dict: - payloads_by_run: dict[int, list[dict]] = {} - prior_visibility: dict[int, dict[str, str]] = {} - artifact_scan_outcomes: dict[int, str] = {} - event_name, event_action = source_event_kind.split(":", 1) - expected_payload_name = _artifact_expected_payload_name( + run_correlation["later_recheck_complete"] = bool(existing_gap.get("full_scan_complete")) + artifact_correlation = None + run_detail = None + if run_correlation.get("status") == "candidate_runs_found": + artifact_correlation = inspect_run_artifact_payloads( + bot, + run_correlation.get("candidate_runs", []), + source_event_key, + pr_number=issue_number, + source_event_kind=source_event_kind, + ) + exact_run_id = artifact_correlation.get("correlated_run") if isinstance(artifact_correlation, dict) else None + if isinstance(exact_run_id, int): + run_correlation["correlated_run"] = exact_run_id + run_correlation["correlated_run_found"] = True + run_detail = _maybe_fetch_single_candidate_run_detail(bot, run_correlation, artifact_correlation) + reason, diagnostic_reason = deferred_gap_diagnosis.evaluate_deferred_gap_state( { - "source_event_name": event_name, - "source_event_action": event_action, - } + **existing_gap, + "source_event_created_at": source_created_at, + }, + run_correlation, + run_detail, + artifact_correlation, + runbook_signature=_approval_pending_signature_from_runbook(), + ) + _record_gap_diagnostics( + bot, + review_data, + source_event_key, + source_event_name=source_event_name, + source_event_action=source_event_action, + issue_number=issue_number, + source_created_at=source_created_at, + workflow_file=workflow_file, + run_correlation=run_correlation, + run_detail=run_detail, + artifact_correlation=artifact_correlation, + reason=reason, + diagnostic_reason=diagnostic_reason, ) - for run in workflow_runs: - run_id = run.get("id") - if not isinstance(run_id, int): - continue - run_attempt = run.get("run_attempt") - if not isinstance(run_attempt, int): - continue - expected_name = _artifact_expected_name( - { - "source_event_name": event_name, - "source_event_action": event_action, - "source_run_id": run_id, - "source_run_attempt": run_attempt, - } - ) - artifacts = _list_run_artifacts(bot, run_id) - if artifacts is None: - return {"status": "observer_state_unknown", "reason": "artifact_listing_unavailable", "payloads_by_run": None} - filtered = [] - for artifact in artifacts: - name = artifact.get("name") - if not isinstance(name, str) or name != expected_name: - continue - filtered.append(artifact) - prior_visibility[run_id] = {"artifact_seen_at": _now_iso()} - status, payload = _download_artifact_payload(bot, artifact, expected_payload_name) - if status == "ok" and isinstance(payload, dict): - payloads_by_run.setdefault(run_id, []).append(payload) - artifact_scan_outcomes[run_id] = "ok" - elif status == "expired": - prior_visibility[run_id]["artifact_last_downloadable_at"] = prior_visibility[run_id]["artifact_seen_at"] - artifact_scan_outcomes[run_id] = "expired" - else: - artifact_scan_outcomes[run_id] = status - if run_id not in payloads_by_run and filtered: - payloads_by_run.setdefault(run_id, []) - result = correlate_run_artifacts_exact(payloads_by_run, source_event_key, pr_number=pr_number) - result["payloads_by_run"] = payloads_by_run - result["prior_visibility"] = prior_visibility - result["artifact_scan_outcomes"] = artifact_scan_outcomes - return result - - -def evaluate_deferred_gap_state( - existing_gap: dict, - run_correlation: dict, - run_detail: dict | None, - artifact_correlation: dict | None, -) -> tuple[str, str]: - if run_correlation.get("status") == "observer_state_unknown": - return "observer_state_unknown", str(run_correlation.get("reason") or "run_scan_unknown") - if run_correlation.get("status") == "no_candidate_runs": - gap = dict(existing_gap) - gap["full_scan_complete"] = bool(run_correlation.get("full_scan_complete")) - gap["later_recheck_complete"] = bool(run_correlation.get("later_recheck_complete")) - gap["correlated_run_found"] = False - gap["approval_pending_evidence_retained"] = False - if can_mark_observer_run_missing(gap): - return "observer_run_missing", "negative_inference_satisfied" - created_at = parse_timestamp(existing_gap.get("source_event_created_at")) - if created_at is not None and _now() < created_at + timedelta(hours=24): - return "awaiting_observer_run", "missing_run_window_open" - return "observer_state_unknown", "missing_run_window_not_proven" - if run_detail is None: - return "observer_state_unknown", "run_detail_unavailable" - run_state = observer_run_reason_from_details(run_detail, _approval_pending_signature_from_runbook()) - if run_state in { - "awaiting_observer_approval", - "observer_in_progress", - "observer_failed", - "observer_cancelled", - "observer_state_unknown", - }: - return run_state, f"run_detail:{run_state}" - if run_state != "completed_success": - return "observer_state_unknown", "unmapped_run_state" - if artifact_correlation is None: - return "artifact_missing", "artifact_correlation_unavailable" - artifact_status = artifact_correlation.get("status") - if artifact_status == "exact_artifact_match": - return "observer_state_unknown", "successful_artifact_present_without_reconcile_marker" - if artifact_status == "no_exact_artifact_match": - scan_outcomes = artifact_correlation.get("artifact_scan_outcomes") - if isinstance(scan_outcomes, dict): - if any(outcome == "expired" for outcome in scan_outcomes.values()): - return "artifact_expired", "prior_visibility_or_retention_proof_required" - invalid_outcomes = {"missing_download_url", "download_failed", "invalid_payload_layout", "invalid_payload_format"} - if any(outcome in invalid_outcomes for outcome in scan_outcomes.values()): - return "artifact_invalid", "artifact_download_or_payload_invalid" - return "artifact_missing", str(artifact_correlation.get("reason") or "exact_artifact_missing") - if artifact_status == "observer_state_unknown": - return "observer_state_unknown", str(artifact_correlation.get("reason") or "artifact_ambiguity") - return "artifact_invalid", str(artifact_correlation.get("reason") or "artifact_invalid") - - -def _update_observer_watermark(bot, review_data: dict, surface: str, event_time: str, event_id: str) -> None: - watermarks = review_data.setdefault("observer_discovery_watermarks", {}) - current = watermarks.get(surface) if isinstance(watermarks.get(surface), dict) else {} - watermarks[surface] = { - "last_scan_started_at": current.get("last_scan_started_at") or _now_iso(), - "last_scan_completed_at": _now_iso(), - "last_safe_event_time": event_time, - "last_safe_event_id": event_id, - "lookback_seconds": bot.DEFERRED_DISCOVERY_OVERLAP_SECONDS if hasattr(bot, "DEFERRED_DISCOVERY_OVERLAP_SECONDS") else 3600, - "bootstrap_window_seconds": bot.DEFERRED_DISCOVERY_BOOTSTRAP_WINDOW_SECONDS if hasattr(bot, "DEFERRED_DISCOVERY_BOOTSTRAP_WINDOW_SECONDS") else 604800, - "bootstrap_completed_at": current.get("bootstrap_completed_at") or _now_iso(), - } def _load_surface_watermark(review_data: dict, surface: str) -> dict: - watermarks = review_data.setdefault("observer_discovery_watermarks", {}) + watermarks = gap_bookkeeping._observer_discovery_watermarks(review_data) current = watermarks.get(surface) if isinstance(current, dict): return current @@ -441,7 +206,22 @@ def _list_issue_comments_paginated(bot, issue_number: int) -> tuple[list[dict] | comments: list[dict] = [] page = 1 while True: - response = bot.github_api("GET", f"issues/{issue_number}/comments?per_page=100&page={page}") + response, _ = _read_api_payload(bot, f"issues/{issue_number}/comments?per_page=100&page={page}") + if response is None: + return None, False + if not isinstance(response, list): + return None, False + comments.extend([comment for comment in response if isinstance(comment, dict)]) + if len(response) < 100: + return comments, True + page += 1 + + +def _list_review_comments_paginated(bot, issue_number: int) -> tuple[list[dict] | None, bool]: + comments: list[dict] = [] + page = 1 + while True: + response, _ = _read_api_payload(bot, f"pulls/{issue_number}/comments?per_page=100&page={page}") if response is None: return None, False if not isinstance(response, list): @@ -470,7 +250,7 @@ def _is_automation_comment(comment: dict) -> bool: def _fetch_live_issue_comment(bot, comment_id: str) -> dict | None: if not comment_id.isdigit(): return None - response = bot.github_api("GET", f"issues/comments/{comment_id}") + response, _ = _read_api_payload(bot, f"issues/comments/{comment_id}") return response if isinstance(response, dict) else None @@ -481,8 +261,8 @@ def _purge_bot_authored_comment_gap(bot, review_data: dict, source_event_key: st live_comment = _fetch_live_issue_comment(bot, comment_id) if not isinstance(live_comment, dict) or not _is_automation_comment(live_comment): return False - deferred_gaps = review_data.get("deferred_gaps") - if not isinstance(deferred_gaps, dict) or source_event_key not in deferred_gaps: + deferred_gaps = gap_bookkeeping._deferred_gaps(review_data) + if source_event_key not in deferred_gaps: return False deferred_gaps.pop(source_event_key, None) return True @@ -506,46 +286,31 @@ def _maybe_fetch_single_candidate_run_detail(bot, run_correlation: dict, artifac return _fetch_run_detail(bot, run_id) -def _can_repair_visible_review(bot, review_data: dict, review: dict, source_event_key: str) -> tuple[str, str, str] | None: - current_reviewer = review_data.get("current_reviewer") - if not isinstance(current_reviewer, str) or not current_reviewer.strip(): - return None - author = review.get("user", {}).get("login") if isinstance(review, dict) else None - commit_id = review.get("commit_id") if isinstance(review, dict) else None - submitted_at = review.get("submitted_at") if isinstance(review, dict) else None - review_id = review.get("id") if isinstance(review, dict) else None - if not isinstance(author, str) or author.lower() != current_reviewer.lower(): - return None - if not isinstance(commit_id, str) or not commit_id.strip(): - return None - if not isinstance(submitted_at, str): - return None - boundary = bot.reviews_module.get_current_cycle_boundary(bot, review_data) - submitted_dt = parse_timestamp(submitted_at) - if boundary is None or submitted_dt is None or submitted_dt < boundary: - return None - if source_event_key != f"pull_request_review:{review_id}": - return None - return author, submitted_at, commit_id - - def _repair_visible_review_gap(bot, review_data: dict, issue_number: int, source_event_key: str, review: dict) -> bool: - repair = _can_repair_visible_review(bot, review_data, review, source_event_key) + repair = deferred_gap_diagnosis.recommend_review_submission_gap_repair( + review_data, + review, + source_event_key, + artifact_status=None, + current_cycle_boundary=get_current_cycle_boundary(bot, review_data), + ) if repair is None: return False - author, submitted_at, commit_id = repair - changed = bot.reviews_module.accept_reviewer_review_from_live_review(review_data, review, actor=author) - changed = bot.reviews_module.refresh_reviewer_review_from_live_preferred_review( + payload = repair["payload"] + author = str(payload["author"]) + submitted_at = str(payload["submitted_at"]) + changed = accept_reviewer_review_from_live_review(review_data, review, actor=author) + changed = refresh_reviewer_review_from_live_preferred_review( bot, issue_number, review_data, actor=author, )[0] or changed - bot.reviews_module.record_reviewer_activity(review_data, submitted_at) - completion, _ = bot.reviews_module.rebuild_pr_approval_state(bot, issue_number, review_data) - _mark_reconciled_source_event(review_data, source_event_key) - _clear_source_event_key(review_data, source_event_key) - return changed or completion is not None + record_reviewer_activity(review_data, submitted_at) + completion, _ = rebuild_pr_approval_state(bot, issue_number, review_data) + reconciled_changed = gap_bookkeeping._mark_reconciled_source_event(review_data, source_event_key) + gap_cleared_changed = gap_bookkeeping._clear_source_event_key(review_data, source_event_key) + return changed or completion is not None or reconciled_changed or gap_cleared_changed def _discover_visible_comment_events(bot, issue_number: int, review_data: dict) -> tuple[list[dict] | None, bool]: @@ -583,7 +348,7 @@ def _discover_visible_comment_events(bot, issue_number: int, review_data: dict) def _discover_visible_review_events(bot, issue_number: int, review_data: dict) -> tuple[list[dict] | None, bool]: watermark = _load_surface_watermark(review_data, "reviews_submitted") watermark["last_scan_started_at"] = _now_iso() - reviews = bot.get_pull_request_reviews(issue_number) + reviews = bot.github.get_pull_request_reviews(issue_number) if reviews is None: return None, False floor = _surface_scan_floor(bot, watermark) @@ -616,11 +381,9 @@ def _discover_visible_review_events(bot, issue_number: int, review_data: dict) - def _discover_visible_review_comment_events(bot, issue_number: int, review_data: dict) -> tuple[list[dict] | None, bool]: watermark = _load_surface_watermark(review_data, "review_comments") watermark["last_scan_started_at"] = _now_iso() - comments = bot.github_api("GET", f"pulls/{issue_number}/comments?per_page=100") + comments, complete = _list_review_comments_paginated(bot, issue_number) if comments is None: return None, False - if not isinstance(comments, list): - return None, False floor = _surface_scan_floor(bot, watermark) discovered: list[dict] = [] for comment in comments: @@ -644,13 +407,13 @@ def _discover_visible_review_comment_events(bot, issue_number: int, review_data: "comment": comment, } ) - return discovered, True + return discovered, complete def _discover_visible_review_dismissal_events(bot, issue_number: int, review_data: dict) -> tuple[list[dict] | None, bool]: watermark = _load_surface_watermark(review_data, "reviews_dismissed") watermark["last_scan_started_at"] = _now_iso() - reviews = bot.get_pull_request_reviews(issue_number) + reviews = bot.github.get_pull_request_reviews(issue_number) if reviews is None: return None, False floor = _surface_scan_floor(bot, watermark) @@ -697,7 +460,16 @@ def _record_gap_diagnostics( reason: str, diagnostic_reason: str, ) -> None: - _update_deferred_gap( + payload_kind = None + if source_event_name == "pull_request_review" and source_event_action == "submitted": + payload_kind = "deferred_review_submitted" + elif source_event_name == "pull_request_review" and source_event_action == "dismissed": + payload_kind = "deferred_review_dismissed" + elif source_event_name == "pull_request_review_comment" and source_event_action == "created": + payload_kind = "deferred_review_comment" + elif source_event_name == "issue_comment" and source_event_action == "created": + payload_kind = "deferred_comment" + gap_bookkeeping._update_deferred_gap( bot, review_data, { @@ -711,6 +483,7 @@ def _record_gap_diagnostics( "source_run_attempt": run_detail.get("run_attempt") if isinstance(run_detail, dict) else None, "source_artifact_name": _artifact_expected_name( { + "payload_kind": payload_kind, "source_event_name": source_event_name, "source_event_action": source_event_action, "source_run_id": run_correlation.get("correlated_run") or 0, @@ -721,7 +494,7 @@ def _record_gap_diagnostics( reason, f"Trusted sweeper diagnostics for {source_event_key}: {diagnostic_reason}. See {bot.REVIEW_FRESHNESS_RUNBOOK_PATH}.", ) - gap = review_data["deferred_gaps"][source_event_key] + gap = gap_bookkeeping._deferred_gaps(review_data)[source_event_key] gap["full_scan_complete"] = bool(run_correlation.get("full_scan_complete")) gap["later_recheck_complete"] = bool(run_correlation.get("later_recheck_complete")) gap["correlated_run_found"] = bool(run_correlation.get("correlated_run")) @@ -737,10 +510,11 @@ def _record_gap_diagnostics( def _should_skip_discovered_key(bot, review_data: dict, source_event_key: str, channels: tuple[str, ...]) -> bool: - if _was_reconciled_source_event(review_data, source_event_key): + if gap_bookkeeping._was_reconciled_source_event(review_data, source_event_key): return True - if source_event_key in review_data.get("deferred_gaps", {}): - existing_gap = review_data["deferred_gaps"].get(source_event_key) + deferred_gaps = gap_bookkeeping._deferred_gaps(review_data) + if source_event_key in deferred_gaps: + existing_gap = deferred_gaps.get(source_event_key) if isinstance(existing_gap, dict) and existing_gap.get("reason") in { "awaiting_observer_run", "awaiting_observer_approval", @@ -755,7 +529,7 @@ def _should_skip_discovered_key(bot, review_data: dict, source_event_key: str, c "reconcile_failed_closed", }: return False - return any(bot.reviews_module._semantic_key_seen(review_data, channel, source_event_key) for channel in channels) + return any(semantic_key_seen(review_data, channel, source_event_key) for channel in channels) def sweep_deferred_gaps(bot, state: dict) -> bool: @@ -767,14 +541,13 @@ def sweep_deferred_gaps(bot, state: dict) -> bool: if not isinstance(review_data, dict): continue issue_number = int(issue_key) - pull_request = bot.github_api("GET", f"pulls/{issue_number}") + pull_request, _ = _read_api_payload(bot, f"pulls/{issue_number}") if not isinstance(pull_request, dict) or str(pull_request.get("state", "")).lower() != "open": continue - deferred_gaps = review_data.get("deferred_gaps") - if isinstance(deferred_gaps, dict): - for source_event_key in list(deferred_gaps): - if _purge_bot_authored_comment_gap(bot, review_data, source_event_key): - changed = True + deferred_gaps = gap_bookkeeping._deferred_gaps(review_data) + for source_event_key in list(deferred_gaps): + if _purge_bot_authored_comment_gap(bot, review_data, source_event_key): + changed = True discovered_comments, comments_complete = _discover_visible_comment_events(bot, issue_number, review_data) if comments_complete and isinstance(discovered_comments, list): for discovered in discovered_comments: @@ -782,66 +555,22 @@ def sweep_deferred_gaps(bot, state: dict) -> bool: created_at = discovered["source_created_at"] if _should_skip_discovered_key(bot, review_data, source_event_key, ("reviewer_comment", "contributor_comment")): continue - existing_gap = review_data.get("deferred_gaps", {}).get(source_event_key, {}) workflow_file = ".github/workflows/reviewer-bot-pr-comment-observer.yml" workflow_runs = _fetch_workflow_runs_for_file(bot, workflow_file, "issue_comment") - run_correlation = correlate_candidate_observer_runs( - source_event_key, - source_event_kind="issue_comment:created", - source_event_created_at=created_at, - pr_number=issue_number, - workflow_file=workflow_file, - workflow_runs=workflow_runs, - ) - run_correlation["later_recheck_complete"] = bool(existing_gap.get("full_scan_complete")) - artifact_correlation = None - run_detail = None - if run_correlation.get("status") == "candidate_runs_found": - artifact_correlation = inspect_run_artifact_payloads( - bot, - run_correlation.get("candidate_runs", []), - source_event_key, - pr_number=issue_number, - source_event_kind="issue_comment:created", - ) - exact_run_id = artifact_correlation.get("correlated_run") if isinstance(artifact_correlation, dict) else None - if isinstance(exact_run_id, int): - run_correlation["correlated_run"] = exact_run_id - run_correlation["correlated_run_found"] = True - run_detail = _maybe_fetch_single_candidate_run_detail(bot, run_correlation, artifact_correlation) - reason, diagnostic_reason = evaluate_deferred_gap_state( - { - **existing_gap, - "source_event_created_at": created_at, - }, - run_correlation, - run_detail, - artifact_correlation, - ) - _record_gap_diagnostics( + _diagnose_deferred_event( bot, review_data, - source_event_key, + source_event_key=source_event_key, source_event_name="issue_comment", source_event_action="created", issue_number=issue_number, source_created_at=created_at, workflow_file=workflow_file, - run_correlation=run_correlation, - run_detail=run_detail, - artifact_correlation=artifact_correlation, - reason=reason, - diagnostic_reason=diagnostic_reason, + source_event_kind="issue_comment:created", + workflow_runs=workflow_runs, ) changed = True - if discovered_comments: - last_comment = discovered_comments[-1] - _update_observer_watermark(bot, review_data, "comments", last_comment["source_created_at"], last_comment["object_id"]) - else: - watermark = _load_surface_watermark(review_data, "comments") - watermark["last_scan_started_at"] = watermark.get("last_scan_started_at") or _now_iso() - watermark["last_scan_completed_at"] = _now_iso() - watermark["bootstrap_completed_at"] = watermark.get("bootstrap_completed_at") or _now_iso() + _complete_surface_scan(bot, review_data, "comments", discovered_comments) discovered_reviews, reviews_complete = _discover_visible_review_events(bot, issue_number, review_data) if reviews_complete and isinstance(discovered_reviews, list): for discovered in discovered_reviews: @@ -849,16 +578,17 @@ def sweep_deferred_gaps(bot, state: dict) -> bool: submitted_at = discovered["source_created_at"] if _should_skip_discovered_key(bot, review_data, source_event_key, ("reviewer_review",)): continue - existing_gap = review_data.get("deferred_gaps", {}).get(source_event_key, {}) + existing_gap = gap_bookkeeping._deferred_gaps(review_data).get(source_event_key, {}) workflow_file = ".github/workflows/reviewer-bot-pr-review-submitted-observer.yml" workflow_runs = _fetch_workflow_runs_for_file(bot, workflow_file, "pull_request_review") - run_correlation = correlate_candidate_observer_runs( + run_correlation = deferred_gap_diagnosis.correlate_candidate_observer_runs( source_event_key, source_event_kind="pull_request_review:submitted", source_event_created_at=submitted_at, pr_number=issue_number, workflow_file=workflow_file, workflow_runs=workflow_runs, + github_repository=_github_repository(bot), ) run_correlation["later_recheck_complete"] = bool(existing_gap.get("full_scan_complete")) artifact_correlation = None @@ -878,14 +608,23 @@ def sweep_deferred_gaps(bot, state: dict) -> bool: run_detail = _maybe_fetch_single_candidate_run_detail(bot, run_correlation, artifact_correlation) review_payload = discovered.get("review") if isinstance(discovered.get("review"), dict) else None artifact_status = artifact_correlation.get("status") if isinstance(artifact_correlation, dict) else None - if ( - review_payload is not None - and artifact_status != "exact_artifact_match" - and _repair_visible_review_gap(bot, review_data, issue_number, source_event_key, review_payload) + repair_recommendation = deferred_gap_diagnosis.recommend_review_submission_gap_repair( + review_data, + review_payload, + source_event_key, + artifact_status=artifact_status, + current_cycle_boundary=get_current_cycle_boundary(bot, review_data), + ) + if repair_recommendation is not None and _repair_visible_review_gap( + bot, + review_data, + issue_number, + source_event_key, + review_payload, ): changed = True continue - reason, diagnostic_reason = evaluate_deferred_gap_state( + reason, diagnostic_reason = deferred_gap_diagnosis.evaluate_deferred_gap_state( { **existing_gap, "source_event_created_at": submitted_at, @@ -893,6 +632,7 @@ def sweep_deferred_gaps(bot, state: dict) -> bool: run_correlation, run_detail, artifact_correlation, + runbook_signature=_approval_pending_signature_from_runbook(), ) _record_gap_diagnostics( bot, @@ -925,66 +665,22 @@ def sweep_deferred_gaps(bot, state: dict) -> bool: created_at = discovered["source_created_at"] if _should_skip_discovered_key(bot, review_data, source_event_key, ("reviewer_comment", "contributor_comment")): continue - existing_gap = review_data.get("deferred_gaps", {}).get(source_event_key, {}) workflow_file = ".github/workflows/reviewer-bot-pr-review-comment-observer.yml" workflow_runs = _fetch_workflow_runs_for_file(bot, workflow_file, "pull_request_review_comment") - run_correlation = correlate_candidate_observer_runs( - source_event_key, - source_event_kind="pull_request_review_comment:created", - source_event_created_at=created_at, - pr_number=issue_number, - workflow_file=workflow_file, - workflow_runs=workflow_runs, - ) - run_correlation["later_recheck_complete"] = bool(existing_gap.get("full_scan_complete")) - artifact_correlation = None - run_detail = None - if run_correlation.get("status") == "candidate_runs_found": - artifact_correlation = inspect_run_artifact_payloads( - bot, - run_correlation.get("candidate_runs", []), - source_event_key, - pr_number=issue_number, - source_event_kind="pull_request_review_comment:created", - ) - exact_run_id = artifact_correlation.get("correlated_run") if isinstance(artifact_correlation, dict) else None - if isinstance(exact_run_id, int): - run_correlation["correlated_run"] = exact_run_id - run_correlation["correlated_run_found"] = True - run_detail = _maybe_fetch_single_candidate_run_detail(bot, run_correlation, artifact_correlation) - reason, diagnostic_reason = evaluate_deferred_gap_state( - { - **existing_gap, - "source_event_created_at": created_at, - }, - run_correlation, - run_detail, - artifact_correlation, - ) - _record_gap_diagnostics( + _diagnose_deferred_event( bot, review_data, - source_event_key, + source_event_key=source_event_key, source_event_name="pull_request_review_comment", source_event_action="created", issue_number=issue_number, source_created_at=created_at, workflow_file=workflow_file, - run_correlation=run_correlation, - run_detail=run_detail, - artifact_correlation=artifact_correlation, - reason=reason, - diagnostic_reason=diagnostic_reason, + source_event_kind="pull_request_review_comment:created", + workflow_runs=workflow_runs, ) changed = True - if discovered_review_comments: - last_comment = discovered_review_comments[-1] - _update_observer_watermark(bot, review_data, "review_comments", last_comment["source_created_at"], last_comment["object_id"]) - else: - watermark = _load_surface_watermark(review_data, "review_comments") - watermark["last_scan_started_at"] = watermark.get("last_scan_started_at") or _now_iso() - watermark["last_scan_completed_at"] = _now_iso() - watermark["bootstrap_completed_at"] = watermark.get("bootstrap_completed_at") or _now_iso() + _complete_surface_scan(bot, review_data, "review_comments", discovered_review_comments) discovered_dismissals, dismissals_complete = _discover_visible_review_dismissal_events(bot, issue_number, review_data) if dismissals_complete and isinstance(discovered_dismissals, list): for discovered in discovered_dismissals: @@ -992,64 +688,20 @@ def sweep_deferred_gaps(bot, state: dict) -> bool: dismissed_at = discovered["source_created_at"] if _should_skip_discovered_key(bot, review_data, source_event_key, ("review_dismissal",)): continue - existing_gap = review_data.get("deferred_gaps", {}).get(source_event_key, {}) workflow_file = ".github/workflows/reviewer-bot-pr-review-dismissed-observer.yml" workflow_runs = _fetch_workflow_runs_for_file(bot, workflow_file, "pull_request_review") - run_correlation = correlate_candidate_observer_runs( - source_event_key, - source_event_kind="pull_request_review:dismissed", - source_event_created_at=dismissed_at, - pr_number=issue_number, - workflow_file=workflow_file, - workflow_runs=workflow_runs, - ) - run_correlation["later_recheck_complete"] = bool(existing_gap.get("full_scan_complete")) - artifact_correlation = None - run_detail = None - if run_correlation.get("status") == "candidate_runs_found": - artifact_correlation = inspect_run_artifact_payloads( - bot, - run_correlation.get("candidate_runs", []), - source_event_key, - pr_number=issue_number, - source_event_kind="pull_request_review:dismissed", - ) - exact_run_id = artifact_correlation.get("correlated_run") if isinstance(artifact_correlation, dict) else None - if isinstance(exact_run_id, int): - run_correlation["correlated_run"] = exact_run_id - run_correlation["correlated_run_found"] = True - run_detail = _fetch_run_detail(bot, exact_run_id) - reason, diagnostic_reason = evaluate_deferred_gap_state( - { - **existing_gap, - "source_event_created_at": dismissed_at, - }, - run_correlation, - run_detail, - artifact_correlation, - ) - _record_gap_diagnostics( + _diagnose_deferred_event( bot, review_data, - source_event_key, + source_event_key=source_event_key, source_event_name="pull_request_review", source_event_action="dismissed", issue_number=issue_number, source_created_at=dismissed_at, workflow_file=workflow_file, - run_correlation=run_correlation, - run_detail=run_detail, - artifact_correlation=artifact_correlation, - reason=reason, - diagnostic_reason=diagnostic_reason, + source_event_kind="pull_request_review:dismissed", + workflow_runs=workflow_runs, ) changed = True - if discovered_dismissals: - last_dismissal = discovered_dismissals[-1] - _update_observer_watermark(bot, review_data, "reviews_dismissed", last_dismissal["source_created_at"], last_dismissal["object_id"]) - else: - watermark = _load_surface_watermark(review_data, "reviews_dismissed") - watermark["last_scan_started_at"] = watermark.get("last_scan_started_at") or _now_iso() - watermark["last_scan_completed_at"] = _now_iso() - watermark["bootstrap_completed_at"] = watermark.get("bootstrap_completed_at") or _now_iso() + _complete_surface_scan(bot, review_data, "reviews_dismissed", discovered_dismissals) return changed diff --git a/scripts/reviewer_bot_lib/sweeper_observer_correlation.py b/scripts/reviewer_bot_lib/sweeper_observer_correlation.py new file mode 100644 index 000000000..1ebc94f9c --- /dev/null +++ b/scripts/reviewer_bot_lib/sweeper_observer_correlation.py @@ -0,0 +1,204 @@ +"""Observer correlation helpers for reviewer-bot sweeper.""" + +from __future__ import annotations + +import io +import json +import zipfile +from typing import Any +from urllib.parse import quote + +from scripts.reviewer_bot_core import deferred_gap_diagnosis + +from . import deferred_gap_bookkeeping as gap_bookkeeping +from . import retrying +from .reconcile_payloads import artifact_expected_name, artifact_expected_payload_name + + +def _now_iso(bot) -> str: + return bot.clock.now().isoformat() + + +def _read_api_payload(bot, endpoint: str) -> tuple[Any | None, str | None]: + try: + response = bot.github_api_request("GET", endpoint, retry_policy="idempotent_read", suppress_error_log=True) + except SystemExit: + payload = bot.github_api("GET", endpoint) + return payload, None if payload is not None else "unavailable" + if not response.ok: + return None, response.failure_kind or "unavailable" + return response.payload, None + + +def _download_retry_delay(bot, retry_attempt: int) -> float: + return retrying.bounded_exponential_delay(float(bot.lock_retry_base_seconds()), retry_attempt, jitter=bot.jitter) + + +def _sleep(bot, seconds: float) -> None: + bot.sleeper.sleep(seconds) + + +def fetch_workflow_runs_for_file(bot, workflow_file: str, event_name: str) -> list[dict] | None: + runs: list[dict] = [] + page = 1 + encoded_workflow = quote(workflow_file, safe="") + while True: + response, _ = _read_api_payload(bot, f"actions/workflows/{encoded_workflow}/runs?event={quote(event_name, safe='')}&per_page=100&page={page}") + if response is None: + return None + workflow_runs = response.get("workflow_runs") if isinstance(response, dict) else None + if not isinstance(workflow_runs, list): + return None + runs.extend([run for run in workflow_runs if isinstance(run, dict)]) + if len(workflow_runs) < 100: + return runs + page += 1 + + +def fetch_run_detail(bot, run_id: int) -> dict | None: + response, _ = _read_api_payload(bot, f"actions/runs/{run_id}") + if isinstance(response, dict): + return response + return None + + +def _list_run_artifacts(bot, run_id: int) -> list[dict] | None: + artifacts: list[dict] = [] + page = 1 + while True: + response, _ = _read_api_payload(bot, f"actions/runs/{run_id}/artifacts?per_page=100&page={page}") + if response is None: + return None + page_artifacts = response.get("artifacts") if isinstance(response, dict) else None + if not isinstance(page_artifacts, list): + return None + artifacts.extend([artifact for artifact in page_artifacts if isinstance(artifact, dict)]) + if len(page_artifacts) < 100: + return artifacts + page += 1 + + +def _matching_payload(payload: dict, *, run_id: int, run_attempt: int, event_name: str, event_action: str, source_event_key: str, pr_number: int) -> bool: + return ( + payload.get("source_run_id") == run_id + and payload.get("source_run_attempt") == run_attempt + and payload.get("source_event_name") == event_name + and payload.get("source_event_action") == event_action + and payload.get("source_event_key") == source_event_key + and payload.get("pr_number") == pr_number + ) + + +def _download_artifact_payloads(bot, artifact: dict) -> tuple[str, list[dict] | None]: + if artifact.get("expired") is True: + return "expired", None + download_url = artifact.get("archive_download_url") + if not isinstance(download_url, str) or not download_url: + return "missing_download_url", None + max_attempts = int(bot.lock_api_retry_limit()) + 1 + response = None + for attempt in range(1, max_attempts + 1): + try: + response = bot.artifact_download_transport.download(download_url, headers={"Authorization": f"Bearer {bot.get_github_token()}", "Accept": "application/vnd.github+json", "X-GitHub-Api-Version": "2022-11-28"}) + except Exception: + if attempt < max_attempts: + _sleep(bot, _download_retry_delay(bot, attempt)) + continue + return "download_unavailable", None + if retrying.is_retryable_status(response.status_code): + if attempt < max_attempts: + _sleep(bot, _download_retry_delay(bot, attempt)) + continue + return "download_unavailable", None + break + if response is None: + return "download_unavailable", None + if response.status_code >= 400: + return "download_failed", None + try: + with zipfile.ZipFile(io.BytesIO(response.content)) as archive: + payloads = [] + for name in archive.namelist(): + if name.endswith("/") or not name.endswith(".json"): + continue + with archive.open(name) as handle: + payload = json.loads(handle.read().decode("utf-8")) + if not isinstance(payload, dict): + return "invalid_payload_format", None + payloads.append(payload) + except (zipfile.BadZipFile, json.JSONDecodeError, OSError, UnicodeDecodeError): + return "invalid_payload_format", None + return ("ok", payloads) if payloads else ("artifact_missing", None) + + +def inspect_run_artifact_payloads(bot, workflow_runs: list[dict], source_event_key: str, *, pr_number: int, source_event_kind: str) -> dict: + payloads_by_run: dict[int, list[dict]] = {} + prior_visibility: dict[int, dict[str, str]] = {} + artifact_scan_outcomes: dict[int, str] = {} + event_name, event_action = source_event_kind.split(":", 1) + for run in workflow_runs: + run_id = run.get("id") + run_attempt = run.get("run_attempt") + if not isinstance(run_id, int) or not isinstance(run_attempt, int): + continue + expected_name = artifact_expected_name({"source_event_name": event_name, "source_event_action": event_action, "source_run_id": run_id, "source_run_attempt": run_attempt}) + artifacts = _list_run_artifacts(bot, run_id) + if artifacts is None: + return {"status": "observer_state_unknown", "reason": "artifact_listing_unavailable", "payloads_by_run": None} + filtered = [] + for artifact in artifacts: + name = artifact.get("name") + if not isinstance(name, str) or name != expected_name: + continue + filtered.append(artifact) + prior_visibility[run_id] = {"artifact_seen_at": _now_iso(bot)} + status, payloads = _download_artifact_payloads(bot, artifact) + if status == "ok" and isinstance(payloads, list): + matches = [ + payload for payload in payloads + if _matching_payload(payload, run_id=run_id, run_attempt=run_attempt, event_name=event_name, event_action=event_action, source_event_key=source_event_key, pr_number=pr_number) + ] + if len(matches) == 1: + payloads_by_run.setdefault(run_id, []).append(matches[0]) + artifact_scan_outcomes[run_id] = "ok" + else: + artifact_scan_outcomes[run_id] = "artifact_invalid" + payloads_by_run.setdefault(run_id, []) + elif status == "expired": + prior_visibility[run_id]["artifact_last_downloadable_at"] = prior_visibility[run_id]["artifact_seen_at"] + artifact_scan_outcomes[run_id] = "expired" + else: + artifact_scan_outcomes[run_id] = "artifact_missing" if status == "artifact_missing" else "artifact_invalid" + if run_id not in payloads_by_run and filtered: + payloads_by_run.setdefault(run_id, []) + result = deferred_gap_diagnosis.correlate_run_artifacts_exact(payloads_by_run, source_event_key, pr_number=pr_number) + result["payloads_by_run"] = payloads_by_run + result["prior_visibility"] = prior_visibility + result["artifact_scan_outcomes"] = artifact_scan_outcomes + result["expected_payload_name"] = artifact_expected_payload_name({"source_event_name": event_name, "source_event_action": event_action}) + return result + + +def update_observer_watermark(bot, review_data: dict, surface: str, event_time: str, event_id: str) -> None: + watermarks = gap_bookkeeping._observer_discovery_watermarks(review_data) + current = watermarks.get(surface) if isinstance(watermarks.get(surface), dict) else {} + watermarks[surface] = { + "last_scan_started_at": current.get("last_scan_started_at") or _now_iso(bot), + "last_scan_completed_at": _now_iso(bot), + "last_safe_event_time": event_time, + "last_safe_event_id": event_id, + "lookback_seconds": bot.DEFERRED_DISCOVERY_OVERLAP_SECONDS if hasattr(bot, "DEFERRED_DISCOVERY_OVERLAP_SECONDS") else 3600, + "bootstrap_window_seconds": bot.DEFERRED_DISCOVERY_BOOTSTRAP_WINDOW_SECONDS if hasattr(bot, "DEFERRED_DISCOVERY_BOOTSTRAP_WINDOW_SECONDS") else 604800, + "bootstrap_completed_at": current.get("bootstrap_completed_at") or _now_iso(bot), + } + + +def complete_surface_scan(bot, review_data: dict, surface: str, discovered: list[dict], load_surface_watermark) -> None: + if discovered: + last_seen = discovered[-1] + update_observer_watermark(bot, review_data, surface, last_seen["source_created_at"], last_seen["object_id"]) + return + watermark = load_surface_watermark(review_data, surface) + watermark["last_scan_started_at"] = watermark.get("last_scan_started_at") or _now_iso(bot) + watermark["last_scan_completed_at"] = _now_iso(bot) + watermark["bootstrap_completed_at"] = watermark.get("bootstrap_completed_at") or _now_iso(bot) diff --git a/src/spec.lock b/src/spec.lock index d8148b189..7ddf8fd7d 100644 --- a/src/spec.lock +++ b/src/spec.lock @@ -2,20289 +2,20274 @@ "documents": [ { "informational": false, - "link": "lexical-elements.html", + "link": "entities-and-resolution.html", "sections": [ { - "id": "fls_411up5z0b6n6", - "informational": true, - "link": "lexical-elements.html", - "number": "2", - "paragraphs": [ - { - "checksum": "c63cd9576e1595dfb4d7d38e0c9732d20c4044d18947914c36952a45f6592248", - "id": "fls_pqwpf87b84tr", - "link": "lexical-elements.html#fls_pqwpf87b84tr", - "number": "2:1" - } - ], - "title": "Lexical Elements" + "id": "fls_gdeyap4or1db", + "informational": false, + "link": "entities-and-resolution.html", + "number": "14", + "paragraphs": [], + "title": "Entities and Resolution" }, { - "id": "fls_2i089jvv8j5g", + "id": "fls_151r19d7xbgz", "informational": false, - "link": "lexical-elements.html#character-set", - "number": "2.1", + "link": "entities-and-resolution.html#entities", + "number": "14.1", "paragraphs": [ { - "checksum": "40ca6b69e8a1f39dacde761ccaecbaca619375767abb05e925bec6c504198244", - "id": "fls_itcth8292ud6", - "link": "lexical-elements.html#fls_itcth8292ud6", - "number": "2.1:1" + "checksum": "92b15a7349815675a6ada6d976e81b32a73c72307b7c303744b672b2b79f3ccb", + "id": "fls_x7j6wcigqt7u", + "link": "entities-and-resolution.html#fls_x7j6wcigqt7u", + "number": "14.1:1" }, { - "checksum": "e82f4769c3930c55731cfdff64cba2bf3f3fe6f20f8bbb741ec95c4bce18f4d2", - "id": "fls_vfx8byq5zo8t", - "link": "lexical-elements.html#fls_vfx8byq5zo8t", - "number": "2.1:2" + "checksum": "3ac0c135867846c8545f48d505c7223de228d3c834e6c35369d20839f30645cc", + "id": "fls_40d2g0hvq2il", + "link": "entities-and-resolution.html#fls_40d2g0hvq2il", + "number": "14.1:2" }, { - "checksum": "a2710a596348e56b2b389ff7a77765369a97fcd53c60fc5c39a42050c3aa2b74", - "id": "fls_pvslhm3chtlb", - "link": "lexical-elements.html#fls_pvslhm3chtlb", - "number": "2.1:3" + "checksum": "387b393db8ebbfbbaa31538cf9d1ed1fbd883a62e99cd43daae557d55d6e3778", + "id": "fls_lcca91wjwnpx", + "link": "entities-and-resolution.html#fls_lcca91wjwnpx", + "number": "14.1:3" }, { - "checksum": "2a0b32b27fe371cd7e93f22f0a2244eba0ae620a5b844fe722d308d5cba30392", - "id": "fls_a5ec9cpn4sc8", - "link": "lexical-elements.html#fls_a5ec9cpn4sc8", - "number": "2.1:4" + "checksum": "1e043bf7a4a6c40020a77d37fba4d646c5e5114a2fd0ce467c817c77746df26f", + "id": "fls_94l2d7ti0hjw", + "link": "entities-and-resolution.html#fls_94l2d7ti0hjw", + "number": "14.1:4" }, { - "checksum": "66a1720f2e9d2efd1b01c5a8e3d89b8b70f8f707c0d5c446e6b1326f2e44e146", - "id": "fls_dgyrj49y3c7c", - "link": "lexical-elements.html#fls_dgyrj49y3c7c", - "number": "2.1:5" + "checksum": "cc53b299dafa70bd4b1bc538690c6759ff1aad962a25dd458050dd834792d02d", + "id": "fls_kvdqmo8gmdxi", + "link": "entities-and-resolution.html#fls_kvdqmo8gmdxi", + "number": "14.1:5" }, { - "checksum": "2d61867ea0852e491d286632860fa0111941830a7af9e3240f26cbce126f5a67", - "id": "fls_5ocmngyur7by", - "link": "lexical-elements.html#fls_5ocmngyur7by", - "number": "2.1:6" + "checksum": "62877b7edbba7547c96ed213430dab1fe6be842fc13b0905bf1e0e39c1574c5b", + "id": "fls_b3cdg74utyvo", + "link": "entities-and-resolution.html#fls_b3cdg74utyvo", + "number": "14.1:6" }, { - "checksum": "7f6017bf13927f894ed1460d26d072453606445d1dbff591da54b2f1eb9fe834", - "id": "fls_1aj0rgi9kpib", - "link": "lexical-elements.html#fls_1aj0rgi9kpib", - "number": "2.1:7" + "checksum": "df0355bf9d408621ab3501afd0c95fb72223e5ff74db7d62eaddc72e96f02fe7", + "id": "fls_njcmeqxzvfsa", + "link": "entities-and-resolution.html#fls_njcmeqxzvfsa", + "number": "14.1:7" }, { - "checksum": "ffc345f23396d082ca468df1855e3f5aca7a1585cff5388705d9c1a3f70e74bd", - "id": "fls_bfzdxsbq2c2q", - "link": "lexical-elements.html#fls_bfzdxsbq2c2q", - "number": "2.1:8" + "checksum": "9cd87b048aec0624e781b4917dc99e0e3956051679365ab7d33211685d93833e", + "id": "fls_63ul8sgf6dgr", + "link": "entities-and-resolution.html#fls_63ul8sgf6dgr", + "number": "14.1:8" }, { - "checksum": "02086e7f1249fcbd972c9e0518ee1939b64715df2d4770f72ce70c688b6338ad", - "id": "fls_vw0kq2y1o63m", - "link": "lexical-elements.html#fls_vw0kq2y1o63m", - "number": "2.1:9" + "checksum": "43a9474bc05f5cd0e56b671046a19f56d4d25db70d02d706182d94772fbd9525", + "id": "fls_6hxf0rn9j1sr", + "link": "entities-and-resolution.html#fls_6hxf0rn9j1sr", + "number": "14.1:9" }, { - "checksum": "82e2f578c850a061a6aaaad593ca77086467b3ecb1e21068c97a4c8d66c0aee0", - "id": "fls_ao296bmamwzh", - "link": "lexical-elements.html#fls_ao296bmamwzh", - "number": "2.1:10" + "checksum": "9db5af7bb0d5b4e2f0254774b4d7490d11b8de8e608b0b73eab3268c19fb91fb", + "id": "fls_2qitjk5ssaau", + "link": "entities-and-resolution.html#fls_2qitjk5ssaau", + "number": "14.1:10" }, { - "checksum": "8815f83db374f9a454841fc1f6b6390c6ecd954fabc83881cea39dcc3e76bfc7", - "id": "fls_6kymhq7embdh", - "link": "lexical-elements.html#fls_6kymhq7embdh", - "number": "2.1:11" + "checksum": "e602c23f513b2a0a4e008c3ea5961a7ed8f80dac05e98ee7a31d32e25387986d", + "id": "fls_4li2c5qc31c7", + "link": "entities-and-resolution.html#fls_4li2c5qc31c7", + "number": "14.1:11" }, { - "checksum": "e0d4111521e0ef7716e900e64befc27972b65b3002d9d3421d0df6e070601ecd", - "id": "fls_8mxmrxvhn3by", - "link": "lexical-elements.html#fls_8mxmrxvhn3by", - "number": "2.1:12" + "checksum": "f8bbcd5c0b5f9b29fea6dd2805dd0de209e9d9b443342686960740d64e79321f", + "id": "fls_nq8n7w2s3bja", + "link": "entities-and-resolution.html#fls_nq8n7w2s3bja", + "number": "14.1:12" }, { - "checksum": "8d7234572699415553e79da17fd2524d1fcc2a7efe1e5033a320ab62a52024e6", - "id": "fls_bc6D1ATvmJJr", - "link": "lexical-elements.html#fls_bc6D1ATvmJJr", - "number": "2.1:13" + "checksum": "fa112b87d033633e63c7f4216a3dac2dfa3bd0d440c7f574ab7a8375752d577d", + "id": "fls_jv7qi34flit0", + "link": "entities-and-resolution.html#fls_jv7qi34flit0", + "number": "14.1:13" }, { - "checksum": "e3b6a12dce515b8175a3e8b860650047b16ef9d3ca779fa9d7d654e44137ddc7", - "id": "fls_zfs15iel08y0", - "link": "lexical-elements.html#fls_zfs15iel08y0", - "number": "2.1:14" + "checksum": "e61e4578e8cd0a2124be00366bcc0ace261b47e5cc829bcf30274cd2b97c588f", + "id": "fls_rutlgmzh3tnz", + "link": "entities-and-resolution.html#fls_rutlgmzh3tnz", + "number": "14.1:14" }, { - "checksum": "0019b4f55d1b71c339a645baf16461aa169dbed3b2d823b8af749a41fe5c41ac", - "id": "fls_7eifv4ksunu1", - "link": "lexical-elements.html#fls_7eifv4ksunu1", - "number": "2.1:15" + "checksum": "79a170cb7c345638c6755e2d61f7444d2a35b53530a2cb6d81638b4f002de86e", + "id": "fls_1owx5ch7sidm", + "link": "entities-and-resolution.html#fls_1owx5ch7sidm", + "number": "14.1:15" }, { - "checksum": "8a24c7ea76335af8905267c0228e0a1fb01b2367fb5def7030a6d69e27d6a447", - "id": "fls_PIDKEm8GiLNL", - "link": "lexical-elements.html#fls_PIDKEm8GiLNL", - "number": "2.1:16" + "checksum": "25dffbf70cb2bb0cd22c431fd0a46235618a589b0bdbf4bf180b51ed37a9d5d6", + "id": "fls_8ldy7lec9bcd", + "link": "entities-and-resolution.html#fls_8ldy7lec9bcd", + "number": "14.1:16" }, { - "checksum": "d5ae1cce239b620e197250f6a2726fc8f495aee8a7115926a963718c9033130f", - "id": "fls_2brw13n9ldgy", - "link": "lexical-elements.html#fls_2brw13n9ldgy", - "number": "2.1:17" - } - ], - "title": "Character Set" - }, - { - "id": "fls_fgnllgz5k3e6", - "informational": false, - "link": "lexical-elements.html#lexical-elements-separators-and-punctuation", - "number": "2.2", - "paragraphs": [ - { - "checksum": "653136e8fac3c1c53040add411c925009e38f8501d503a48b3c73829f59e7bbc", - "id": "fls_d4nvxsvxj537", - "link": "lexical-elements.html#fls_d4nvxsvxj537", - "number": "2.2:1" + "checksum": "9f6fa9d43fbf2924e1c1ecc5b6eef6db41f10e0eda261f898fa00b0db1afddce", + "id": "fls_3mt2p4ssqt0a", + "link": "entities-and-resolution.html#fls_3mt2p4ssqt0a", + "number": "14.1:17" }, { - "checksum": "73d17b1543408a0260ebf6781483f768a98a08e65e7b3efd7a67056c0769a116", - "id": "fls_a1zylpqha73x", - "link": "lexical-elements.html#fls_a1zylpqha73x", - "number": "2.2:2" + "checksum": "79b617624b18e886c67a3fd5303e3c3701a1bd761fbd598742fa457fc62de196", + "id": "fls_qqwu3e98lktb", + "link": "entities-and-resolution.html#fls_qqwu3e98lktb", + "number": "14.1:18" }, { - "checksum": "11d71ea399510ce28be4e570347824ac5796e92cb7aeec1b5a5917ddac5f41dc", - "id": "fls_jy6wifn5r2bu", - "link": "lexical-elements.html#fls_jy6wifn5r2bu", - "number": "2.2:3" + "checksum": "3cb5bba3fd185734dbe976155c21323253786de2fe38287190058d186d71d532", + "id": "fls_fup6984lxdfy", + "link": "entities-and-resolution.html#fls_fup6984lxdfy", + "number": "14.1:19" }, { - "checksum": "f7f8a0f54827276c6d4560722f7e9788d530dd5d345822294140f92d6b9bca15", - "id": "fls_efdfq9nhpmp5", - "link": "lexical-elements.html#fls_efdfq9nhpmp5", - "number": "2.2:4" + "checksum": "d3626b9e94df64ec6a68712a8efc764bafebfd1c7736ff21617a39badd0aaffb", + "id": "fls_ji9iem1c7ekq", + "link": "entities-and-resolution.html#fls_ji9iem1c7ekq", + "number": "14.1:20" }, { - "checksum": "c9af1ffb3e58642391c41d16d4207e4f717098bcbe022bdf29bec6ce24663dc4", - "id": "fls_go25sisi5fdp", - "link": "lexical-elements.html#fls_go25sisi5fdp", - "number": "2.2:5" + "checksum": "65940ab579200a562e9131adc24d67f63cf6751522972925d98e3eae88aff033", + "id": "fls_v7w8ptbyxv9w", + "link": "entities-and-resolution.html#fls_v7w8ptbyxv9w", + "number": "14.1:21" }, { - "checksum": "761bcfdf8467cee695b29cd8d454ab4a558df08126df0edde891a74925371be6", - "id": "fls_a6t53o8h1vdk", - "link": "lexical-elements.html#fls_a6t53o8h1vdk", - "number": "2.2:6" + "checksum": "d2c4cc3aa6df1ff8c7acad8083bd97da4c7bd274a886f15fd95a0d2495bdbb3c", + "id": "fls_ig1l38gpy5gy", + "link": "entities-and-resolution.html#fls_ig1l38gpy5gy", + "number": "14.1:22" }, { - "checksum": "eb78e3a8a8e977064b2d7149e4306c21f9fcdfb8f18ceeb51eb2b40b9ac4a9c7", - "id": "fls_8fv63w6f4udl", - "link": "lexical-elements.html#fls_8fv63w6f4udl", - "number": "2.2:7" + "checksum": "87567b121feb41fb49a72ba0f88e7147e1ba7182f665fcd3118ee7cb4e446d2a", + "id": "fls_ed0t6u7fo3fi", + "link": "entities-and-resolution.html#fls_ed0t6u7fo3fi", + "number": "14.1:23" }, { - "checksum": "2db8aaaa096a0e58bd2ffc1db8997f6950ac40fd421ec7ba8b451a13f9a86de5", - "id": "fls_es0tz1q9cmoo", - "link": "lexical-elements.html#fls_es0tz1q9cmoo", - "number": "2.2:8" - }, + "checksum": "d9efa486af937ab8018d16e3b87ca3ba0732e7ca8ee67d91e37f85f401545d65", + "id": "fls_gjps01c8l6aa", + "link": "entities-and-resolution.html#fls_gjps01c8l6aa", + "number": "14.1:24" + } + ], + "title": "Entities" + }, + { + "id": "fls_jdknpu3kf865", + "informational": false, + "link": "entities-and-resolution.html#visibility", + "number": "14.2", + "paragraphs": [ { - "checksum": "f795577e13f973533719549c1acfa613203c8ccb340008307f46401e67c453d7", - "id": "fls_vm86olkeecer", - "link": "lexical-elements.html#fls_vm86olkeecer", - "number": "2.2:9" + "checksum": "14af7dc4459e85c32c0011005e8e1d05bae942ed8f5d4cb54aa713543425be90", + "id": "fls_7kpepal8ghuj", + "link": "entities-and-resolution.html#fls_7kpepal8ghuj", + "number": "14.2:1" }, { - "checksum": "a00d2db187c054f568dbc3e78a1605de3149d3c6abc4a34aa93f2710ba6ca2fd", - "id": "fls_5zxdgxy8tjrq", - "link": "lexical-elements.html#fls_5zxdgxy8tjrq", - "number": "2.2:10" + "checksum": "fca421955d7d461ea14be29c223491277febdafc297c8580073d5de797c6d03d", + "id": "fls_qo0itr5il1kk", + "link": "entities-and-resolution.html#fls_qo0itr5il1kk", + "number": "14.2:2" }, { - "checksum": "115718cd9f0c771924efeceb67f8b777b85cec578ef27ca2e037c38243f8f350", - "id": "fls_x89vkq9rwlyt", - "link": "lexical-elements.html#fls_x89vkq9rwlyt", - "number": "2.2:11" + "checksum": "5ca85d6ded39a41b0210496968063f04f76d7902d519e0044cb431f2888c032e", + "id": "fls_knjruq5wppv", + "link": "entities-and-resolution.html#fls_knjruq5wppv", + "number": "14.2:3" }, { - "checksum": "5f4e89c3e94a0b323a33d11a320cc4dcc9f49ad2aadb50b2bafe1222bc8ddf57", - "id": "fls_bo3xh8r60ji1", - "link": "lexical-elements.html#fls_bo3xh8r60ji1", - "number": "2.2:12" + "checksum": "f5f0a1362543a52c48a0832fff6511e7b9cdc663430d2e625d748329455133e9", + "id": "fls_t7i4n19qdgn4", + "link": "entities-and-resolution.html#fls_t7i4n19qdgn4", + "number": "14.2:4" }, { - "checksum": "6f99b7bbcf1e55392ad60df6c0625665b9dc85560643ad80f415189d8c7ea74d", - "id": "fls_sslkjuxjnteu", - "link": "lexical-elements.html#fls_sslkjuxjnteu", - "number": "2.2:13" + "checksum": "66f72eec45e87d39e392824cbccd92cde109e25c1ee3731ca8e7b27a1bf1b778", + "id": "fls_aa4f3rvir9lm", + "link": "entities-and-resolution.html#fls_aa4f3rvir9lm", + "number": "14.2:5" }, { - "checksum": "deb239c182541b9351e7079d839b668ddf59a12393d1595c39ea0a9e9ddb774e", - "id": "fls_9g1godm0jp0z", - "link": "lexical-elements.html#fls_9g1godm0jp0z", - "number": "2.2:14" + "checksum": "3e16d751015c5913b6d155171e1fe22c30d873dd4c5557cff7f046161cdcea45", + "id": "fls_tnh7o3pb4e22", + "link": "entities-and-resolution.html#fls_tnh7o3pb4e22", + "number": "14.2:6" }, { - "checksum": "0aa275a9c135534577ced486481a428aeb61c42968ca8a6ceafaada95c15ce69", - "id": "fls_6oith9q0soot", - "link": "lexical-elements.html#fls_6oith9q0soot", - "number": "2.2:15" + "checksum": "452fb25c8aa516688bcf11408b978c40b71cfd10786c03becf5b16b12856c292", + "id": "fls_yymgpyi67dty", + "link": "entities-and-resolution.html#fls_yymgpyi67dty", + "number": "14.2:7" }, { - "checksum": "da7de79db9b949ccd8272fe75b48914ed735298d02e452128b6f46462c2d48a6", - "id": "fls_1dledwdc8fa6", - "link": "lexical-elements.html#fls_1dledwdc8fa6", - "number": "2.2:16" + "checksum": "80ceeb14a02b69d81e041c8aa913ebc5ae529cf2ee64c36a4e3d2490d9362418", + "id": "fls_hc121mxknq03", + "link": "entities-and-resolution.html#fls_hc121mxknq03", + "number": "14.2:8" }, { - "checksum": "47d2b64fbe4f3b6669f313e326a56bc80c9e29d2609798c8fbd66944b711bc45", - "id": "fls_lunw7ucj5ius", - "link": "lexical-elements.html#fls_lunw7ucj5ius", - "number": "2.2:17" + "checksum": "5b655efb0d1d9c1ebf6eb86a27556a8c7c7f88e3eefb54012f7a4875c62c4c65", + "id": "fls_icztzxjpm1du", + "link": "entities-and-resolution.html#fls_icztzxjpm1du", + "number": "14.2:9" }, { - "checksum": "c9313afb793a167c53fe2e63b2f4af9b33b23f6209fbf7e0b074de80d123e380", - "id": "fls_a4oiuhz95uiv", - "link": "lexical-elements.html#fls_a4oiuhz95uiv", - "number": "2.2:18" + "checksum": "ce3d4660375205a9a7a05150a0d32054e4b084831f3597693559ff0ac6ecc11a", + "id": "fls_np8aghofjqhm", + "link": "entities-and-resolution.html#fls_np8aghofjqhm", + "number": "14.2:10" }, { - "checksum": "6e723820ff0d3af2b33911208421ec93bfa3d9388535b353d60fe2a74ccc1c8e", - "id": "fls_137x9s6guj6h", - "link": "lexical-elements.html#fls_137x9s6guj6h", - "number": "2.2:19" + "checksum": "58a169c5015aa314a9ea29344a5fd52675e580674d1e2c168b5a1cf37a662248", + "id": "fls_quzvhzpr0124", + "link": "entities-and-resolution.html#fls_quzvhzpr0124", + "number": "14.2:11" }, { - "checksum": "d871501237f68094e868228be1c9ed208944a082241b0cf7a5e34375ee6310ba", - "id": "fls_y0wdb09cpp1w", - "link": "lexical-elements.html#fls_y0wdb09cpp1w", - "number": "2.2:20" + "checksum": "807c234d700c1c58154b62858df9203ffd5320127beface925513a72d43746a9", + "id": "fls_utgjx6l5zwfl", + "link": "entities-and-resolution.html#fls_utgjx6l5zwfl", + "number": "14.2:12" }, { - "checksum": "27d189b098b53140fcdc85a3f039a286f0345dd4aa05f64de22d79e472edc815", - "id": "fls_48b7mepiuupz", - "link": "lexical-elements.html#fls_48b7mepiuupz", - "number": "2.2:21" + "checksum": "651aeacef587c3880996a14a105866d5887bfa31fee311aca7513f5271e0ab59", + "id": "fls_jifg2st5bfd6", + "link": "entities-and-resolution.html#fls_jifg2st5bfd6", + "number": "14.2:13" }, { - "checksum": "cead86db2db6918d8b043df1bb6d9a515f70eedb704cb0533f19bf1a702786d5", - "id": "fls_g9h9bsvrsmk1", - "link": "lexical-elements.html#fls_g9h9bsvrsmk1", - "number": "2.2:22" - }, + "checksum": "d2248a6b249c97703150accda87c18ce1d43c45ffefa58683d93eab6824434a9", + "id": "fls_dm0xr424ine1", + "link": "entities-and-resolution.html#fls_dm0xr424ine1", + "number": "14.2:14" + } + ], + "title": "Visibility" + }, + { + "id": "fls_9i5msiuuyihf", + "informational": false, + "link": "entities-and-resolution.html#paths", + "number": "14.3", + "paragraphs": [ { - "checksum": "9919d5618da261c20fa0dcbe41e0e33cdadc04c63ce6b754c24cc21f2cea2143", - "id": "fls_fxne2xd0zzzo", - "link": "lexical-elements.html#fls_fxne2xd0zzzo", - "number": "2.2:23" + "checksum": "faccaa19e7b0dd16805a53e9927816227b3f4124f6860de31b266715a59bde86", + "id": "fls_klcltwcwrw6i", + "link": "entities-and-resolution.html#fls_klcltwcwrw6i", + "number": "14.3:1" }, { - "checksum": "1db05fadca01dde48b4ba9f4c5941275523165e10756ef03f43cfa640cb2164e", - "id": "fls_il7zv5x3aw0q", - "link": "lexical-elements.html#fls_il7zv5x3aw0q", - "number": "2.2:24" + "checksum": "6f1bc5b5d9ddca435a18802abb0879b3e878d2b97ae4b40649b17237e7aefb96", + "id": "fls_y1z7kougmahd", + "link": "entities-and-resolution.html#fls_y1z7kougmahd", + "number": "14.3:2" }, { - "checksum": "0f736355e9492cd634311cdd73a04550f157f18342e924ed85e3662ff008085f", - "id": "fls_ovcs1qm86ss9", - "link": "lexical-elements.html#fls_ovcs1qm86ss9", - "number": "2.2:25" + "checksum": "a598a12193d0fc3d17a4719a6654b5c3e5de057fb03a8f36364aedb05933e05d", + "id": "fls_8q8nqfpSz7Ly", + "link": "entities-and-resolution.html#fls_8q8nqfpSz7Ly", + "number": "14.3:3" }, { - "checksum": "90ab0fbf40492678aadc1c1460d74be57bd1740b09977b1ac1304747e519aba3", - "id": "fls_wmhlvjm0b0j9", - "link": "lexical-elements.html#fls_wmhlvjm0b0j9", - "number": "2.2:26" + "checksum": "a04567d394d65300bd24d7a6f612dfe96ab09a8e1d9d24007027cc4d257070ef", + "id": "fls_opn5n5t2mo3m", + "link": "entities-and-resolution.html#fls_opn5n5t2mo3m", + "number": "14.3:4" }, { - "checksum": "d68ed94fe4e38b43ce1f83e10bd9d63c0a70f2869947d5e7a3b05323020be8e0", - "id": "fls_gg42klb2gn9v", - "link": "lexical-elements.html#fls_gg42klb2gn9v", - "number": "2.2:27" + "checksum": "5acaf482d5f5abc1db1e3f4e641bcd7d6c807d43c02ce713887758947708de2a", + "id": "fls_774uryecc2sx", + "link": "entities-and-resolution.html#fls_774uryecc2sx", + "number": "14.3:5" }, { - "checksum": "2565056b10c31e82a75554c2f93e8566040b57a534228e4eb172894ee351df1e", - "id": "fls_icahptg5enj4", - "link": "lexical-elements.html#fls_icahptg5enj4", - "number": "2.2:28" + "checksum": "a51e77fdf7a71f0b0eb300f601b6c822e9ab150f5103aa44a1d5ca3df82f5d6e", + "id": "fls_7k88ypcgaoff", + "link": "entities-and-resolution.html#fls_7k88ypcgaoff", + "number": "14.3:6" }, { - "checksum": "72b90114176314f65b298489501e1763317985e1f90ddc834c7ba00bceacc339", - "id": "fls_baawlxoi7yd4", - "link": "lexical-elements.html#fls_baawlxoi7yd4", - "number": "2.2:29" + "checksum": "ac1d3cb4221100b61e82b61f5b77c09f81760c8ad406f8180479d1578f69aad6", + "id": "fls_7kb6ltajgiou", + "link": "entities-and-resolution.html#fls_7kb6ltajgiou", + "number": "14.3:7" }, { - "checksum": "7e8d7cb2d1db1915eefb51e0bb97b3578e2168dc029dbb4c6ce360f326d9f760", - "id": "fls_m7gt3wfbtm81", - "link": "lexical-elements.html#fls_m7gt3wfbtm81", - "number": "2.2:30" + "checksum": "2bbb4365668a8e9949e6ea226fc1c4ca1bb0c3059e7c2972f064baed667ad506", + "id": "fls_n77icl6idazp", + "link": "entities-and-resolution.html#fls_n77icl6idazp", + "number": "14.3:8" }, { - "checksum": "d1faa62ca541cacacd198a34628995933bad01292a21dd568ddcf474e4dbc4b2", - "id": "fls_6ewl7gn3sjm2", - "link": "lexical-elements.html#fls_6ewl7gn3sjm2", - "number": "2.2:31" + "checksum": "46a2d9694956884e4ad1725c0e2505ade9aa39d6718a015d2850e0cfd1d66063", + "id": "fls_YnUsdSM4x9eq", + "link": "entities-and-resolution.html#fls_YnUsdSM4x9eq", + "number": "14.3:9" }, { - "checksum": "721175acceef5a74b065e1b446344787ec7a10cbffbef36ce67f086022ce23ba", - "id": "fls_nb8q6oq8txv3", - "link": "lexical-elements.html#fls_nb8q6oq8txv3", - "number": "2.2:32" + "checksum": "804ea247d1050feb630c47dee50038329c0f8fa4a1c7bb724eb1d19b29eaaaa0", + "id": "fls_iuzvtr3oax1o", + "link": "entities-and-resolution.html#fls_iuzvtr3oax1o", + "number": "14.3:10" }, { - "checksum": "d3f16e94c1af504357ac2df39037b3897e443574d68f41a97f3a3751fb96fd49", - "id": "fls_4nnky9ansr9j", - "link": "lexical-elements.html#fls_4nnky9ansr9j", - "number": "2.2:33" + "checksum": "0810d5dde86b8556c45ed507bfbec7f51260bd25badbaf62080b21443358840c", + "id": "fls_cw006jhlboa", + "link": "entities-and-resolution.html#fls_cw006jhlboa", + "number": "14.3:11" }, { - "checksum": "f33b2927d4c93c042777b5efd14b10f479d4567213b07eeb39af2f226b208a84", - "id": "fls_h1gvudehmnn9", - "link": "lexical-elements.html#fls_h1gvudehmnn9", - "number": "2.2:34" + "checksum": "0b5983245cd5346dfd1276ced7307c04d4d81717e265035c72319454fbdfac52", + "id": "fls_kv5bpq8rf1j9", + "link": "entities-and-resolution.html#fls_kv5bpq8rf1j9", + "number": "14.3:12" }, { - "checksum": "3b19d33ec536baf30efd81e160cddae06ecb9f87449e9a2f0eb4b7b41f45a9e9", - "id": "fls_6yj1c3lh691s", - "link": "lexical-elements.html#fls_6yj1c3lh691s", - "number": "2.2:35" + "checksum": "6b80947a0c4468e18fade3520519a9b79a5d2d8115001c1eb8bfc0a4a029399f", + "id": "fls_chtj3hcfe3ap", + "link": "entities-and-resolution.html#fls_chtj3hcfe3ap", + "number": "14.3:13" }, { - "checksum": "1849bba6049bdbc82b21b091dec81c60ec2b201d17fb46c10b00dd7008a75383", - "id": "fls_2d3oo9nou9vv", - "link": "lexical-elements.html#fls_2d3oo9nou9vv", - "number": "2.2:36" + "checksum": "f525bc5d1cc46a6aa7cf36eff8be2a47a90c9803c8a06fe6f8dc345c61c9b080", + "id": "fls_wm61yeclairz", + "link": "entities-and-resolution.html#fls_wm61yeclairz", + "number": "14.3:14" }, { - "checksum": "1e646156faf2fee7e2b56b8ba713276080504a642c80857cae228114284669d0", - "id": "fls_st2vhcy14ud9", - "link": "lexical-elements.html#fls_st2vhcy14ud9", - "number": "2.2:37" + "checksum": "34501af716aa34b93e15d351af04e08ef899126df7109cba1f2037f7ebaf9b3e", + "id": "fls_nRgjCLYZL3iX", + "link": "entities-and-resolution.html#fls_nRgjCLYZL3iX", + "number": "14.3:15" }, { - "checksum": "296ef99a61a7ee9d96f8b232724e9426eaf744eb4fee6e5a8d5f382bab039071", - "id": "fls_9gdyw71dl25", - "link": "lexical-elements.html#fls_9gdyw71dl25", - "number": "2.2:38" + "checksum": "612a8b592294352e6c67e0fd45f33a2baa3584b57d904a270e78d4339281dd9e", + "id": "fls_tvvycup09b51", + "link": "entities-and-resolution.html#fls_tvvycup09b51", + "number": "14.3:16" }, { - "checksum": "a6eb8fc79796023bbf80ae6ebebbe9aa8ca02eaea9e91dd0101a92d6502c436a", - "id": "fls_sp8ufz28l9w3", - "link": "lexical-elements.html#fls_sp8ufz28l9w3", - "number": "2.2:39" + "checksum": "6ea02148b0bd9c494f779eeaac0fa983285fd00082c3df4add60a7293f1b4d54", + "id": "fls_h2zikgmazoxx", + "link": "entities-and-resolution.html#fls_h2zikgmazoxx", + "number": "14.3:17" }, { - "checksum": "1206752913d765884541bbdca0403f9d865f619f0a66850120cd7d31fdf9dba4", - "id": "fls_7kdr8biodxvz", - "link": "lexical-elements.html#fls_7kdr8biodxvz", - "number": "2.2:40" + "checksum": "2356207dea29ed2d5aa6e5a78e4994316431b1fe5a8a50b76c7d4b8cdeddede4", + "id": "fls_nj7s6xmzx55f", + "link": "entities-and-resolution.html#fls_nj7s6xmzx55f", + "number": "14.3:18" }, { - "checksum": "313032bcb9b35d0790c79d41a0ca4b969af9522ce30869193930e68bc013f5ab", - "id": "fls_pf92l9bkte2u", - "link": "lexical-elements.html#fls_pf92l9bkte2u", - "number": "2.2:41" + "checksum": "8ed372f7349a77552ebc1390e264ac1254ab102736d6e6455ffb3143c4602dbe", + "id": "fls_e65q3iz50j6a", + "link": "entities-and-resolution.html#fls_e65q3iz50j6a", + "number": "14.3:19" }, { - "checksum": "33ac534f7ecfa88d267e49b475398c2c99a9dc708d9947f300849180c325d542", - "id": "fls_ui40thspgyav", - "link": "lexical-elements.html#fls_ui40thspgyav", - "number": "2.2:42" + "checksum": "038e052fffa92303139c1be17bb4f7521e552611f2647cf22270e8358b9553e4", + "id": "fls_Ai1jN5a8h3Dz", + "link": "entities-and-resolution.html#fls_Ai1jN5a8h3Dz", + "number": "14.3:20" }, { - "checksum": "7d1d9f51a7a61689fd5c36e8bb1692e0155f452ed6ee445950644a177628e200", - "id": "fls_h33qzachmimc", - "link": "lexical-elements.html#fls_h33qzachmimc", - "number": "2.2:43" + "checksum": "af34b5b94f5c4f453bbbfd85b46861f49c8a88f06df9ae12aa2283441c4c8138", + "id": "fls_ybv0tdu7dnj5", + "link": "entities-and-resolution.html#fls_ybv0tdu7dnj5", + "number": "14.3:21" }, { - "checksum": "2f5232d2dd945c47f4332b95ce35d9d402898e71f4f7c411c663e301e2429cfa", - "id": "fls_13ud1clgdnyv", - "link": "lexical-elements.html#fls_13ud1clgdnyv", - "number": "2.2:44" + "checksum": "87aea9dbee8a48f85557797bbc2d29f8e6fdb0dac7615ebc8e45ed28500441bc", + "id": "fls_qkYF2J7GVah8", + "link": "entities-and-resolution.html#fls_qkYF2J7GVah8", + "number": "14.3:22" }, { - "checksum": "70b0b1197e263b4916e16125090cd6e9a9899d0554b9d920f0a3ea7fa28419bb", - "id": "fls_7fosi8l2ktz2", - "link": "lexical-elements.html#fls_7fosi8l2ktz2", - "number": "2.2:45" + "checksum": "94b5df1f402d9ada3134d26046016aef437a8e86533892e27c5a99f4edf52a58", + "id": "fls_QjNQbQhUcRTT", + "link": "entities-and-resolution.html#fls_QjNQbQhUcRTT", + "number": "14.3:23" }, { - "checksum": "5683fc048d59af88dbe3eab83e4bd8d23b94856307f288a6f6377f4037f4595d", - "id": "fls_9qitp6r75ia6", - "link": "lexical-elements.html#fls_9qitp6r75ia6", - "number": "2.2:46" + "checksum": "8902e7d1f80cd0a6e4e560838c03d8a2949169ae5cf6ffc22f241884a23bdc84", + "id": "fls_7sm3206va03c", + "link": "entities-and-resolution.html#fls_7sm3206va03c", + "number": "14.3:24" }, { - "checksum": "910783ea8b3d0346e1c6f25a4103c369012b37ccd28169fb7513ec357673c4d2", - "id": "fls_g0umao9roi2l", - "link": "lexical-elements.html#fls_g0umao9roi2l", - "number": "2.2:47" + "checksum": "272e37c850f521889e6d9d7e6813f0a87f44a727cb59102b140179debf821a56", + "id": "fls_huynsyx13gsz", + "link": "entities-and-resolution.html#fls_huynsyx13gsz", + "number": "14.3:25" }, { - "checksum": "425e9c50e0a6e99ab3bd2a32d34417baaa88ef286249a7ea4c40aff88803a88d", - "id": "fls_lamrpdpko48", - "link": "lexical-elements.html#fls_lamrpdpko48", - "number": "2.2:48" + "checksum": "07fbe740bf19563ae613bfea97ef80ae7a163aeeb40db28860b3e3271f30dec9", + "id": "fls_RZvIsApi4WQm", + "link": "entities-and-resolution.html#fls_RZvIsApi4WQm", + "number": "14.3:26" }, { - "checksum": "44095be1f456e5e12848444c3cccdaf9cb0595d285c3b9af792f68c3c6d12e7a", - "id": "fls_s4lte9onbmqb", - "link": "lexical-elements.html#fls_s4lte9onbmqb", - "number": "2.2:49" + "checksum": "3f386dfca473cbbe5216f7579aabaa0cda4f6d9bfe53ab258a43c18168692f75", + "id": "fls_f1ciozzetj5a", + "link": "entities-and-resolution.html#fls_f1ciozzetj5a", + "number": "14.3:27" }, { - "checksum": "ef74969af8ed15f1495ae8e706dfd8210ff6adfe2b49651ba869290be7687de0", - "id": "fls_ywc297y8s0dt", - "link": "lexical-elements.html#fls_ywc297y8s0dt", - "number": "2.2:50" + "checksum": "c3776124fbef0e532209f8f067a47e7af3a3bf7032ceeab5cfb896c7651d07dd", + "id": "fls_cy7vza3flqi9", + "link": "entities-and-resolution.html#fls_cy7vza3flqi9", + "number": "14.3:28" }, { - "checksum": "5646edff72acfcef1812fe04617d99f20bd1792169e20de2737a09248d9dd74f", - "id": "fls_ijb0fws4gshu", - "link": "lexical-elements.html#fls_ijb0fws4gshu", - "number": "2.2:51" + "checksum": "acc7d0ee1b047d062435a8ce89e6a518f16f42bf9f2c5dfd6dfc5ff8b5ced03e", + "id": "fls_cul31g1kkz5c", + "link": "entities-and-resolution.html#fls_cul31g1kkz5c", + "number": "14.3:29" }, { - "checksum": "20e185246054cf07309c6a13c1abd811e9488b5a85b5b076da651edbcdb233bd", - "id": "fls_c25ur4xwbpk0", - "link": "lexical-elements.html#fls_c25ur4xwbpk0", - "number": "2.2:52" + "checksum": "3ad2063380470f0ebbf19e527f4669a78071b833fe64f5e0f1521729680bc477", + "id": "fls_no853u27p4f3", + "link": "entities-and-resolution.html#fls_no853u27p4f3", + "number": "14.3:30" }, { - "checksum": "c9171c2be2e92bbdf83f7a6944a2ba47aed3227600b99f5fb6a50b1ee7cfffdd", - "id": "fls_9dd9479zzq30", - "link": "lexical-elements.html#fls_9dd9479zzq30", - "number": "2.2:53" + "checksum": "177724d6510ea45d50120fed6c28c92b69dca2fc3124541b67c96e933917bc0d", + "id": "fls_28c21rzc6rsp", + "link": "entities-and-resolution.html#fls_28c21rzc6rsp", + "number": "14.3:31" }, { - "checksum": "6392d2a1b3e699903a3891e189ec2627cd4cdadb40b1901227056869728ed558", - "id": "fls_kwsu9d3ppv3f", - "link": "lexical-elements.html#fls_kwsu9d3ppv3f", - "number": "2.2:54" + "checksum": "a16e3a0e1a36090a7b32f972287f4cdedbddc5e240a9b3c08929277f4696a99a", + "id": "fls_4s2n95h4rd1q", + "link": "entities-and-resolution.html#fls_4s2n95h4rd1q", + "number": "14.3:32" }, { - "checksum": "9cd08193b2cec929c6aa172a5dc0c456e7f5faf0dddc91f4a5010af10ff3cffb", - "id": "fls_oh62j9unw4mg", - "link": "lexical-elements.html#fls_oh62j9unw4mg", - "number": "2.2:55" - }, + "checksum": "3d7face163b14a1f83f142fc864a2664a8c9ff279c76d924be5a0d78c9df1a62", + "id": "fls_ojdntg5i79pb", + "link": "entities-and-resolution.html#fls_ojdntg5i79pb", + "number": "14.3:33" + } + ], + "title": "Paths" + }, + { + "id": "fls_izl8iuhoz9e0", + "informational": false, + "link": "entities-and-resolution.html#scopes", + "number": "14.4", + "paragraphs": [ { - "checksum": "6031f088738902177f0a06367e797bbbed6f87b4be23c0317f10da0e7ca27224", - "id": "fls_g0tltt8qmbum", - "link": "lexical-elements.html#fls_g0tltt8qmbum", - "number": "2.2:56" - }, + "checksum": "43517ff3b8cb455dc43e97cf0f608edaefcb7c7be9cbed87555264c018d5f507", + "id": "fls_5x5xykocwyiy", + "link": "entities-and-resolution.html#fls_5x5xykocwyiy", + "number": "14.4:1" + } + ], + "title": "Scopes" + }, + { + "id": "fls_6ozthochxz1i", + "informational": false, + "link": "entities-and-resolution.html#binding-scopes", + "number": "14.4.1", + "paragraphs": [ { - "checksum": "c66c5da22713843633a8a9e39b2b20bc790ff265b578a9814659071b19adb6d4", - "id": "fls_ounkw8b8tk4f", - "link": "lexical-elements.html#fls_ounkw8b8tk4f", - "number": "2.2:57" + "checksum": "1c091220f546537aa0376292e1056112fdb4f6accdb5704f2b02a353e1dc7f57", + "id": "fls_ncg9etb3x7k0", + "link": "entities-and-resolution.html#fls_ncg9etb3x7k0", + "number": "14.4.1:1" }, { - "checksum": "146ed363f52681258de59c4c591f3e964f91cf5b1f49ab1ba0410c4b99fb63a6", - "id": "fls_8ywv8gftsfr1", - "link": "lexical-elements.html#fls_8ywv8gftsfr1", - "number": "2.2:58" + "checksum": "28f8cbea9a3c14d13f591f4f492b25d66fb3fef3471e02a0bce1eb50f219a2fa", + "id": "fls_u52mx4xw8zod", + "link": "entities-and-resolution.html#fls_u52mx4xw8zod", + "number": "14.4.1:2" }, { - "checksum": "db921aee3f65b1447572f308b1536051374edd08a46c64e1900f1f18f7d003e1", - "id": "fls_hsn6zc29ifyx", - "link": "lexical-elements.html#fls_hsn6zc29ifyx", - "number": "2.2:59" + "checksum": "38c14cea741975dfcf0745579b4485aa6e7fb1819efc47af41906460216ba171", + "id": "fls_t9mk8kasobea", + "link": "entities-and-resolution.html#fls_t9mk8kasobea", + "number": "14.4.1:3" }, { - "checksum": "5b84c1b6b34b6a35f73d808399557de0dda5643c5dd061eab6fe2f70f841047a", - "id": "fls_o3amqe3ca82d", - "link": "lexical-elements.html#fls_o3amqe3ca82d", - "number": "2.2:60" + "checksum": "c828a2288a376f29a23988cc6c36a6e201e5de5b00188c8dae26d122a334a4e2", + "id": "fls_h9cvs854ae34", + "link": "entities-and-resolution.html#fls_h9cvs854ae34", + "number": "14.4.1:4" }, { - "checksum": "503fcedbff8acca4704b08fc4a18dd34eacb130b874da3393499d1b4b2767f79", - "id": "fls_lkevfpj7sqd3", - "link": "lexical-elements.html#fls_lkevfpj7sqd3", - "number": "2.2:61" + "checksum": "9738803f2e1cc4b49daa374b470dec9c822944abdddc2d5553cc3a7d2ff81962", + "id": "fls_vl1qk0odouyb", + "link": "entities-and-resolution.html#fls_vl1qk0odouyb", + "number": "14.4.1:5" }, { - "checksum": "d11919954e7ca0bf885a5dcecbf1173c4e7d47a9a0c9680cdfe30689bdc09607", - "id": "fls_ff05ge2189z", - "link": "lexical-elements.html#fls_ff05ge2189z", - "number": "2.2:62" + "checksum": "704d759b8e808aade84bf9d91c6547afd260d382951c9e63b34d612e46adb44a", + "id": "fls_74nk389rk075", + "link": "entities-and-resolution.html#fls_74nk389rk075", + "number": "14.4.1:6" }, { - "checksum": "f5fea3393e5672ccf2f182c92ada0ce8e51b908b4c56d6e5dff341b04aadbdda", - "id": "fls_nplkudde6oxf", - "link": "lexical-elements.html#fls_nplkudde6oxf", - "number": "2.2:63" + "checksum": "01919a0675796333a57c77739f2e33e59788ca34fb915657ee4300af13e689ff", + "id": "fls_xbnki64un70v", + "link": "entities-and-resolution.html#fls_xbnki64un70v", + "number": "14.4.1:7" }, { - "checksum": "0f4d81ea4a932a0b0c4ca39ea2ab7cf36fe9262611f07950869b94f715652cb3", - "id": "fls_qwnrklmbz0b", - "link": "lexical-elements.html#fls_qwnrklmbz0b", - "number": "2.2:64" + "checksum": "e8ab2db877edb9f626751999aac1bbfe95531e7b09745bf82c87979427adffa5", + "id": "fls_eBacCVlDaKYK", + "link": "entities-and-resolution.html#fls_eBacCVlDaKYK", + "number": "14.4.1:8" } ], - "title": "Lexical Elements, Separators, and Punctuation" + "title": "Binding Scopes" }, { - "id": "fls_21vnag69kbwe", + "id": "fls_ftphlagzd2te", "informational": false, - "link": "lexical-elements.html#identifiers", - "number": "2.3", + "link": "entities-and-resolution.html#generic-parameter-scope", + "number": "14.4.2", "paragraphs": [ { - "checksum": "3987fd5d747ef2d4785094f84e754d8cae140017fa5f9c28362feeef107d4dac", - "id": "fls_ls7ymvgd5kfa", - "link": "lexical-elements.html#fls_ls7ymvgd5kfa", - "number": "2.3:1" + "checksum": "d584c25b23d784679f49bee07542de8692b945ee1f537fbbfe6de0748ac4fb71", + "id": "fls_amoh8r4gghyj", + "link": "entities-and-resolution.html#fls_amoh8r4gghyj", + "number": "14.4.2:1" }, { - "checksum": "711b7898931cab56cb1058ef3932abe5b767cb86bbe9998c0d33d0dec68b7fc5", - "id": "fls_aqj9aguczgqs", - "link": "lexical-elements.html#fls_aqj9aguczgqs", - "number": "2.3:2" + "checksum": "8279dc6a8b235106f42ec6b4f7ac14f3cbc73a5387e8050967651ecbc6aba0b0", + "id": "fls_6o38qhbna46z", + "link": "entities-and-resolution.html#fls_6o38qhbna46z", + "number": "14.4.2:2" }, { - "checksum": "17d71ec4c1973c054222c7033a9c64d1fd8dca1fdf82d323472445e8c7511c81", - "id": "fls_xsdmun5uqy4c", - "link": "lexical-elements.html#fls_xsdmun5uqy4c", - "number": "2.3:3" + "checksum": "1bf64d3433e9732df7b69f03bc39b74cb23d3c97ec46beaacab32fd3c95b7311", + "id": "fls_jqevvpndxzdz", + "link": "entities-and-resolution.html#fls_jqevvpndxzdz", + "number": "14.4.2:3" }, { - "checksum": "3a5077067bbd7d98abb0e8ef62f3c68e25a82638fe896907b96848701a6b9ed0", - "id": "fls_ktnf6zkrdy45", - "link": "lexical-elements.html#fls_ktnf6zkrdy45", - "number": "2.3:4" + "checksum": "b58069d3632126cd1d327344ae6ddc3ea80724b6edb47aae53f655412afbebe2", + "id": "fls_t9ztg017itkp", + "link": "entities-and-resolution.html#fls_t9ztg017itkp", + "number": "14.4.2:4" }, { - "checksum": "7c2cc455bbdbc244dae982edf85b1ec3c9109a0bcba535a244af7823fff6829a", - "id": "fls_jpecw46eh061", - "link": "lexical-elements.html#fls_jpecw46eh061", - "number": "2.3:5" - }, - { - "checksum": "198814e4e6f61528781c58b11e16a0038ae24778bba6d20a1a9a8dd79185cb3e", - "id": "fls_lwcflgezgs5z", - "link": "lexical-elements.html#fls_lwcflgezgs5z", - "number": "2.3:6" - }, - { - "checksum": "a2af446e5664b56514cde36964346072a3c31267e8e81fd5417885aa9f091b32", - "id": "fls_uts0hywaw1rq", - "link": "lexical-elements.html#fls_uts0hywaw1rq", - "number": "2.3:7" - }, - { - "checksum": "3d09604f290b1e849a5430295209e2f6556f6c9f2024c7160c92b0ac7d91a602", - "id": "fls_lju1avcn0pfd", - "link": "lexical-elements.html#fls_lju1avcn0pfd", - "number": "2.3:8" - }, - { - "checksum": "4e8345c2b229363a30e4c701ca308a343dc98f3fc1025d6321b4a101aa16e444", - "id": "fls_cs6cbw625np1", - "link": "lexical-elements.html#fls_cs6cbw625np1", - "number": "2.3:9" - }, - { - "checksum": "54da339a3e1b9269cf812fda1ad283d6cf0918c3a3807f8c61fab188ef3d2658", - "id": "fls_irwcldiotei2", - "link": "lexical-elements.html#fls_irwcldiotei2", - "number": "2.3:10" - }, - { - "checksum": "9ae1eb42d553b7e7443d02b5b2d1e3a6d78bb9bcb4eedc72060780aa692d65bf", - "id": "fls_g72rxs2z5960", - "link": "lexical-elements.html#fls_g72rxs2z5960", - "number": "2.3:11" + "checksum": "ba9481f2a0dab8367b56e1986f679ff8388f4d6fdbd58144bd8fec7d02d50597", + "id": "fls_pmo939jw9m1m", + "link": "entities-and-resolution.html#fls_pmo939jw9m1m", + "number": "14.4.2:5" }, { - "checksum": "c7c123027fb95da5b47bbb45a0d262f1ccc8a67be78b8c5e6995d4eabf0dcb75", - "id": "fls_w473jevurlt1", - "link": "lexical-elements.html#fls_w473jevurlt1", - "number": "2.3:12" + "checksum": "5be283d945e26887b35476d71c9983067056e07394062f8e074c437f23e48e7c", + "id": "fls_67dtv1z3arbl", + "link": "entities-and-resolution.html#fls_67dtv1z3arbl", + "number": "14.4.2:6" }, { - "checksum": "14cfd5ffca7331e76e77c2fc7141bce89335774b3850f3cc90a06066c4660451", - "id": "fls_mt1u4m3simhc", - "link": "lexical-elements.html#fls_mt1u4m3simhc", - "number": "2.3:13" + "checksum": "b51781ef00cf909b9c6f954d1f3ad0fcde40305f987b06a26fde3bc2fce0cebd", + "id": "fls_y8j4isk9libl", + "link": "entities-and-resolution.html#fls_y8j4isk9libl", + "number": "14.4.2:7" }, { - "checksum": "b8d023cf7da2ee836679c97c7dccb223c0cfe0d172d661327da271ea51059628", - "id": "fls_e2v58o233lvd", - "link": "lexical-elements.html#fls_e2v58o233lvd", - "number": "2.3:14" + "checksum": "d80167a0dcd4ced3ddd314d967d1072c823cab9fb8cd19ea7168bce3bda55d70", + "id": "fls_ow5ih7q3xxfx", + "link": "entities-and-resolution.html#fls_ow5ih7q3xxfx", + "number": "14.4.2:8" }, { - "checksum": "5d492f34dedfe4c347ae8fc5cc979751886b374d0f597aec7270a8850f233696", - "id": "fls_op0lp1i065di", - "link": "lexical-elements.html#fls_op0lp1i065di", - "number": "2.3:15" + "checksum": "8ba2db48a9fbc5cd1345c45bbc4ffcce13b8b4fd94cb3d312f53128b4459aaf9", + "id": "fls_h9rpwxpz72v0", + "link": "entities-and-resolution.html#fls_h9rpwxpz72v0", + "number": "14.4.2:9" }, { - "checksum": "d8a9ddf03bd6e5fe69b23403d83fb785383eaa9e3e1b12e4d7a2d26f930c5843", - "id": "fls_vde7gev5rz4q", - "link": "lexical-elements.html#fls_vde7gev5rz4q", - "number": "2.3:16" + "checksum": "6da6b05924e3ca7b78a52d8b6bebba58a20200ccc2beec4a64d3e81ad77da76f", + "id": "fls_3qm3vh97bvpb", + "link": "entities-and-resolution.html#fls_3qm3vh97bvpb", + "number": "14.4.2:10" }, { - "checksum": "cb6600fc3669e86c2955c97b83b7f6396c4187ddd330aa46e989b9cd1c9f0ace", - "id": "fls_j9yh8j8jgdeu", - "link": "lexical-elements.html#fls_j9yh8j8jgdeu", - "number": "2.3:17" + "checksum": "e918fbab918fabdf9fdbc58cfc78079484a925ab64ba7147cf502d5a1ad77c81", + "id": "fls_xuxbpv5b2ym9", + "link": "entities-and-resolution.html#fls_xuxbpv5b2ym9", + "number": "14.4.2:11" }, { - "checksum": "8123eef62794e72515c380c13f09fe4a1a1b30ee0516beacac7e93a43c943a7b", - "id": "fls_jejt5z8m1yew", - "link": "lexical-elements.html#fls_jejt5z8m1yew", - "number": "2.3:18" + "checksum": "689d6488c7516efa05b316fc2b100ccd54fc41bf3bac4b72f0868c6a4f251d1d", + "id": "fls_95z5mytvfjia", + "link": "entities-and-resolution.html#fls_95z5mytvfjia", + "number": "14.4.2:12" } ], - "title": "Identifiers" + "title": "Generic Parameter Scope" }, { - "id": "fls_nrkd5wpi64oo", + "id": "fls_m0z7omni9hp0", "informational": false, - "link": "lexical-elements.html#literals", - "number": "2.4", + "link": "entities-and-resolution.html#item-scope", + "number": "14.4.3", "paragraphs": [ { - "checksum": "4303183568664aedd54d9840d1fb3ca733ea37f08af4e8a629102afce83dc9bc", - "id": "fls_s76un78zyd0j", - "link": "lexical-elements.html#fls_s76un78zyd0j", - "number": "2.4:1" + "checksum": "fb831c1ad14c0df880fe0e2965047fd726fb0ed1e305c93656d65e870501e7cd", + "id": "fls_p5o243hhe1y3", + "link": "entities-and-resolution.html#fls_p5o243hhe1y3", + "number": "14.4.3:1" + }, + { + "checksum": "25bc4c9ccd15441cb75deda5c2ca31e035e40a8231453a5c22e52e2646c44186", + "id": "fls_huvo0mp2i6fb", + "link": "entities-and-resolution.html#fls_huvo0mp2i6fb", + "number": "14.4.3:2" + }, + { + "checksum": "21fe2e6b23a8c76e0b909cb3bb66d87811b819ce8cae90179b3d73eceff47c01", + "id": "fls_x8r0oppuc1t6", + "link": "entities-and-resolution.html#fls_x8r0oppuc1t6", + "number": "14.4.3:3" } ], - "title": "Literals" + "title": "Item Scope" }, { - "id": "fls_2ifjqwnw03ms", + "id": "fls_769b4p8v3cwu", "informational": false, - "link": "lexical-elements.html#byte-literals", - "number": "2.4.1", + "link": "entities-and-resolution.html#label-scope", + "number": "14.4.4", "paragraphs": [ { - "checksum": "c40401452c29d8db5b9b193597726e18789ccd7a194187365e54cf4fb4b57067", - "id": "fls_3hpzf12h60u4", - "link": "lexical-elements.html#fls_3hpzf12h60u4", - "number": "2.4.1:1" + "checksum": "521fb43b24f85cbd6c81cae0c33b399291a86e51201554028deeb6b63c47e26a", + "id": "fls_96kczd4zhpco", + "link": "entities-and-resolution.html#fls_96kczd4zhpco", + "number": "14.4.4:1" }, { - "checksum": "59045ce2e31e65cd7b1195587644af1891984b02d9dc612c1f7a61850d140332", - "id": "fls_q0qwr83frszx", - "link": "lexical-elements.html#fls_q0qwr83frszx", - "number": "2.4.1:2" + "checksum": "f9d012717e48391d6a169f0d2134dd95a7d39ab75ea97dbf1dff6d58431a9ba5", + "id": "fls_8sevg1sa82h4", + "link": "entities-and-resolution.html#fls_8sevg1sa82h4", + "number": "14.4.4:2" }, { - "checksum": "34306366aa23941d86cf2fc0d347e2b007703424de36480b5357c7c4583e3671", - "id": "fls_fggytrv5jvw0", - "link": "lexical-elements.html#fls_fggytrv5jvw0", - "number": "2.4.1:3" + "checksum": "688be0bee74edd58c2e2e383d5686e45abd8a325780d2d340605d0b7b3af96bc", + "id": "fls_ep5smja1rxdv", + "link": "entities-and-resolution.html#fls_ep5smja1rxdv", + "number": "14.4.4:3" } ], - "title": "Byte Literals" + "title": "Label Scope" }, { - "id": "fls_fqaffyrjob7v", + "id": "fls_kgbi26212eof", "informational": false, - "link": "lexical-elements.html#byte-string-literals", - "number": "2.4.2", + "link": "entities-and-resolution.html#self-scope", + "number": "14.4.5", "paragraphs": [ { - "checksum": "28f4df70edac5734cc64ff6089f04d400be4ce9f2af1664070e1b0326cb24d26", - "id": "fls_t63zfv5JdUhj", - "link": "lexical-elements.html#fls_t63zfv5JdUhj", - "number": "2.4.2:1" + "checksum": "46ec90b1b86c7151216c55420994d294c6454cf9a68db0d8835a9fb9b50e5984", + "id": "fls_kgt81m4f72ne", + "link": "entities-and-resolution.html#fls_kgt81m4f72ne", + "number": "14.4.5:1" }, { - "checksum": "b97d7cd6b67ccbb2930123cc93a7e4857e32dbbf7a22764e35120ca15f7dca95", - "id": "fls_Xd6LnfzMb7t7", - "link": "lexical-elements.html#fls_Xd6LnfzMb7t7", - "number": "2.4.2:2" + "checksum": "9ea7a212c6751d354e97c5eaa4a920477f382ff731fbf5eaf9539e771f658286", + "id": "fls_kxdwq4b136tl", + "link": "entities-and-resolution.html#fls_kxdwq4b136tl", + "number": "14.4.5:2" + }, + { + "checksum": "d453f4e8c7603cc4a3af26b98e29a3619d3e4151e2257de52a6d49f61e7ac8d1", + "id": "fls_nf4g82gi12ij", + "link": "entities-and-resolution.html#fls_nf4g82gi12ij", + "number": "14.4.5:3" + }, + { + "checksum": "f92935a62e26ce498f4d124d356e3e78782eef0ef6a3740c3cf24f7b09250efe", + "id": "fls_dy4gyepebe7b", + "link": "entities-and-resolution.html#fls_dy4gyepebe7b", + "number": "14.4.5:4" + }, + { + "checksum": "3554e24b5a9b2499a683328f0576d51a86bd26bf117490738edb7ae2e7904a1c", + "id": "fls_cha4ddwfqwvj", + "link": "entities-and-resolution.html#fls_cha4ddwfqwvj", + "number": "14.4.5:5" + }, + { + "checksum": "2e5e179e6007b149b3d35850933d604403fbaf8f3cce6fdbd380b2f719d27cd8", + "id": "fls_ql4i021ut2n8", + "link": "entities-and-resolution.html#fls_ql4i021ut2n8", + "number": "14.4.5:6" + }, + { + "checksum": "6c12e7861360d4792869765a4ef4afc10ef9b835148fd7c3b5b6a78c61e470db", + "id": "fls_mj9vlxnf44oi", + "link": "entities-and-resolution.html#fls_mj9vlxnf44oi", + "number": "14.4.5:7" } ], - "title": "Byte String Literals" + "title": "Self Scope" }, { - "id": "fls_msbaxfc09vkk", + "id": "fls_octf6sf7yso", "informational": false, - "link": "lexical-elements.html#simple-byte-string-literals", - "number": "2.4.2.1", + "link": "entities-and-resolution.html#textual-macro-scope", + "number": "14.4.6", "paragraphs": [ { - "checksum": "62ac30b5cfa63f4632a34f11e4698ea67c7ae55a34b3d28b393ca9d5fa086c06", - "id": "fls_3dcqhuosqb84", - "link": "lexical-elements.html#fls_3dcqhuosqb84", - "number": "2.4.2.1:1" + "checksum": "9d8d566bf0cc9c442a6f8caa9750acaad5fd97b2051978a07e83ecde9be6c8ed", + "id": "fls_xkh8cqubhxad", + "link": "entities-and-resolution.html#fls_xkh8cqubhxad", + "number": "14.4.6:1" }, { - "checksum": "5e13841395ee1fa0c45128b43036136b6cb4e4916df10822bac39fdbd3793ff4", - "id": "fls_moe3zfx39ox2", - "link": "lexical-elements.html#fls_moe3zfx39ox2", - "number": "2.4.2.1:2" + "checksum": "20ccfad5b0eb356d3674f0df4b46713ca8de9f5e25b83f9ec21fa2ff02dc92cc", + "id": "fls_iec3otx863yp", + "link": "entities-and-resolution.html#fls_iec3otx863yp", + "number": "14.4.6:2" }, { - "checksum": "8f22427e1ba53391df74a01e8e153344365f804365f3a68124e8fb15dc383bb6", - "id": "fls_vffxb6arj9jf", - "link": "lexical-elements.html#fls_vffxb6arj9jf", - "number": "2.4.2.1:3" + "checksum": "fb291d2a2d9aa2c05d01d7c0697cf5633d229a13e733f5c63cf3880d0e198fe7", + "id": "fls_cbfuh9y87y6i", + "link": "entities-and-resolution.html#fls_cbfuh9y87y6i", + "number": "14.4.6:3" } ], - "title": "Simple Byte String Literals" + "title": "Textual Macro Scope" }, { - "id": "fls_jps9102q0qfi", + "id": "fls_lnpyb285qdiy", "informational": false, - "link": "lexical-elements.html#raw-byte-string-literals", - "number": "2.4.2.2", + "link": "entities-and-resolution.html#scope-hierarchy", + "number": "14.4.7", "paragraphs": [ { - "checksum": "990fc45d4abab0b67d0ef88df0be04d33d65c7522287bb90176a8a85a131faf8", - "id": "fls_yyw7nv651580", - "link": "lexical-elements.html#fls_yyw7nv651580", - "number": "2.4.2.2:1" + "checksum": "f551c6d52274528f4d39e9f7ebb33850b7a9886014adda99bd7dfdea6a937703", + "id": "fls_4o7vfo6v39l7", + "link": "entities-and-resolution.html#fls_4o7vfo6v39l7", + "number": "14.4.7:1" }, { - "checksum": "f646d235451afad8b859ba4be5f8fb7a8bfd7a81daf561627ab2473cdba57128", - "id": "fls_5ybq0euwya42", - "link": "lexical-elements.html#fls_5ybq0euwya42", - "number": "2.4.2.2:2" - } - ], - "title": "Raw Byte String Literals" - }, - { - "id": "fls_u1ghcy16emve", - "informational": false, - "link": "lexical-elements.html#c-string-literals", - "number": "2.4.3", - "paragraphs": [ - { - "checksum": "7e16d894447f8ef686c945d36f1faf6f80c8b136f4741bca6b0179cef2491e4f", - "id": "fls_VKCW830CzhhN", - "link": "lexical-elements.html#fls_VKCW830CzhhN", - "number": "2.4.3:1" + "checksum": "4d1555490789b23e0b29f3539cc6b17f32c58c25a752e2dff0ab22d6d85ba927", + "id": "fls_ns4eog3od4kw", + "link": "entities-and-resolution.html#fls_ns4eog3od4kw", + "number": "14.4.7:2" }, { - "checksum": "6bb6d55a686c54d2952946d0c2402066f7f569ce6a973b70b764b9dd3b68ddc5", - "id": "fls_XJprzaEn82Xs", - "link": "lexical-elements.html#fls_XJprzaEn82Xs", - "number": "2.4.3:2" - } - ], - "title": "C String Literals" - }, - { - "id": "fls_p090c5otnelw", - "informational": false, - "link": "lexical-elements.html#simple-c-string-literals", - "number": "2.4.3.1", - "paragraphs": [ - { - "checksum": "4da63974cf01cd18e7aa9a85771f1aa5c6cd0c1ac07ff0d7c0161a5acd50e54b", - "id": "fls_fnwQHo7twAom", - "link": "lexical-elements.html#fls_fnwQHo7twAom", - "number": "2.4.3.1:1" + "checksum": "0c3ab46d2d4bbdfb7f8aa15e861edba999dcd3ab2c8247042b749fdfea5d82b2", + "id": "fls_kqmykyzdb1k6", + "link": "entities-and-resolution.html#fls_kqmykyzdb1k6", + "number": "14.4.7:3" }, { - "checksum": "250f1bc827e567b0bf3ebf794f811002cb851aad7c0cb2a5eebc0af9a3447f9f", - "id": "fls_nPI7j0siGP8G", - "link": "lexical-elements.html#fls_nPI7j0siGP8G", - "number": "2.4.3.1:2" + "checksum": "a879143703b2ebbf1c26fff249edc2ee837f8d4c7b131b30fc5b2da7cb149c8b", + "id": "fls_g86d5v14sxxv", + "link": "entities-and-resolution.html#fls_g86d5v14sxxv", + "number": "14.4.7:4" }, { - "checksum": "d44d85ac2c9004357bdef5501479b3401a2c2f41eef632c27dfe32aa01bc76ac", - "id": "fls_Ae7LM4Wg0NA7", - "link": "lexical-elements.html#fls_Ae7LM4Wg0NA7", - "number": "2.4.3.1:3" - } - ], - "title": "Simple C String Literals" - }, - { - "id": "fls_g4ldypf3rl6i", - "informational": false, - "link": "lexical-elements.html#raw-c-string-literals", - "number": "2.4.3.2", - "paragraphs": [ - { - "checksum": "97abe5a9182e0c5bba193506ce6ed8d0d709c421862ea27f46cbfd5dd8006652", - "id": "fls_gLrei65i8Uzq", - "link": "lexical-elements.html#fls_gLrei65i8Uzq", - "number": "2.4.3.2:1" + "checksum": "e3f691f921462849f2125bb6f79621259b85389c58f9db3809ce843964ba6095", + "id": "fls_ldwencd8zp9a", + "link": "entities-and-resolution.html#fls_ldwencd8zp9a", + "number": "14.4.7:5" }, { - "checksum": "9e1becddf98e3da940cd15c78147593aca7294145116b7a88ef4ee29b333791c", - "id": "fls_9nJHsg9dCi66", - "link": "lexical-elements.html#fls_9nJHsg9dCi66", - "number": "2.4.3.2:2" - } - ], - "title": "Raw C String Literals" - }, - { - "id": "fls_hv9jtycp0o1y", - "informational": false, - "link": "lexical-elements.html#numeric-literals", - "number": "2.4.4", - "paragraphs": [ - { - "checksum": "d35bc8c4c792333754935689e43e0ed039b2a0b85a1730fd03f18091f05a6c19", - "id": "fls_fqpqnku27v99", - "link": "lexical-elements.html#fls_fqpqnku27v99", - "number": "2.4.4:1" - } - ], - "title": "Numeric Literals" - }, - { - "id": "fls_2ed4axpsy9u0", - "informational": false, - "link": "lexical-elements.html#integer-literals", - "number": "2.4.4.1", - "paragraphs": [ - { - "checksum": "838a11239236c63c53764779b279c93d7dd7d3de4c52d1940ac02c6568cca205", - "id": "fls_vkk2krfn93ry", - "link": "lexical-elements.html#fls_vkk2krfn93ry", - "number": "2.4.4.1:1" + "checksum": "0486289257a5a1890cd132dfa6c3f59e5fddf88be745323f21a5d89198421ac0", + "id": "fls_jz7hgkvocc9r", + "link": "entities-and-resolution.html#fls_jz7hgkvocc9r", + "number": "14.4.7:6" }, { - "checksum": "45573384a56c61616dc2f95c2ed80da8e157435fd2eaacf7dba7d34bd26d9f5c", - "id": "fls_nxqncu5yq4eu", - "link": "lexical-elements.html#fls_nxqncu5yq4eu", - "number": "2.4.4.1:2" + "checksum": "5552d190c3a9cad12620fa1208ea5d3f5618d642ab37332f58d35c9d58779495", + "id": "fls_p4g8sxba7at9", + "link": "entities-and-resolution.html#fls_p4g8sxba7at9", + "number": "14.4.7:7" }, { - "checksum": "b53030bb2f4fce14a508b6f1efada25625e0fd5ba0b3fecfd8c97e0cb04cda6a", - "id": "fls_rn8xfd66yvst", - "link": "lexical-elements.html#fls_rn8xfd66yvst", - "number": "2.4.4.1:3" + "checksum": "b37222b3100e2b80b7b034edb7a76cc840d78dd0fe191e49a45846b0e6903d8d", + "id": "fls_d1cp5pt5wn0z", + "link": "entities-and-resolution.html#fls_d1cp5pt5wn0z", + "number": "14.4.7:8" }, { - "checksum": "fb04cc8a0da09c17517d97ccfff5433116894258fe657d52d61fa56ae4f58b22", - "id": "fls_2268lchxkzjp", - "link": "lexical-elements.html#fls_2268lchxkzjp", - "number": "2.4.4.1:4" + "checksum": "f2348baa759c683ef36d18d117184e9e34670dd0247232e3c9765ce2bd5acf41", + "id": "fls_ibmm8y748z4", + "link": "entities-and-resolution.html#fls_ibmm8y748z4", + "number": "14.4.7:9" }, { - "checksum": "39e541881b32e1399af9d22c81f2c9acf7374597615a6e067f19a2a1483716bc", - "id": "fls_4v7awnutbpoe", - "link": "lexical-elements.html#fls_4v7awnutbpoe", - "number": "2.4.4.1:5" + "checksum": "3b60014467b97b96f55006cc958d04dba76f07721782eae6af5f4185ef0b50ed", + "id": "fls_39011vsy2vxx", + "link": "entities-and-resolution.html#fls_39011vsy2vxx", + "number": "14.4.7:10" }, { - "checksum": "e930d37f51da1de444b9cdce508609ab005c7df01cf636cbae19629ed63527cd", - "id": "fls_f1e29aj0sqvl", - "link": "lexical-elements.html#fls_f1e29aj0sqvl", - "number": "2.4.4.1:6" + "checksum": "ee8879063f069e206d0ee79c4f20ae8cdc570027eeec9cd9ba5327ad2ecc4697", + "id": "fls_m81hyd154yun", + "link": "entities-and-resolution.html#fls_m81hyd154yun", + "number": "14.4.7:11" }, { - "checksum": "0c2fc993dbe174e0cd94651585b88f744f4e4a827c5145dd0137aa6eab3387f4", - "id": "fls_u83mffscqm6", - "link": "lexical-elements.html#fls_u83mffscqm6", - "number": "2.4.4.1:7" + "checksum": "92bfa8ddd580edf9895e16b361150c8c3d0cdd1c9ccde8524f7e6451e4e7fc64", + "id": "fls_fvgzmsaox4z3", + "link": "entities-and-resolution.html#fls_fvgzmsaox4z3", + "number": "14.4.7:12" }, { - "checksum": "56156e06e89f2c8d0e82416fb36dbe038b0aad5dd5d553108e3cd3e560351052", - "id": "fls_g10nuv14q4jn", - "link": "lexical-elements.html#fls_g10nuv14q4jn", - "number": "2.4.4.1:8" + "checksum": "be583136a634bdd26f4a80930ae3ca6dbbdd6e0358978bd6923a42e1c84b0da7", + "id": "fls_rj8uld11o1br", + "link": "entities-and-resolution.html#fls_rj8uld11o1br", + "number": "14.4.7:13" }, { - "checksum": "f684fbad635257607f2b55c5da42894852a1ba30d630ac6d1e217b81f6bfdf8d", - "id": "fls_hpkkvuj1z1ez", - "link": "lexical-elements.html#fls_hpkkvuj1z1ez", - "number": "2.4.4.1:9" + "checksum": "628e0a86e2c9490c0f27ad412acf3bfcaebb23591129c816d0863d06c698ea97", + "id": "fls_hyp4dnpqe620", + "link": "entities-and-resolution.html#fls_hyp4dnpqe620", + "number": "14.4.7:14" }, { - "checksum": "fcae3d231f80f39da6c1244a291d2760710458e799952561422e8c9280d35453", - "id": "fls_7yq2fep848ky", - "link": "lexical-elements.html#fls_7yq2fep848ky", - "number": "2.4.4.1:10" + "checksum": "c3092ffb4d6f8e51556d11db102ff26ff4a26fcc6d578001ab2367f0466f8681", + "id": "fls_zgied4qysk2a", + "link": "entities-and-resolution.html#fls_zgied4qysk2a", + "number": "14.4.7:15" }, { - "checksum": "1f4a7189ec6869ed715b2809e1dfdf0d1da660f106391d0ae75419cb2a3bc674", - "id": "fls_bzm8lwq3qlat", - "link": "lexical-elements.html#fls_bzm8lwq3qlat", - "number": "2.4.4.1:11" + "checksum": "ef0cd3b9e3e74fb06c3b65260e11dad890b4f75033c75029d59385ec3657f7b4", + "id": "fls_cn6dzmrxdp1w", + "link": "entities-and-resolution.html#fls_cn6dzmrxdp1w", + "number": "14.4.7:16" }, { - "checksum": "da4e7c81f503ecc2ccc9f9996edd96f76f669a45826eeb0bbb333d2e4a34dc91", - "id": "fls_l4cx36brc1r5", - "link": "lexical-elements.html#fls_l4cx36brc1r5", - "number": "2.4.4.1:12" + "checksum": "b147985d5033458a477fc80cfbdbe97b50bfe9cba065f8e54ce66e21162aa93b", + "id": "fls_9n7m0tv7w2np", + "link": "entities-and-resolution.html#fls_9n7m0tv7w2np", + "number": "14.4.7:17" }, { - "checksum": "c1fe3c1f3f5a441d07e72c6702bc49ef8f801249e1a6b368c41d41cb087234e7", - "id": "fls_wthchinwx996", - "link": "lexical-elements.html#fls_wthchinwx996", - "number": "2.4.4.1:13" + "checksum": "f72b277b5c1c9727052cdaaee9b8c8db7db662bebc2b7e7059b6af7aed73da62", + "id": "fls_sj2ivbf2l2dp", + "link": "entities-and-resolution.html#fls_sj2ivbf2l2dp", + "number": "14.4.7:18" }, { - "checksum": "9efb52c23e09f3c839307b622ea91a74fb827775d9312350956fc958a2a13a48", - "id": "fls_7uoaet2pm3am", - "link": "lexical-elements.html#fls_7uoaet2pm3am", - "number": "2.4.4.1:14" + "checksum": "86db3013b3c7a67a2925b579f72ceec4124fd83d53e5dc632e4b57d7cc2eaf3b", + "id": "fls_cejfio3ddy0j", + "link": "entities-and-resolution.html#fls_cejfio3ddy0j", + "number": "14.4.7:19" }, { - "checksum": "90e73387f32e74d98fccc01498d6b81106bfa39272684edd996c6bdc2ad8f099", - "id": "fls_p4rw583o2qbi", - "link": "lexical-elements.html#fls_p4rw583o2qbi", - "number": "2.4.4.1:15" + "checksum": "c2e9b4fad1e4fc3b81216128713d9fd685d26f561850a43261e300f242650592", + "id": "fls_j3rot386teec", + "link": "entities-and-resolution.html#fls_j3rot386teec", + "number": "14.4.7:20" }, { - "checksum": "a4c17c7e7b65883a7408b8cb26441cee4c5a98f040aec9f252015fc7fa72a28e", - "id": "fls_xrv4q56lmoo3", - "link": "lexical-elements.html#fls_xrv4q56lmoo3", - "number": "2.4.4.1:16" + "checksum": "f953ad67423f37e5b897c8c6668894471626c602b1893f1d007a340ddedbecbd", + "id": "fls_nuobrpnymym1", + "link": "entities-and-resolution.html#fls_nuobrpnymym1", + "number": "14.4.7:21" }, { - "checksum": "5cd753841d4cf1b3ac2e299d851b9d7d782ffec5218f862a6145c1bb97fe48af", - "id": "fls_66e3q5um6cwc", - "link": "lexical-elements.html#fls_66e3q5um6cwc", - "number": "2.4.4.1:17" + "checksum": "f961d6fe49562f6bd683b5e7dac9856e6b65949cdfe488452a9af791a1a20a38", + "id": "fls_r0x9sw7dwnww", + "link": "entities-and-resolution.html#fls_r0x9sw7dwnww", + "number": "14.4.7:22" }, { - "checksum": "b8c0a318a752dfac35fa253f5eaa5d86aa4d60c81746dbef4f79760bfbbb910e", - "id": "fls_5asyk66y7c9d", - "link": "lexical-elements.html#fls_5asyk66y7c9d", - "number": "2.4.4.1:18" + "checksum": "0132fa4a1bb8914b9f99869812101355d04a8da7aad46c2a3fdb0838b020ca97", + "id": "fls_ve7svuy7xvh0", + "link": "entities-and-resolution.html#fls_ve7svuy7xvh0", + "number": "14.4.7:23" }, { - "checksum": "def3e28ea7543e8f1e3cd2976118a6057928353e8c252c4b3dbd25c489171d9e", - "id": "fls_76fifqjka0lx", - "link": "lexical-elements.html#fls_76fifqjka0lx", - "number": "2.4.4.1:19" - }, - { - "checksum": "7be03faca47fa6b3d15b6e1bc4e6cb7412ac921daeaa997fec6b05a74ebf315f", - "id": "fls_fsaimo419gf0", - "link": "lexical-elements.html#fls_fsaimo419gf0", - "number": "2.4.4.1:20" - }, - { - "checksum": "a0e4b322fa7da7cd532ed7b176659ab4f67e9391a98e3f8ff3a6aee868b8cc88", - "id": "fls_hvzacbu7yiwc", - "link": "lexical-elements.html#fls_hvzacbu7yiwc", - "number": "2.4.4.1:21" + "checksum": "ab92461a17e2778df037251723d9e8305445c141efe44b6ca98f7ddd5804cbe9", + "id": "fls_pvfqhtts3qsa", + "link": "entities-and-resolution.html#fls_pvfqhtts3qsa", + "number": "14.4.7:24" }, { - "checksum": "7addf8fc9c1954d1f299f518261046655b25344da4e2566f4a6d2ad56af8f9a2", - "id": "fls_50qipwqi3arw", - "link": "lexical-elements.html#fls_50qipwqi3arw", - "number": "2.4.4.1:22" + "checksum": "08f70ffd77b18c260a9e349a49a29aed866ef6fa09e0df2aa9f1a8228046edc8", + "id": "fls_9k9hourczbv7", + "link": "entities-and-resolution.html#fls_9k9hourczbv7", + "number": "14.4.7:25" }, { - "checksum": "f00c3c8190df9c8356e8d00d40dc820ee8c2066ee14bc6fa8b113f1c2338df01", - "id": "fls_idzhusp2l908", - "link": "lexical-elements.html#fls_idzhusp2l908", - "number": "2.4.4.1:23" + "checksum": "6d3036f41d092e3cd2e55476c4918788be2ab98eb9c6d0b9e69ee70d433cbce7", + "id": "fls_p6wiuhkeypzs", + "link": "entities-and-resolution.html#fls_p6wiuhkeypzs", + "number": "14.4.7:26" }, { - "checksum": "a94d2d7881175e1f816e2299655e250ae163b1ded65deba1d719d1480c2c8213", - "id": "fls_qqrqyc6uhol", - "link": "lexical-elements.html#fls_qqrqyc6uhol", - "number": "2.4.4.1:24" + "checksum": "4025d7292308fbfa960b1bb6777f0012c20f86641c9d40465dbeefdf5b08c521", + "id": "fls_34usianesmf6", + "link": "entities-and-resolution.html#fls_34usianesmf6", + "number": "14.4.7:27" }, { - "checksum": "15953208cdd66d1e39876632dc159e551d824c61166d95fef941338342da8c30", - "id": "fls_pexi5jazthq6", - "link": "lexical-elements.html#fls_pexi5jazthq6", - "number": "2.4.4.1:25" - } - ], - "title": "Integer Literals" - }, - { - "id": "fls_29tlg1vyqay2", - "informational": false, - "link": "lexical-elements.html#float-literals", - "number": "2.4.4.2", - "paragraphs": [ - { - "checksum": "5fbdbcd887fb491134091fc535b453fbfc99b8b158a38e18667520fde61df8fd", - "id": "fls_rzi7oeqokd6e", - "link": "lexical-elements.html#fls_rzi7oeqokd6e", - "number": "2.4.4.2:1" + "checksum": "6e01c796d3afc2eeedc107698dfc4456b7446b5110ba7583416245987f3b16db", + "id": "fls_n1a41d8i0rot", + "link": "entities-and-resolution.html#fls_n1a41d8i0rot", + "number": "14.4.7:28" }, { - "checksum": "49113c092a3bbaaee5ecc5e237faa3a1d6b023cb7963903b2987786386b06cbc", - "id": "fls_2ru1zyrykd37", - "link": "lexical-elements.html#fls_2ru1zyrykd37", - "number": "2.4.4.2:2" + "checksum": "0db116cf8a599b71212419a8bae8425a590f80dc601f73ff9e537a80f6411a9a", + "id": "fls_amhi3d9dd3i3", + "link": "entities-and-resolution.html#fls_amhi3d9dd3i3", + "number": "14.4.7:29" }, { - "checksum": "de707bc1cc7a5d7d39af4acc4398ef7e645ac259968bb70066966823e3320d0b", - "id": "fls_21mhnhplzam7", - "link": "lexical-elements.html#fls_21mhnhplzam7", - "number": "2.4.4.2:3" + "checksum": "b12e44bc28cdd8d7415305251565ee0efd311e80ca5798f02424d3d97af7abf9", + "id": "fls_nu8xj3vza55j", + "link": "entities-and-resolution.html#fls_nu8xj3vza55j", + "number": "14.4.7:30" }, { - "checksum": "8851975681aef35016e51dcd1065ff3a7c4dcc59185943068c52b5f39a45220c", - "id": "fls_drqh80k0sfkb", - "link": "lexical-elements.html#fls_drqh80k0sfkb", - "number": "2.4.4.2:4" + "checksum": "2831803e44ad7befd12a919e248e44a0be126600a95ea86bddaf2a682634f7ea", + "id": "fls_fiyj50u6cg2n", + "link": "entities-and-resolution.html#fls_fiyj50u6cg2n", + "number": "14.4.7:31" }, { - "checksum": "4f2baa57a7d41e696d29d2b12dd05976c69c2dca1d26d41b65c2ae18930aa583", - "id": "fls_cbs7j9pjpusw", - "link": "lexical-elements.html#fls_cbs7j9pjpusw", - "number": "2.4.4.2:5" + "checksum": "be9f93509f539e90659d105d4f3312acd8a85b94e8c3aa00920639811e1eb437", + "id": "fls_azjx3y5yezoi", + "link": "entities-and-resolution.html#fls_azjx3y5yezoi", + "number": "14.4.7:32" }, { - "checksum": "1c3e432fbd868c7c0e6f540bab2df3a5a05a874a2cf90c0c008273c8750df94a", - "id": "fls_b9w7teaw1f8f", - "link": "lexical-elements.html#fls_b9w7teaw1f8f", - "number": "2.4.4.2:6" + "checksum": "9360333f7d8597a9b299a8028b723451ce669aa9bf7571105bcffc0ed2665b48", + "id": "fls_puly43s4x360", + "link": "entities-and-resolution.html#fls_puly43s4x360", + "number": "14.4.7:33" }, { - "checksum": "0ed79abbdbc256059087c1f145e130bf6ae83c5d9ee2cf63b10c872203678b32", - "id": "fls_eawxng4ndhv0", - "link": "lexical-elements.html#fls_eawxng4ndhv0", - "number": "2.4.4.2:7" + "checksum": "07f7dbb2dcd0707931f55e12d3c59fd8e10ecb809691978bc03ebcae149c872a", + "id": "fls_pxtlu7ud6w2h", + "link": "entities-and-resolution.html#fls_pxtlu7ud6w2h", + "number": "14.4.7:34" }, { - "checksum": "5af8baeae865b0b892668889094599d1003adefb8ca8775954c2bbb981df6244", - "id": "fls_yuhza1muo7o", - "link": "lexical-elements.html#fls_yuhza1muo7o", - "number": "2.4.4.2:8" + "checksum": "73d81df70d3dae226f85d4fc1d947dcb6c50a3b857102f823d92d248260ce169", + "id": "fls_ddxxt11u0yal", + "link": "entities-and-resolution.html#fls_ddxxt11u0yal", + "number": "14.4.7:35" }, { - "checksum": "3ab353f0319f1a988557a20ca5d4cbbe6564a0901c1e800e65fdb0f7d63244c4", - "id": "fls_4sxt1ct7fyen", - "link": "lexical-elements.html#fls_4sxt1ct7fyen", - "number": "2.4.4.2:9" + "checksum": "e9128513da136c0be871747b63ccd23562fce9810967916d929dfa3e17fdd8b7", + "id": "fls_qofr9vme46wp", + "link": "entities-and-resolution.html#fls_qofr9vme46wp", + "number": "14.4.7:36" }, { - "checksum": "da8b316abb73b8a595ca4e315dca897882f91c0dce9825eb81538e688460e26b", - "id": "fls_wa72rssp0jnt", - "link": "lexical-elements.html#fls_wa72rssp0jnt", - "number": "2.4.4.2:10" + "checksum": "0900771280a33d9ba8e1d92770357e08ea0ba60343df556c52ebd24218d890a6", + "id": "fls_gjvfty9m84a9", + "link": "entities-and-resolution.html#fls_gjvfty9m84a9", + "number": "14.4.7:37" }, { - "checksum": "8cf076d0fe9b1af3dae27583a7489cd5da291f7a3ad82ee704b53d75a6b88bd3", - "id": "fls_x2cw7g8g56f8", - "link": "lexical-elements.html#fls_x2cw7g8g56f8", - "number": "2.4.4.2:11" + "checksum": "32dbf2307aedd581270c9472e89c27976e8bafcdef78287e45a2d79c85a0e2fa", + "id": "fls_xr9wors6oa7w", + "link": "entities-and-resolution.html#fls_xr9wors6oa7w", + "number": "14.4.7:38" } ], - "title": "Float Literals" + "title": "Scope Hierarchy" }, { - "id": "fls_ypa86oqxhn9u", + "id": "fls_dq403wq5yrs", "informational": false, - "link": "lexical-elements.html#character-literals", - "number": "2.4.5", + "link": "entities-and-resolution.html#namespaces", + "number": "14.5", "paragraphs": [ { - "checksum": "74e9ccd35447ec63b8c0425e182a07fca94414ed3f7ddea9fa310ad919d551f9", - "id": "fls_j9q9ton57rvl", - "link": "lexical-elements.html#fls_j9q9ton57rvl", - "number": "2.4.5:1" + "checksum": "4836b7a81f95f97d95f77726c269b4aa77f371889797ad03dd5ac5df22bb36d3", + "id": "fls_1d4jm61qnt4l", + "link": "entities-and-resolution.html#fls_1d4jm61qnt4l", + "number": "14.5:1" }, { - "checksum": "eaa3a29da7d1231106d2806b74071e86016eeeac6bda726a1b67b7946fc688fe", - "id": "fls_5v9gx22g5wPm", - "link": "lexical-elements.html#fls_5v9gx22g5wPm", - "number": "2.4.5:2" + "checksum": "db9113301feaaf01f285b54f58e061b2b4b3902f5faf3d7d227eb0b315c29ed6", + "id": "fls_avsua7bho205", + "link": "entities-and-resolution.html#fls_avsua7bho205", + "number": "14.5:2" }, { - "checksum": "22ca23a599d712303316952fe635355f16bf2f9ff55658bf7a1ecc55ecca4ce1", - "id": "fls_vag2oy4q7d4n", - "link": "lexical-elements.html#fls_vag2oy4q7d4n", - "number": "2.4.5:3" + "checksum": "9d7737fbed1d15d12a68fb7556505470fade9e802af7fd449a008b5b02bcfcdc", + "id": "fls_9e3xeza853wx", + "link": "entities-and-resolution.html#fls_9e3xeza853wx", + "number": "14.5:3" }, { - "checksum": "e33f767e24f5ea1c1341bf515463aec11114c952dba17c8c17dfa64f277d2291", - "id": "fls_n8z6p6g564r2", - "link": "lexical-elements.html#fls_n8z6p6g564r2", - "number": "2.4.5:4" - } - ], - "title": "Character Literals" - }, - { - "id": "fls_boyhlu5srp6u", - "informational": false, - "link": "lexical-elements.html#string-literals", - "number": "2.4.6", - "paragraphs": [ + "checksum": "1ca9460a4376c7d597c332cf181c59202868d0b5d8d5fe8255a7b430c6025ed1", + "id": "fls_w625tk3ogdui", + "link": "entities-and-resolution.html#fls_w625tk3ogdui", + "number": "14.5:4" + }, { - "checksum": "c886554b68518a6cb11a6671911a00850d338a207bf5813cbf9a2dfd6f6ee63a", - "id": "fls_7fuctvtvdi7x", - "link": "lexical-elements.html#fls_7fuctvtvdi7x", - "number": "2.4.6:1" + "checksum": "a19dd69d00d64c388eab5b3d9c048c0137016955c4437b1898ac721c57e8f994", + "id": "fls_crwfafrmydr7", + "link": "entities-and-resolution.html#fls_crwfafrmydr7", + "number": "14.5:5" }, { - "checksum": "c2898629e305307a7f0f10353abc8b6b09eed1a9ec43b094adae9757f6831fe5", - "id": "fls_NyiCpU2tzJlQ", - "link": "lexical-elements.html#fls_NyiCpU2tzJlQ", - "number": "2.4.6:2" - } - ], - "title": "String Literals" - }, - { - "id": "fls_hucd52suu6it", - "informational": false, - "link": "lexical-elements.html#simple-string-literals", - "number": "2.4.6.1", - "paragraphs": [ + "checksum": "823ca05b98ccb7e81d9e549d58a3aae35ebbed2f14813b3715bc9e393835a951", + "id": "fls_t8fcpm8ldv1y", + "link": "entities-and-resolution.html#fls_t8fcpm8ldv1y", + "number": "14.5:6" + }, { - "checksum": "ac492dd896d3af7d678cdc44d9708f856cbc67ce41ee48385f730c82f0fb65eb", - "id": "fls_1pdzwkt5txfj", - "link": "lexical-elements.html#fls_1pdzwkt5txfj", - "number": "2.4.6.1:1" + "checksum": "b25e6462bc8422aa888e0dfcd2fce3eb82d9ad592ae8442b03f73c12fb73f326", + "id": "fls_7pkex797rkeu", + "link": "entities-and-resolution.html#fls_7pkex797rkeu", + "number": "14.5:7" }, { - "checksum": "8eeeebd628d07b2ea71c78a21c61dd978f7d9af4bd0b88a95768966881dd52b4", - "id": "fls_wawtu6j3fiqn", - "link": "lexical-elements.html#fls_wawtu6j3fiqn", - "number": "2.4.6.1:2" + "checksum": "1d855f70942b4b97e20f7a8c2376293535691ffc63e1a3c00d9ff48fbed1c016", + "id": "fls_v32f2evgqt5q", + "link": "entities-and-resolution.html#fls_v32f2evgqt5q", + "number": "14.5:8" }, { - "checksum": "4a6e317133ecb5ae53aefdc730f509e09e0c3713395895908d88d18e106527b7", - "id": "fls_ycy5ee6orjx", - "link": "lexical-elements.html#fls_ycy5ee6orjx", - "number": "2.4.6.1:3" + "checksum": "be6570673f514f4a0d3d22cd860599d6811f9cd474760042c53ed220001d5d06", + "id": "fls_f6yrzwu6yi30", + "link": "entities-and-resolution.html#fls_f6yrzwu6yi30", + "number": "14.5:9" }, { - "checksum": "cb50f037e2188c9a58f86b87d2c9d1f90186b0dcbe6117c343aa5fba6b2d7daa", - "id": "fls_6nt5kls21xes", - "link": "lexical-elements.html#fls_6nt5kls21xes", - "number": "2.4.6.1:4" - } - ], - "title": "Simple String Literals" - }, - { - "id": "fls_usr6iuwpwqqh", - "informational": false, - "link": "lexical-elements.html#raw-string-literals", - "number": "2.4.6.2", - "paragraphs": [ + "checksum": "039f5baf7e3e9350742076c571ec8701657413eb2ab8cf7c8ba7e4a9c6525e47", + "id": "fls_nk0swexy2ztm", + "link": "entities-and-resolution.html#fls_nk0swexy2ztm", + "number": "14.5:10" + }, { - "checksum": "cf9f7d7333258a4d9a235a6a611a80fd87d664cafd5732da2eb6c1155dfad367", - "id": "fls_36suwhbwmq1t", - "link": "lexical-elements.html#fls_36suwhbwmq1t", - "number": "2.4.6.2:1" + "checksum": "4e6c9e8735f706d3ed311192bdd242aebefbc9cd8b79ed32481799fc4e4d54ab", + "id": "fls_ckptn88o6lla", + "link": "entities-and-resolution.html#fls_ckptn88o6lla", + "number": "14.5:11" }, { - "checksum": "d3b402e15a8bff639df20fcf15d4306d568a4d8b0e65132224975ba1cd7ffd25", - "id": "fls_ms43w1towz40", - "link": "lexical-elements.html#fls_ms43w1towz40", - "number": "2.4.6.2:2" - } - ], - "title": "Raw String Literals" - }, - { - "id": "fls_jkab8eevzbte", - "informational": false, - "link": "lexical-elements.html#boolean-literals", - "number": "2.4.7", - "paragraphs": [ + "checksum": "c2df785b7f8b4c342a0cd204655bf38f1cdcdf6da21e8f7033577a0779ea7dfa", + "id": "fls_3ma5v1fop98p", + "link": "entities-and-resolution.html#fls_3ma5v1fop98p", + "number": "14.5:12" + }, { - "checksum": "9222cb981e760f03c8b46b8708ecf54b556f6fb343a0017cb16fba2bcb98c10f", - "id": "fls_1lll64ftupjd", - "link": "lexical-elements.html#fls_1lll64ftupjd", - "number": "2.4.7:1" + "checksum": "b626273c028df98bf83f9cc308f4b313b40fd4af9a828d4baa99d514847b7e4f", + "id": "fls_nj7sep7ht7lg", + "link": "entities-and-resolution.html#fls_nj7sep7ht7lg", + "number": "14.5:13" }, { - "checksum": "68a6151c72c232478bda2c413c1552acfd1e8304956b5733ef8a13c86768b079", - "id": "fls_pgngble3ilyx", - "link": "lexical-elements.html#fls_pgngble3ilyx", - "number": "2.4.7:2" - } - ], - "title": "Boolean Literals" - }, - { - "id": "fls_q8l2jza7d9xa", - "informational": false, - "link": "lexical-elements.html#comments", - "number": "2.5", - "paragraphs": [ + "checksum": "e2e5c5f30f5fe02da3765cc72c29c109ec1284daaaacef2440cd86c7dca3ce84", + "id": "fls_g8h6t5x6yprm", + "link": "entities-and-resolution.html#fls_g8h6t5x6yprm", + "number": "14.5:14" + }, { - "checksum": "80a97a25c037e20dbb697978e3c79c22243ea2bb094a1a60f10b8cc358d57568", - "id": "fls_8obn3dtzpe5f", - "link": "lexical-elements.html#fls_8obn3dtzpe5f", - "number": "2.5:1" + "checksum": "6cc5106a9453f6b24aa3d1b25413993384c934c46830703cf83d6aea69144c37", + "id": "fls_2l1o7vqfr4m7", + "link": "entities-and-resolution.html#fls_2l1o7vqfr4m7", + "number": "14.5:15" }, { - "checksum": "2dea71c3ed9833c779a0c63b8eae45c64771074fdbc263b67cd2115d7e9cf4ab", - "id": "fls_qsbnl11be35s", - "link": "lexical-elements.html#fls_qsbnl11be35s", - "number": "2.5:2" + "checksum": "6de1568e937c91c9d59930b38b47a437c2f0d6c186377eb0f20facbfed8ffed5", + "id": "fls_6q8rjv1jmu84", + "link": "entities-and-resolution.html#fls_6q8rjv1jmu84", + "number": "14.5:16" }, { - "checksum": "21cfcc3e2a54f9de067b507a9febe04379152937834f9c805769db5f4f31efaf", - "id": "fls_nayisy85kyq2", - "link": "lexical-elements.html#fls_nayisy85kyq2", - "number": "2.5:3" + "checksum": "da1ee981a9e1da0bcc03eb523e05994e90313be4bbd2f3a91ac1cef0226944fe", + "id": "fls_lx2tx1jt8t3a", + "link": "entities-and-resolution.html#fls_lx2tx1jt8t3a", + "number": "14.5:17" }, { - "checksum": "44db9ffd84a7092979605a0738feeec5f4b7058b977a23c47673c33c3ece8211", - "id": "fls_k3hj30hjkdhw", - "link": "lexical-elements.html#fls_k3hj30hjkdhw", - "number": "2.5:4" + "checksum": "1f6a93e2bd7f34e4fba8c83590318721a361b61a42c82338c30914ca994e74b0", + "id": "fls_mo00df28t7c1", + "link": "entities-and-resolution.html#fls_mo00df28t7c1", + "number": "14.5:18" }, { - "checksum": "46b1ba35f675d689f5cccd754f8935bdbf89c5ad8ddf1e0563957ca73a891019", - "id": "fls_tspijl68lduc", - "link": "lexical-elements.html#fls_tspijl68lduc", - "number": "2.5:5" + "checksum": "e64cf0d2e680eb773614e1b17642212b8071fe0e2601a1b4c95e71731c84a4e0", + "id": "fls_8o3izim4zf8t", + "link": "entities-and-resolution.html#fls_8o3izim4zf8t", + "number": "14.5:19" }, { - "checksum": "270e8134659947ebca11488a29a6802edd92f2d00c5c5cc2cdd352343a96c6da", - "id": "fls_KZp0yiFLTqxb", - "link": "lexical-elements.html#fls_KZp0yiFLTqxb", - "number": "2.5:6" + "checksum": "8030bef3d19c581e3a4a96c91f3fd57388b1fb2bedea44b40c010da243302e7f", + "id": "fls_fweohszgbuj4", + "link": "entities-and-resolution.html#fls_fweohszgbuj4", + "number": "14.5:20" }, { - "checksum": "7db44552637b47a7c80a482ccb21eff193aec3ef608b793139b79c90b9d259e9", - "id": "fls_63gzofa9ktic", - "link": "lexical-elements.html#fls_63gzofa9ktic", - "number": "2.5:7" + "checksum": "df9b14ad6a4ce87a81cdbfcb59cf04e07e8eb0a0c2de14823a7b87d1ee2a5700", + "id": "fls_ry02dzisxz3h", + "link": "entities-and-resolution.html#fls_ry02dzisxz3h", + "number": "14.5:21" }, { - "checksum": "1d2db5c6599512c9ff5347dd4643d081ca0ef713a638861316d0eb850fea2aef", - "id": "fls_scko7crha0um", - "link": "lexical-elements.html#fls_scko7crha0um", - "number": "2.5:8" + "checksum": "5cd65fc00ce58c6713964263dae05662f477e761c5b34c5bd4f050b3608b12bc", + "id": "fls_dcz1bxjjfsq", + "link": "entities-and-resolution.html#fls_dcz1bxjjfsq", + "number": "14.5:22" }, { - "checksum": "89aba4c16b95e9fa5e40e6f8fda1c986eb5097820542fc57bf4d63ee2641be25", - "id": "fls_RYVL9KgaxKvl", - "link": "lexical-elements.html#fls_RYVL9KgaxKvl", - "number": "2.5:9" + "checksum": "1d817e252ce6759b84635d64a683f45d0081ab7c687143959331fb141cce459a", + "id": "fls_wt9kgsi6n6ep", + "link": "entities-and-resolution.html#fls_wt9kgsi6n6ep", + "number": "14.5:23" }, { - "checksum": "b5cdea2b94f91084652299806e1a80b282c551014c7d65952547fca3a2bb7cf8", - "id": "fls_7n6d3jx61ose", - "link": "lexical-elements.html#fls_7n6d3jx61ose", - "number": "2.5:10" + "checksum": "52ce21886a1f1adcb3176e37769e7fbf75f96b161b8f1ff617e40e254a1113b6", + "id": "fls_w29t5njbe46s", + "link": "entities-and-resolution.html#fls_w29t5njbe46s", + "number": "14.5:24" }, { - "checksum": "a34430d6423f60015c14850fbc8c713a11943c37acb592477ed6bb0ed902b6a2", - "id": "fls_6fxcs17n4kw", - "link": "lexical-elements.html#fls_6fxcs17n4kw", - "number": "2.5:11" + "checksum": "26d2b1e1d098bd315370c984c209e9bcfe05242d062331290c72be6bbe298b92", + "id": "fls_u1533bngb0yv", + "link": "entities-and-resolution.html#fls_u1533bngb0yv", + "number": "14.5:25" }, { - "checksum": "fe2e7af7111eab22f62c00b2ba9fc0c53748fcdb5ec02a7e20dd1e4247706e15", - "id": "fls_uze7l7cxonk1", - "link": "lexical-elements.html#fls_uze7l7cxonk1", - "number": "2.5:12" + "checksum": "daa0b3886b8ff9f6ff8b555ad965ddbc2563f2cf44e1b869d632647511fde7f0", + "id": "fls_e8v4g45v5ry2", + "link": "entities-and-resolution.html#fls_e8v4g45v5ry2", + "number": "14.5:26" }, { - "checksum": "6cff261ef0c0d257749ff6809ac237900fb6673ccb75e5186c7b4fa8857f9330", - "id": "fls_gy23lwlqw2mc", - "link": "lexical-elements.html#fls_gy23lwlqw2mc", - "number": "2.5:13" + "checksum": "120d3ec4ef8b8d96a2aeed9aae90b73656762d0a9cad70696c487cf430845448", + "id": "fls_pq8bzav84q3z", + "link": "entities-and-resolution.html#fls_pq8bzav84q3z", + "number": "14.5:27" }, { - "checksum": "cf00d52ba7c749b5e814bc2852d5e73d2b5454950abb829df0c20d7bb9ba9d60", - "id": "fls_w7d0skpov1is", - "link": "lexical-elements.html#fls_w7d0skpov1is", - "number": "2.5:14" + "checksum": "809a2c313aa61cdd28a44cd5fcd05bcb52778a18127ddd942da86fc441d1c2de", + "id": "fls_ttr6v8ca4av0", + "link": "entities-and-resolution.html#fls_ttr6v8ca4av0", + "number": "14.5:28" }, { - "checksum": "255a7dcb2fc2b925e5aa49e3be573dcce5c450e40835c21a4ce5008439cdbeb8", - "id": "fls_32ncjvj2kn7z", - "link": "lexical-elements.html#fls_32ncjvj2kn7z", - "number": "2.5:15" + "checksum": "3ff5dde150cb5c944aec2ecd5a8349fb90870bae072f2f54544f3c820bfa7fee", + "id": "fls_aivi97hhfxy2", + "link": "entities-and-resolution.html#fls_aivi97hhfxy2", + "number": "14.5:29" }, { - "checksum": "fc50838ac3e706c4c30cfb01beba8783dc8fdb5dd7d6cb42ea6b4123e33b6e5b", - "id": "fls_ok0zvo9vcmzo", - "link": "lexical-elements.html#fls_ok0zvo9vcmzo", - "number": "2.5:16" + "checksum": "d4015ccb1d4717c0b29f6d3f48fddfad1ac1f9f8a397e4963f0d571b37bd4a4b", + "id": "fls_pie4ltdtzkl3", + "link": "entities-and-resolution.html#fls_pie4ltdtzkl3", + "number": "14.5:30" }, { - "checksum": "4c1420ace1da3c3694b1aebd35cada39def878b5c4f642ff6950ad600c7138a5", - "id": "fls_nWtKuPi8Fw6v", - "link": "lexical-elements.html#fls_nWtKuPi8Fw6v", - "number": "2.5:17" + "checksum": "650724cc8ebda80bcd989f6e99e585db43a4590a015f45fedea21bb82f33dc3c", + "id": "fls_qmf7lk6h96sv", + "link": "entities-and-resolution.html#fls_qmf7lk6h96sv", + "number": "14.5:31" + }, + { + "checksum": "ba289fa126ce5f48c2046c9cc9e5e7cbd13217e772757f9c2d12848473872ebd", + "id": "fls_ufp3btk8pet5", + "link": "entities-and-resolution.html#fls_ufp3btk8pet5", + "number": "14.5:32" + }, + { + "checksum": "cc447ee2f7a5dac66d5e22eb032c6a31eb21d5526ca66fda96ebc2ad415fe6e6", + "id": "fls_t3bnpkfazw4z", + "link": "entities-and-resolution.html#fls_t3bnpkfazw4z", + "number": "14.5:33" + }, + { + "checksum": "61fb8303812158a4adfaef901dc1452be9cb24ee1b643dfde5f612d0d0a7eacf", + "id": "fls_y0shlli54n5y", + "link": "entities-and-resolution.html#fls_y0shlli54n5y", + "number": "14.5:34" + }, + { + "checksum": "42dffcc4cecdc17b20167b7319ca32561b6c3bac4a546b72a18301ddd55a407a", + "id": "fls_tghgxcju5u2t", + "link": "entities-and-resolution.html#fls_tghgxcju5u2t", + "number": "14.5:35" + }, + { + "checksum": "40f27fcd73d87d8592e01e22030b08faea5b83cae412960f492dcc2f8a684cc2", + "id": "fls_yesesxynpq6s", + "link": "entities-and-resolution.html#fls_yesesxynpq6s", + "number": "14.5:36" + }, + { + "checksum": "ff385d3524d4f2788d893d77f024a5876c248a7eeee84ac5626642a07c55e0aa", + "id": "fls_40o8y6exr3df", + "link": "entities-and-resolution.html#fls_40o8y6exr3df", + "number": "14.5:37" + }, + { + "checksum": "a2c64e06759e68c6b0cf453b6868f12decc89182b9e88d83b02190bccc393951", + "id": "fls_y76o5ug7dtv", + "link": "entities-and-resolution.html#fls_y76o5ug7dtv", + "number": "14.5:38" + }, + { + "checksum": "45f1bccebe0303da8288a05a61a1209ff3203d962d252fcfa1fb0a549f9feb28", + "id": "fls_3np518s1su4w", + "link": "entities-and-resolution.html#fls_3np518s1su4w", + "number": "14.5:39" } ], - "title": "Comments" + "title": "Namespaces" }, { - "id": "fls_lish33a1naw5", + "id": "fls_ld0ize96cm6m", "informational": false, - "link": "lexical-elements.html#keywords", - "number": "2.6", - "paragraphs": [ - { - "checksum": "b661cfaf9bef72a6f8070ed5158ce24d582cc5371b80a7c78f52e7d49157b48f", - "id": "fls_dti0uu7rz81w", - "link": "lexical-elements.html#fls_dti0uu7rz81w", - "number": "2.6:1" - }, - { - "checksum": "9d03ce7dba3147733fdacd077fd30354c8d7b6a79883472bedba6a9643f63d6b", - "id": "fls_sxg1o4oxql51", - "link": "lexical-elements.html#fls_sxg1o4oxql51", - "number": "2.6:2" - } - ], - "title": "Keywords" - }, - { - "id": "fls_mec5cg5aptf8", - "informational": false, - "link": "lexical-elements.html#strict-keywords", - "number": "2.6.1", + "link": "entities-and-resolution.html#preludes", + "number": "14.6", "paragraphs": [ { - "checksum": "8394e357df5177d8ada084b53fdd141fc0e1a1ca7b937b28a47415ac0dce750f", - "id": "fls_bsh7qsyvox21", - "link": "lexical-elements.html#fls_bsh7qsyvox21", - "number": "2.6.1:1" - } - ], - "title": "Strict Keywords" - }, - { - "id": "fls_cbsgp6k0qa82", - "informational": false, - "link": "lexical-elements.html#reserved-keywords", - "number": "2.6.2", - "paragraphs": [ + "checksum": "38d62c936f206c7f1e48f785bbde2d34f1bc2865287994876e882b1c0f65d05e", + "id": "fls_po4gw6t2ptwu", + "link": "entities-and-resolution.html#fls_po4gw6t2ptwu", + "number": "14.6:1" + }, { - "checksum": "3f05a14ae9bd0f2dcdc7c65c76de451ed9485badef6dc6bb29c3fae69124e92d", - "id": "fls_w4b97ewwnql", - "link": "lexical-elements.html#fls_w4b97ewwnql", - "number": "2.6.2:1" - } - ], - "title": "Reserved Keywords" - }, - { - "id": "fls_9kjpxri0axvg", - "informational": false, - "link": "lexical-elements.html#weak-keywords", - "number": "2.6.3", - "paragraphs": [ + "checksum": "9110a045c9c8c940ad03c36e93d5155d9143317d35c55691fb325a15db4b8607", + "id": "fls_n4102qskkmz2", + "link": "entities-and-resolution.html#fls_n4102qskkmz2", + "number": "14.6:2" + }, { - "checksum": "507c02a61084d5e2447a4401c3c9ff54ecfb2b0e46adf98b0e9f96d5fbcd8eb7", - "id": "fls_bv87t1gvj7bz", - "link": "lexical-elements.html#fls_bv87t1gvj7bz", - "number": "2.6.3:1" + "checksum": "ec28e48bd9269897b4f0446e74ae493289085d680cbdc44336623ee5d261c82b", + "id": "fls_atvnwly4w8g2", + "link": "entities-and-resolution.html#fls_atvnwly4w8g2", + "number": "14.6:3" }, { - "checksum": "c2adb1a3f82e557d2e62b7aa4e734ce8a14427d039fd743bf82710befacdae6a", - "id": "fls_bl55g03jmayf", - "link": "lexical-elements.html#fls_bl55g03jmayf", - "number": "2.6.3:2" + "checksum": "51511e88fc1929d2b8a82b1599001557503f199917df1a80d734152884ff7c77", + "id": "fls_pbc7ktlu0pl", + "link": "entities-and-resolution.html#fls_pbc7ktlu0pl", + "number": "14.6:4" }, { - "checksum": "7190ee3532730e20b19057227f945e1ad04d6f993bc33984e2dad01773e09ab2", - "id": "fls_c354oryv513p", - "link": "lexical-elements.html#fls_c354oryv513p", - "number": "2.6.3:3" + "checksum": "6bc798373f758197663c5691e9bf15b398a778e6f199a05c44fa7aa932fe6c11", + "id": "fls_frjv68kqqxfh", + "link": "entities-and-resolution.html#fls_frjv68kqqxfh", + "number": "14.6:5" }, { - "checksum": "dc15f002b42ce194ade6b4b0f4f6b5ca7bb28176b493eac737f309f66b3e42da", - "id": "fls_r9fhuiq1ys1p", - "link": "lexical-elements.html#fls_r9fhuiq1ys1p", - "number": "2.6.3:4" + "checksum": "c92c07a0326e48234f51c8b6a55bbc0ef651cc56135b5f0f5b5203095dac192e", + "id": "fls_rf6a2ae3y7vu", + "link": "entities-and-resolution.html#fls_rf6a2ae3y7vu", + "number": "14.6:6" }, { - "checksum": "aa39e6b294be595698343bd1c8da7549bf90acb7b9d7532f7db483335321ba4c", - "id": "fls_g0JEluWqBpNc", - "link": "lexical-elements.html#fls_g0JEluWqBpNc", - "number": "2.6.3:5" - } - ], - "title": "Weak Keywords" - } - ], - "title": "Lexical Elements" - }, - { - "informational": false, - "link": "index.html", - "sections": [], - "title": "FLS" - }, - { - "informational": false, - "link": "ownership-and-deconstruction.html", - "sections": [ - { - "id": "fls_ronnwodjjjsh", - "informational": false, - "link": "ownership-and-deconstruction.html", - "number": "15", - "paragraphs": [], - "title": "Ownership and Destruction" - }, - { - "id": "fls_svkx6szhr472", - "informational": false, - "link": "ownership-and-deconstruction.html#ownership", - "number": "15.1", - "paragraphs": [ + "checksum": "4346e39808389249844816dc52643e4e1c2e04e1990eddc603e94ad0e797c809", + "id": "fls_sxnnkzmuvexa", + "link": "entities-and-resolution.html#fls_sxnnkzmuvexa", + "number": "14.6:7" + }, { - "checksum": "5144c7d76d836132f4544481a5a0aa566e56d0824bc6757bb51dfdf413f2809e", - "id": "fls_wt81sbsecmu0", - "link": "ownership-and-deconstruction.html#fls_wt81sbsecmu0", - "number": "15.1:1" + "checksum": "27cab7337ca595be01ad78b78429ef14d328fa6a449fea686acad8144ea79c34", + "id": "fls_qsyorqjkdh2t", + "link": "entities-and-resolution.html#fls_qsyorqjkdh2t", + "number": "14.6:8" }, { - "checksum": "317ff352b8f0f3c112a25e2c334aa6f7e2c227140ef1153851b0068b65544c4b", - "id": "fls_ckcnkbb6y3cq", - "link": "ownership-and-deconstruction.html#fls_ckcnkbb6y3cq", - "number": "15.1:2" + "checksum": "77446948cc77c5f2d38ee156686b5607c4f0331f371b47e54e835ad173f80ba9", + "id": "fls_aolj6abvp9sa", + "link": "entities-and-resolution.html#fls_aolj6abvp9sa", + "number": "14.6:9" }, { - "checksum": "5aae1e903f79062d2e7d47807976de140feb73932e1bd4d1db63f2eda9a280e8", - "id": "fls_ze0u9gfylmhn", - "link": "ownership-and-deconstruction.html#fls_ze0u9gfylmhn", - "number": "15.1:3" + "checksum": "9a4a1df8c10224f0c968a22de8bdb0bef1914575cfb19ea0eabb39bce468dd62", + "id": "fls_of4n3vv15l5z", + "link": "entities-and-resolution.html#fls_of4n3vv15l5z", + "number": "14.6:10" } ], - "title": "Ownership" + "title": "Preludes" }, { - "id": "fls_3xvm61x0t251", + "id": "fls_9gprp17h6t1q", "informational": false, - "link": "ownership-and-deconstruction.html#initialization", - "number": "15.2", + "link": "entities-and-resolution.html#use-imports", + "number": "14.7", "paragraphs": [ { - "checksum": "e347ab2d0c9e7fb6d14b8e7ee107a2220a39391724c13eeaab6b25939b1be2d9", - "id": "fls_drfzu02bo7oe", - "link": "ownership-and-deconstruction.html#fls_drfzu02bo7oe", - "number": "15.2:1" + "checksum": "813cc34b517b6e85c804618d58441cbaec0e01369396ee4e16225d853a8ea8f3", + "id": "fls_lyw4t098sxrj", + "link": "entities-and-resolution.html#fls_lyw4t098sxrj", + "number": "14.7:1" }, { - "checksum": "6ee782d7b7403e35dd81c6622fb5562c3c6116ef95eca20a6496d36f6acfe442", - "id": "fls_wnhci8phdu4m", - "link": "ownership-and-deconstruction.html#fls_wnhci8phdu4m", - "number": "15.2:2" + "checksum": "60cd8ef0b9e4e5913161b5314d0d298edd34ff20cb398eb5e69afcb53c884a65", + "id": "fls_sxo1jb25pl8a", + "link": "entities-and-resolution.html#fls_sxo1jb25pl8a", + "number": "14.7:2" }, { - "checksum": "ba3e26bd692acacc422e0367466b77045e1802b133f0ba5cdf155ae1e2c0b5d4", - "id": "fls_ch2lvm50olqd", - "link": "ownership-and-deconstruction.html#fls_ch2lvm50olqd", - "number": "15.2:3" + "checksum": "eb328b409e1e5b1f5bfe01c443065950ff895ff862c758235178db6ea7812a7b", + "id": "fls_WAA4WmohGu6T", + "link": "entities-and-resolution.html#fls_WAA4WmohGu6T", + "number": "14.7:3" }, { - "checksum": "b86bc58a37e2d666845ddee6d0ec0fd647264ab45a39b676bc6bf201782eb03a", - "id": "fls_46910buiwvv9", - "link": "ownership-and-deconstruction.html#fls_46910buiwvv9", - "number": "15.2:4" + "checksum": "1b245fbcb87f36b4177fc03be141f12a61d6353063182660fab4e0cdc454c0da", + "id": "fls_IPYvldMqduf4", + "link": "entities-and-resolution.html#fls_IPYvldMqduf4", + "number": "14.7:4" }, { - "checksum": "5948683124b4a837517cb460bac495efff05e298bed8420a688b19d16de45eb2", - "id": "fls_caufcwkpz689", - "link": "ownership-and-deconstruction.html#fls_caufcwkpz689", - "number": "15.2:5" + "checksum": "e517d4937f6b8b583887c5043676af52be726cafd61bbc680f13f5656221172d", + "id": "fls_MOXId37fcNPY", + "link": "entities-and-resolution.html#fls_MOXId37fcNPY", + "number": "14.7:5" }, { - "checksum": "0cd3d0fa123e7c11a9a250acaa0cce74c5f8760b6099acac8e084cea9be81ebd", - "id": "fls_4lg92b9yima6", - "link": "ownership-and-deconstruction.html#fls_4lg92b9yima6", - "number": "15.2:6" + "checksum": "6bf1152c602ed8ba48c73c7fa1915bfad897fb93875da88dcb8ba9ffbffd7dca", + "id": "fls_2UyFcB6Our1v", + "link": "entities-and-resolution.html#fls_2UyFcB6Our1v", + "number": "14.7:6" }, { - "checksum": "063dff1709d956a48b2bb9876a5fabfda25c3ed4b35d8449d19f6163c27811c5", - "id": "fls_xn1au0blioa3", - "link": "ownership-and-deconstruction.html#fls_xn1au0blioa3", - "number": "15.2:7" + "checksum": "79e5b1e6a7d289558aff645ca9eef17ce05e04fedcea56361c3c5c8e0ee8da88", + "id": "fls_irdKqoYzBM0M", + "link": "entities-and-resolution.html#fls_irdKqoYzBM0M", + "number": "14.7:7" }, { - "checksum": "4f92e492c63996bf71cb5f3ffb86c48dd7a928decb62b7ea6fa936acfbef7a78", - "id": "fls_jmcjboopvytb", - "link": "ownership-and-deconstruction.html#fls_jmcjboopvytb", - "number": "15.2:8" - } - ], - "title": "Initialization" - }, - { - "id": "fls_v5x85lt5ulva", - "informational": false, - "link": "ownership-and-deconstruction.html#references", - "number": "15.3", - "paragraphs": [ + "checksum": "f9a3c4f788393bf20c3f6e2f1532ebe181eabd6d14c64bc76bbde37f384cc86b", + "id": "fls_gAWsqibl4GLq", + "link": "entities-and-resolution.html#fls_gAWsqibl4GLq", + "number": "14.7:8" + }, { - "checksum": "681fcd31b82b3e32670aecd9ea9b4eb8068d07a7b2cdeaa6d9fb1442eb63ed86", - "id": "fls_7x9pi2o7pee7", - "link": "ownership-and-deconstruction.html#fls_7x9pi2o7pee7", - "number": "15.3:1" + "checksum": "5b6924c18d30552bc9b8351dcc65a6bd1e5d98680cbcb35bd254e872513eb96e", + "id": "fls_2bkcn83smy2y", + "link": "entities-and-resolution.html#fls_2bkcn83smy2y", + "number": "14.7:9" }, { - "checksum": "1a8fde4fb240539c10a88279cc4dae22433448e6531485e2124cb8558d6c828c", - "id": "fls_tsqvr3fmcel", - "link": "ownership-and-deconstruction.html#fls_tsqvr3fmcel", - "number": "15.3:2" + "checksum": "7fbdb92123ed136d12204172f71af7bdf831c5cca0d09fb88a8446f8a976e9cd", + "id": "fls_v3a6y2ze44v2", + "link": "entities-and-resolution.html#fls_v3a6y2ze44v2", + "number": "14.7:10" }, { - "checksum": "f967a5d91406384965a89b7d358d856f9b96ad76e062a2aa0cb0b238c5053633", - "id": "fls_ev4a82fdhwr8", - "link": "ownership-and-deconstruction.html#fls_ev4a82fdhwr8", - "number": "15.3:3" + "checksum": "7645908217267b4bb43c4e7b5562d641d9bd121bc47c13f2301e854c768d2dab", + "id": "fls_JHU0ersYB6eL", + "link": "entities-and-resolution.html#fls_JHU0ersYB6eL", + "number": "14.7:11" }, { - "checksum": "bf9feda03dfdc034cc70417fab5c0fb8c43e76acb709672e810538a059178ac6", - "id": "fls_cckf6dtkgwb4", - "link": "ownership-and-deconstruction.html#fls_cckf6dtkgwb4", - "number": "15.3:4" + "checksum": "8e0c213bf3df7e68bb3d5a37c5d28f9a2951831f6a212231d780248a26766802", + "id": "fls_jlNKxkuhsvX4", + "link": "entities-and-resolution.html#fls_jlNKxkuhsvX4", + "number": "14.7:12" }, { - "checksum": "71b66b39ac5995dd5408a51d900c9e4a4c0b3f6de90df9f32eeaf0c9f79a747a", - "id": "fls_8kqb8754e6p4", - "link": "ownership-and-deconstruction.html#fls_8kqb8754e6p4", - "number": "15.3:5" + "checksum": "9cec53e98886d395fc8df4d58bb00bfcb91617b34acaf616395d8e52652246e5", + "id": "fls_q0KFfNALMslq", + "link": "entities-and-resolution.html#fls_q0KFfNALMslq", + "number": "14.7:13" }, { - "checksum": "0f5c895a4ac62ecc6ec04b6282dff022b535c2a19bd75b50defdbb28493da899", - "id": "fls_v69rptdjao42", - "link": "ownership-and-deconstruction.html#fls_v69rptdjao42", - "number": "15.3:6" + "checksum": "3bfcbf98de455cd97691285c1acf7f4818c2d6a62889ebc042f7444e9a36d98b", + "id": "fls_Hy17LzOFGfOp", + "link": "entities-and-resolution.html#fls_Hy17LzOFGfOp", + "number": "14.7:14" }, { - "checksum": "f0435a5375c7104a39cbb51a31167f29e447129fae4e42da7755fd5b0d546300", - "id": "fls_vg9h6tz6z37w", - "link": "ownership-and-deconstruction.html#fls_vg9h6tz6z37w", - "number": "15.3:7" + "checksum": "64222f860a3b80479b545a9e1668b362fc096bd84fd135b83848cab59f535464", + "id": "fls_90hQvSh7Bfyg", + "link": "entities-and-resolution.html#fls_90hQvSh7Bfyg", + "number": "14.7:15" }, { - "checksum": "94a208b99cd67ebc20f0f3baf1d225f9f8e7bd033c2196e35b0a01a1aa4c092d", - "id": "fls_wcf5mxrzbujn", - "link": "ownership-and-deconstruction.html#fls_wcf5mxrzbujn", - "number": "15.3:8" + "checksum": "0e4fdf65d5d96641f5f69fd73231dc0d8b53d0a8ee40c16c7e62fc70ee53f047", + "id": "fls_wRmvtgQkFA6w", + "link": "entities-and-resolution.html#fls_wRmvtgQkFA6w", + "number": "14.7:16" }, { - "checksum": "d346ee1754805482153eb4b0ca31367a70b20b0e6b21c40c6d681b5c9b3409cc", - "id": "fls_fckoj1jh5mrc", - "link": "ownership-and-deconstruction.html#fls_fckoj1jh5mrc", - "number": "15.3:9" + "checksum": "b5a17251d386bd737a55045cc108b41a18740d77abb50130269d9ebe6303ec72", + "id": "fls_kz2Gij5wHXnl", + "link": "entities-and-resolution.html#fls_kz2Gij5wHXnl", + "number": "14.7:17" }, { - "checksum": "e8deafe60392b60bd424d7aa0309647dbf4ed1e7325348f23f15970de9f05ebf", - "id": "fls_hqxsuyn285he", - "link": "ownership-and-deconstruction.html#fls_hqxsuyn285he", - "number": "15.3:10" + "checksum": "2649f9cf8cb9cc5a2907d411e6134e4e0997b0ab85f19a07509de9ffc29575b4", + "id": "fls_yY58pFpkig9o", + "link": "entities-and-resolution.html#fls_yY58pFpkig9o", + "number": "14.7:18" }, { - "checksum": "8d9978a993a2da848a7a6a9e23d189173372b34e01fa6d81ba7a78e75f621064", - "id": "fls_i1ny0k726a4a", - "link": "ownership-and-deconstruction.html#fls_i1ny0k726a4a", - "number": "15.3:11" + "checksum": "41745bc2f1843a06a54a672f54f86ac947143da2a368e20e62f8df7a9d67ebeb", + "id": "fls_ar03D5rxjzy0", + "link": "entities-and-resolution.html#fls_ar03D5rxjzy0", + "number": "14.7:19" }, { - "checksum": "7ed63b28b2b7484eed35560cff9dd3420475b6cf99b8b7e943fe944ad33957d9", - "id": "fls_eT1hnLOx6vxk", - "link": "ownership-and-deconstruction.html#fls_eT1hnLOx6vxk", - "number": "15.3:12" - } - ], - "title": "References" - }, - { - "id": "fls_a14slch83hzn", - "informational": false, - "link": "ownership-and-deconstruction.html#borrowing", - "number": "15.4", - "paragraphs": [ - { - "checksum": "de2ae1e7180e12235906678b7ee4195bc10b9f11839f8d171f9c875c94521eca", - "id": "fls_c02flohk54pc", - "link": "ownership-and-deconstruction.html#fls_c02flohk54pc", - "number": "15.4:1" + "checksum": "00e9aea779512a6b5660ecaf0702c0d3ac4c3c845a967ca739fd4cac1be0b551", + "id": "fls_ce73bg0BqV1X", + "link": "entities-and-resolution.html#fls_ce73bg0BqV1X", + "number": "14.7:20" }, { - "checksum": "437a4bdfe9c8e90e143c24e1ca0e4cb8226d0c5ca7f02bc701a25d5adc9b50c0", - "id": "fls_j9kof0px3l7s", - "link": "ownership-and-deconstruction.html#fls_j9kof0px3l7s", - "number": "15.4:2" + "checksum": "43af72288bd22a2547f54bfac05285b72065c3c2cb06c8de5b5ce6ba9efb055b", + "id": "fls_FILuR3pfwjw3", + "link": "entities-and-resolution.html#fls_FILuR3pfwjw3", + "number": "14.7:21" }, { - "checksum": "35970b8df578395e654434eed83f9a06ec63a12c4bbf7e89bdfb97e21a7b9620", - "id": "fls_zepwytjwy049", - "link": "ownership-and-deconstruction.html#fls_zepwytjwy049", - "number": "15.4:3" + "checksum": "2ddb4b51369e74bddbb00fcd04f1bb773bfba5a9e5dfc796d8df300f808159ff", + "id": "fls_iQOgxNihUEr7", + "link": "entities-and-resolution.html#fls_iQOgxNihUEr7", + "number": "14.7:22" }, { - "checksum": "25fd85d527ce1e92367c4336524809c097742c03ed948860ca2f57d950ed5846", - "id": "fls_nordokzfy36d", - "link": "ownership-and-deconstruction.html#fls_nordokzfy36d", - "number": "15.4:4" + "checksum": "fa0a3046e6eb99fb80810bb3178f02f5e79ed989591df2df0d2770e7a189838e", + "id": "fls_ldr7tsuqw34s", + "link": "entities-and-resolution.html#fls_ldr7tsuqw34s", + "number": "14.7:23" }, { - "checksum": "3b7581ecddc33582b4cf58929333c27e97df213d56721343355ed7008eff2fa2", - "id": "fls_bjf3futso849", - "link": "ownership-and-deconstruction.html#fls_bjf3futso849", - "number": "15.4:5" + "checksum": "8cdb7a1fd01eee9c13a292e349acea9eb9ac949a2fa9265baef3c16015ddb27e", + "id": "fls_iNUBX5fJAI1N", + "link": "entities-and-resolution.html#fls_iNUBX5fJAI1N", + "number": "14.7:24" }, { - "checksum": "9d0d7ecaba0ada2895a59198323192c4c0562b596b94bea2c49be0968c22b934", - "id": "fls_yfmy4v5zlgw9", - "link": "ownership-and-deconstruction.html#fls_yfmy4v5zlgw9", - "number": "15.4:6" + "checksum": "12921489bca7899128033af3093a7ba86cffca5d616df55a4805a78bec860c60", + "id": "fls_RUiFQ17bmRLt", + "link": "entities-and-resolution.html#fls_RUiFQ17bmRLt", + "number": "14.7:25" }, { - "checksum": "20c3b044f90c2789882b18d6e87d0e3e641507a2d6af5c0de0ae5274181a7d06", - "id": "fls_jv18y618j2s3", - "link": "ownership-and-deconstruction.html#fls_jv18y618j2s3", - "number": "15.4:7" + "checksum": "923e223c194e910992be0f11cedf10480449de62681514efabd485fd092a163f", + "id": "fls_hv3xT2CjZuxc", + "link": "entities-and-resolution.html#fls_hv3xT2CjZuxc", + "number": "14.7:26" }, { - "checksum": "1acb26f55ea7c004ebe759dee3d3aa058f514f0dc242d2f980413f9e5269d8e9", - "id": "fls_g4i0jb27iryr", - "link": "ownership-and-deconstruction.html#fls_g4i0jb27iryr", - "number": "15.4:8" + "checksum": "7b947c9844d14caa57b996745fe9aba3027e04b17242edb5d6ba5c93d9247024", + "id": "fls_Pxc0Ts8Y7pfW", + "link": "entities-and-resolution.html#fls_Pxc0Ts8Y7pfW", + "number": "14.7:27" }, { - "checksum": "f6b207663eafd42b6f7e8445b48751f5e9f3ca1c278b649e94105c3b0c9d95cb", - "id": "fls_kky9ufexrvaw", - "link": "ownership-and-deconstruction.html#fls_kky9ufexrvaw", - "number": "15.4:9" + "checksum": "17b551577a4582157ce3c9a4dd5f1f653ac4f0fcca4d9b35db57fcb2edc04dda", + "id": "fls_wB3fVglLOqbZ", + "link": "entities-and-resolution.html#fls_wB3fVglLOqbZ", + "number": "14.7:28" }, { - "checksum": "cbca8f374f46d1ef54e4cf6beede54e85406b5090edddcbec4fdff0d1c91b6cf", - "id": "fls_o5oq4jfswr4q", - "link": "ownership-and-deconstruction.html#fls_o5oq4jfswr4q", - "number": "15.4:10" + "checksum": "96787ea2ff0300ae0e9ddc0818a3d47b177e6ce4c85c2c850a2784a954fa5c27", + "id": "fls_zmYSBW995kSN", + "link": "entities-and-resolution.html#fls_zmYSBW995kSN", + "number": "14.7:29" }, { - "checksum": "7f48612f428ed200a7e825eccbdeb3e446b9610271bd51fd922132fbe878f65c", - "id": "fls_MT69AGRXH9pS_", - "link": "ownership-and-deconstruction.html#fls_MT69AGRXH9pS_", - "number": "15.4:11" + "checksum": "74a4e0482ac24acf7980f76673590ee8dd4391bf49277003e96558de9179fbfe", + "id": "fls_5dlnffim6fso", + "link": "entities-and-resolution.html#fls_5dlnffim6fso", + "number": "14.7:30" }, { - "checksum": "958c02a18095324946b8dff0ea0a0e73b7cf7e255dffeaad921362798fa774a8", - "id": "fls_hyl4bdjbuzbw", - "link": "ownership-and-deconstruction.html#fls_hyl4bdjbuzbw", - "number": "15.4:12" + "checksum": "6a1fe59e480a0b1ad4b849816298fff68ba63985da52c560be04b860192302b0", + "id": "fls_9rhflreuubhq", + "link": "entities-and-resolution.html#fls_9rhflreuubhq", + "number": "14.7:31" }, { - "checksum": "72159700271717da9f616eff159a367b6f9b0637825319d7dec05001f401a601", - "id": "fls_pu19i4sj6yg0", - "link": "ownership-and-deconstruction.html#fls_pu19i4sj6yg0", - "number": "15.4:13" + "checksum": "450d1f7e6a218347a2975757a64915986a919f11db07da137f1b613c0651d553", + "id": "fls_s86dgrdpl1w4", + "link": "entities-and-resolution.html#fls_s86dgrdpl1w4", + "number": "14.7:32" + } + ], + "title": "Use Imports" + }, + { + "id": "fls_ydmnb7qnmzzq", + "informational": false, + "link": "entities-and-resolution.html#shadowing", + "number": "14.8", + "paragraphs": [ + { + "checksum": "eb93934c961caf16b01776dfdbf9b28bd8d888368b6ad7e96ed926a29f22bbf4", + "id": "fls_ob0riinmitkl", + "link": "entities-and-resolution.html#fls_ob0riinmitkl", + "number": "14.8:1" }, { - "checksum": "358d780a2a3e8a51b742e63c460cc75632190978e72b9815081a4a81aa80761e", - "id": "fls_kxws4zmaahj6", - "link": "ownership-and-deconstruction.html#fls_kxws4zmaahj6", - "number": "15.4:14" + "checksum": "6e3390d623ef6fb40f9e1687c6a8225b9eba4a6e72844ff3d2c96319a35975fc", + "id": "fls_fslg89a70e3n", + "link": "entities-and-resolution.html#fls_fslg89a70e3n", + "number": "14.8:2" }, { - "checksum": "d898ade965c0b2fab9cda5bc5233816f4058b485ac65840643912b375625ae78", - "id": "fls_kup2ou22nwyl", - "link": "ownership-and-deconstruction.html#fls_kup2ou22nwyl", - "number": "15.4:15" + "checksum": "db3d661af677fa5cf3df3f945d076f9f7636833ffe1dda2753c0bd7295c67912", + "id": "fls_hp3f4r3399kt", + "link": "entities-and-resolution.html#fls_hp3f4r3399kt", + "number": "14.8:3" }, { - "checksum": "cc4a5686c478a89047c0820552383801bd383016256838e3863695f994f8b314", - "id": "fls_8q5ly4x104ai", - "link": "ownership-and-deconstruction.html#fls_8q5ly4x104ai", - "number": "15.4:16" + "checksum": "418ed55705f495765526f09bf71b0e5b2725b130e6f0fce6aef783c6ebca4258", + "id": "fls_z8qjpskt13yq", + "link": "entities-and-resolution.html#fls_z8qjpskt13yq", + "number": "14.8:4" }, { - "checksum": "fb76d5f2ecb1ceb8b3aa709c4729fa2ce46d3bf98262aa34f8d39dcdabc2aa10", - "id": "fls_yhchu2bpil4m", - "link": "ownership-and-deconstruction.html#fls_yhchu2bpil4m", - "number": "15.4:17" + "checksum": "7bb5774d32bb848596abc22f98835526687c7290c92e9a0c480006e2ff78fcee", + "id": "fls_i0gp1y38lr73", + "link": "entities-and-resolution.html#fls_i0gp1y38lr73", + "number": "14.8:5" }, { - "checksum": "03dd78205c60d4db34d2c0df7ae70512ef3146fca9448710dd95263fcd3f6d2b", - "id": "fls_568o7nyihndd", - "link": "ownership-and-deconstruction.html#fls_568o7nyihndd", - "number": "15.4:18" + "checksum": "83eb26105dc85d77872ef84d2a9ee034d52e70b80e1aebf1653955eb3298caad", + "id": "fls_7pif12rt4s4s", + "link": "entities-and-resolution.html#fls_7pif12rt4s4s", + "number": "14.8:6" }, { - "checksum": "5812abb90fe17f8f0b9ec36241547534230a80cfd63db34d650ad71f90d18b2e", - "id": "fls_Z2n7EJiLY5CT", - "link": "ownership-and-deconstruction.html#fls_Z2n7EJiLY5CT", - "number": "15.4:19" + "checksum": "2b077c03fd28171c667fe2cf4b197a2f463d319e9fc967f4718fc7013e240522", + "id": "fls_are9qz67p7b6", + "link": "entities-and-resolution.html#fls_are9qz67p7b6", + "number": "14.8:7" }, { - "checksum": "f183cb055a44d89fb2eb1b2848aa4536e06650cf6344a523690b83a29d829f61", - "id": "fls_ibi9mqGeHNLp", - "link": "ownership-and-deconstruction.html#fls_ibi9mqGeHNLp", - "number": "15.4:20" - }, - { - "checksum": "fea10cfdc65f1e4d588fe2818f275ba47fdfcddd88877c316ca57791ba5de14f", - "id": "fls_f9we73i8vwq3", - "link": "ownership-and-deconstruction.html#fls_f9we73i8vwq3", - "number": "15.4:21" - }, - { - "checksum": "3e31ed577d258e6cdf238a0549751e8b896d9c2cb6ae51fc176c88803e14822f", - "id": "fls_w5bjgaov8w60", - "link": "ownership-and-deconstruction.html#fls_w5bjgaov8w60", - "number": "15.4:22" - }, - { - "checksum": "c316a6c432e9e7d33fde24e96df550a3e74333f7b65cd38e5a7f799997d5db11", - "id": "fls_gbqizu6gu6kk", - "link": "ownership-and-deconstruction.html#fls_gbqizu6gu6kk", - "number": "15.4:23" + "checksum": "c67d0f504519dd854e9530bae15b45de6b718e9b8c41f7cbf3e5178c96e8c04b", + "id": "fls_4tis5syofyg0", + "link": "entities-and-resolution.html#fls_4tis5syofyg0", + "number": "14.8:8" }, { - "checksum": "2a70cecfdc326c846957ef22ba6e0c76d8a4facbba9982e01c2fc53517b16b2b", - "id": "fls_ovkkxeybumvt", - "link": "ownership-and-deconstruction.html#fls_ovkkxeybumvt", - "number": "15.4:24" + "checksum": "77ce43e27ab83c36305d9a185a5ca04f7f8829673d2b5697f77fd89d032f6cbe", + "id": "fls_u0tsnkhacr06", + "link": "entities-and-resolution.html#fls_u0tsnkhacr06", + "number": "14.8:9" }, { - "checksum": "6a5074176f0372826a30df9614dbe4e54b3e06eab80a1ce84b7b9d7c94e3740d", - "id": "fls_7Kzu0qXiBUCb", - "link": "ownership-and-deconstruction.html#fls_7Kzu0qXiBUCb", - "number": "15.4:25" + "checksum": "28721531162ef5d6094a8ccb50a419eb494630777d043b260cc2e84dd9e65180", + "id": "fls_iaklf84guczc", + "link": "entities-and-resolution.html#fls_iaklf84guczc", + "number": "14.8:10" }, { - "checksum": "4478129ff7c47921151d2dcf57a362bba167ad848f1379667903982fe9676594", - "id": "fls_VDHBP4oAoqT5", - "link": "ownership-and-deconstruction.html#fls_VDHBP4oAoqT5", - "number": "15.4:26" + "checksum": "849224ffd513f258a847928a943322879b28ee64055165e1ea8d3bac7ae0e624", + "id": "fls_a0zovslu2v4u", + "link": "entities-and-resolution.html#fls_a0zovslu2v4u", + "number": "14.8:11" } ], - "title": "Borrowing" + "title": "Shadowing" }, { - "id": "fls_77scxuomlbgs", + "id": "fls_40xoego2thsp", "informational": false, - "link": "ownership-and-deconstruction.html#passing-conventions", - "number": "15.5", + "link": "entities-and-resolution.html#resolution", + "number": "14.9", "paragraphs": [ { - "checksum": "ff08808a01947415fb2eb38d55e453078c88c4c344cfdd584e2fcd09ce24ec29", - "id": "fls_fvwx2ufeyzcs", - "link": "ownership-and-deconstruction.html#fls_fvwx2ufeyzcs", - "number": "15.5:1" + "checksum": "9dc68fa012d432e6ce69d9945c40a38ec3fa96e38601d3106e03acfe663fdc95", + "id": "fls_ho4kem1slcxg", + "link": "entities-and-resolution.html#fls_ho4kem1slcxg", + "number": "14.9:1" }, { - "checksum": "aeefde9a0ac87118b21744a83a1a7e010b3cdd7a6d484f2225c2909cc9256ef6", - "id": "fls_h2pgsij1rbms", - "link": "ownership-and-deconstruction.html#fls_h2pgsij1rbms", - "number": "15.5:2" + "checksum": "66ebea6978cb1daf3e00dfeebbba4e819b907b02a40349e360a6b094881ff7ff", + "id": "fls_7le2vcdbtxbq", + "link": "entities-and-resolution.html#fls_7le2vcdbtxbq", + "number": "14.9:2" }, { - "checksum": "632e60b88c4c64ddd9b7398bf3476151bc5701ad1b4a760220667139cf7bb56b", - "id": "fls_yx2knbby70fy", - "link": "ownership-and-deconstruction.html#fls_yx2knbby70fy", - "number": "15.5:3" + "checksum": "f1ca8332615aeed822d795872f0809ed29a4e1d2160faa1ee7da8b3e1f6e6346", + "id": "fls_x3alg07yd7hx", + "link": "entities-and-resolution.html#fls_x3alg07yd7hx", + "number": "14.9:3" }, { - "checksum": "f8d36262285cdd2b4bef51b17a98103be0501e5e38a087145322a3fd9713f59c", - "id": "fls_6ul3f6v0foma", - "link": "ownership-and-deconstruction.html#fls_6ul3f6v0foma", - "number": "15.5:4" + "checksum": "1697af6564bdffc62e51d3c846e72831c1c12e73fe2790f11c7a21ebfaa07a71", + "id": "fls_4hulwazdu20i", + "link": "entities-and-resolution.html#fls_4hulwazdu20i", + "number": "14.9:4" }, { - "checksum": "1f5f58ea8bff9c3f06d08ec8804d370315b1c1190ffc8f6354263a3d92fe64a7", - "id": "fls_3ztdz02efeoc", - "link": "ownership-and-deconstruction.html#fls_3ztdz02efeoc", - "number": "15.5:5" + "checksum": "f79f3efcbf1195ffe54c0a60acbf4bf5874f53ef8c0b36f0851ce886a40598d3", + "id": "fls_ptocwx5p25lj", + "link": "entities-and-resolution.html#fls_ptocwx5p25lj", + "number": "14.9:5" }, { - "checksum": "e1dedaef11fca8fa7e09c7906b87ca804da421ff7b5ada8a981d024d16ae5c6f", - "id": "fls_konzgoybhfqm", - "link": "ownership-and-deconstruction.html#fls_konzgoybhfqm", - "number": "15.5:6" + "checksum": "a131476f47296126bd7d484e19183efe874d331eb45abb3ea465295d1c0645ca", + "id": "fls_ygam5nisv98c", + "link": "entities-and-resolution.html#fls_ygam5nisv98c", + "number": "14.9:6" + } + ], + "title": "Resolution" + }, + { + "id": "fls_xcwfotmq2e5d", + "informational": false, + "link": "entities-and-resolution.html#field-resolution", + "number": "14.9.1", + "paragraphs": [ + { + "checksum": "aaf0db1f36f8f15b96618b5a70cec4a15e5d625748a8590acdfb9e046f222dd1", + "id": "fls_1nxknwjdp0am", + "link": "entities-and-resolution.html#fls_1nxknwjdp0am", + "number": "14.9.1:1" }, { - "checksum": "e84118fd70dabb3e58425bef7f6dff825e6a847042faa9129285c5a1ed911a36", - "id": "fls_4bnbv7mqod57", - "link": "ownership-and-deconstruction.html#fls_4bnbv7mqod57", - "number": "15.5:7" + "checksum": "66cc3c2c1a5ec6dd71396911c8316e01e72ca51ce4bc489387e1e7b0b3371827", + "id": "fls_j1bip4w30q8", + "link": "entities-and-resolution.html#fls_j1bip4w30q8", + "number": "14.9.1:2" }, { - "checksum": "97a3445c9540e7ad4cee9865d34bc4dd2fa75219dffd54e92dded06b85c59541", - "id": "fls_3xk3p1unbjy5", - "link": "ownership-and-deconstruction.html#fls_3xk3p1unbjy5", - "number": "15.5:8" + "checksum": "19df557d0cd32382f3a39cabc59987441fef33fd672cfe083367c1432ed20de2", + "id": "fls_jrk3gzqvqr8e", + "link": "entities-and-resolution.html#fls_jrk3gzqvqr8e", + "number": "14.9.1:3" }, { - "checksum": "8c23071abb8829ee058cb57ae16a5f81b804ff2c1d55f7ec813bdf648a0e5ae8", - "id": "fls_vk1xhvdaakh0", - "link": "ownership-and-deconstruction.html#fls_vk1xhvdaakh0", - "number": "15.5:9" + "checksum": "8a8e13fcffd5809ce5f90c8ea9cefec9fa5ca58b1bea8566b16d1fc9a6040f1d", + "id": "fls_asn20qx16sr6", + "link": "entities-and-resolution.html#fls_asn20qx16sr6", + "number": "14.9.1:4" }, { - "checksum": "5fc750214c56a9e1435062359732e69a801b4413b5f235c73614e41074c91c13", - "id": "fls_vveEJn7lngT8", - "link": "ownership-and-deconstruction.html#fls_vveEJn7lngT8", - "number": "15.5:10" + "checksum": "f7516801073f4902b37f8c6a2860b920595b14089f145b432e64e340de0fca99", + "id": "fls_jzoon4x89zp7", + "link": "entities-and-resolution.html#fls_jzoon4x89zp7", + "number": "14.9.1:5" }, { - "checksum": "ce6808e7d0727241eb783b7bdfb02998d6e5865d3d0d97dee49053512f5a3943", - "id": "fls_gq35gqagw35", - "link": "ownership-and-deconstruction.html#fls_gq35gqagw35", - "number": "15.5:11" + "checksum": "7a2ade6ee042d2fa1254f9e3a047fe526e3618d2f949b87f34a08121a165eb4f", + "id": "fls_r80pixfoe5hk", + "link": "entities-and-resolution.html#fls_r80pixfoe5hk", + "number": "14.9.1:6" }, { - "checksum": "994382b24fcb146882e480cba7da6ddcbbb55a473b76161b1167a520daba8fde", - "id": "fls_cfqzpmnfeh8h", - "link": "ownership-and-deconstruction.html#fls_cfqzpmnfeh8h", - "number": "15.5:12" + "checksum": "3c9f984a5a8cf97fecfc89b84a15e8ae272e62c460b0988610134cebefe43a63", + "id": "fls_40oa0j6aiop3", + "link": "entities-and-resolution.html#fls_40oa0j6aiop3", + "number": "14.9.1:7" }, { - "checksum": "754c9ebda067292c99d20bc43c433c797e386d1c651bf1cd0d37d42596ad9b86", - "id": "fls_go9gdlk5d3km", - "link": "ownership-and-deconstruction.html#fls_go9gdlk5d3km", - "number": "15.5:13" + "checksum": "9f3f6ee5e9400959e3a4445be267664e3ccdec6bf0887f243d3696b27b6c2ac2", + "id": "fls_2bp1zs7qaz7o", + "link": "entities-and-resolution.html#fls_2bp1zs7qaz7o", + "number": "14.9.1:8" }, { - "checksum": "1f27a382873eeb1cbf8a680f1f4445841e31a6ca70791b7e72d1f0064c2e99e4", - "id": "fls_459xx6febmf0", - "link": "ownership-and-deconstruction.html#fls_459xx6febmf0", - "number": "15.5:14" + "checksum": "1245cb0d297861a8c0266d9628d353be5f835d30038c5d29c99d6ebd0af2a540", + "id": "fls_s14fegwhwnc8", + "link": "entities-and-resolution.html#fls_s14fegwhwnc8", + "number": "14.9.1:9" }, { - "checksum": "794dbaeb036da65d453284a372c0a58b55b03099b8433c2c1acea02cf3ebfd44", - "id": "fls_3xyq50abdiv6", - "link": "ownership-and-deconstruction.html#fls_3xyq50abdiv6", - "number": "15.5:15" + "checksum": "1fa259887a9405988fa7b3381ca5a486089f705f216bc750d058d170e8641a6d", + "id": "fls_tfjm27ydiake", + "link": "entities-and-resolution.html#fls_tfjm27ydiake", + "number": "14.9.1:10" }, { - "checksum": "2f24b0e12326e4ad6c1cff5ea009d0cc6878e98b4f8f16fd738a89a432461c62", - "id": "fls_7kcx3u8gvl1d", - "link": "ownership-and-deconstruction.html#fls_7kcx3u8gvl1d", - "number": "15.5:16" + "checksum": "0ac084f03e5a81020440616fa419fc5d5b6861d4a201ef74c1be61813fdeca03", + "id": "fls_p6hgoqo0kcx", + "link": "entities-and-resolution.html#fls_p6hgoqo0kcx", + "number": "14.9.1:11" }, { - "checksum": "45a7939e44809197b647eab86a5bbb791bbd9b7b492d99ff134fd6b1b35bd6f6", - "id": "fls_i4hrifsb9msr", - "link": "ownership-and-deconstruction.html#fls_i4hrifsb9msr", - "number": "15.5:17" + "checksum": "4e24dc92aa06d5b4ad0e1df6508c329176170f30e1a94c6692df3e2e12215796", + "id": "fls_e7sj392ohvbd", + "link": "entities-and-resolution.html#fls_e7sj392ohvbd", + "number": "14.9.1:12" }, { - "checksum": "242ee9281937fc599be5d057484f99ff16cac54d4dedfc9817b957bbec3eac6c", - "id": "fls_7tadh1zel0fc", - "link": "ownership-and-deconstruction.html#fls_7tadh1zel0fc", - "number": "15.5:18" + "checksum": "07f1cdb38f9bd8c127759860f4a51b104e71f573c2880fceec9bb97d393d32aa", + "id": "fls_z6qt9obbhhcg", + "link": "entities-and-resolution.html#fls_z6qt9obbhhcg", + "number": "14.9.1:13" }, { - "checksum": "162688f5929bce2f33343b1dcf6263ceb3ca10d03307199ad9ab3e4ec746aaf9", - "id": "fls_ywt328hcieka", - "link": "ownership-and-deconstruction.html#fls_ywt328hcieka", - "number": "15.5:19" - } - ], - "title": "Passing Conventions" - }, - { - "id": "fls_4jiw35pan7vn", - "informational": false, - "link": "ownership-and-deconstruction.html#destruction", - "number": "15.6", - "paragraphs": [ + "checksum": "c7857117b11bfd871d7e3b709cd608d66f29d41ebccc028b2b47eb0a71ae7fd5", + "id": "fls_ljnjxex3u5o", + "link": "entities-and-resolution.html#fls_ljnjxex3u5o", + "number": "14.9.1:14" + }, { - "checksum": "b715c62ba79445fc86b207de20ae057aeb387fa46fc4a9f8b5d99105c88061e7", - "id": "fls_e7ucq87s806d", - "link": "ownership-and-deconstruction.html#fls_e7ucq87s806d", - "number": "15.6:1" + "checksum": "bb0494028f9d19ce98975cf71392baf6f4b01e861bd44b13c4eec3780403a9fa", + "id": "fls_nm06mru40tyg", + "link": "entities-and-resolution.html#fls_nm06mru40tyg", + "number": "14.9.1:15" } ], - "title": "Destruction" + "title": "Field Resolution" }, { - "id": "fls_u2mzjgiwbkz0", + "id": "fls_wqazkzle0ix9", "informational": false, - "link": "ownership-and-deconstruction.html#destructors", - "number": "15.7", + "link": "entities-and-resolution.html#method-resolution", + "number": "14.9.2", "paragraphs": [ { - "checksum": "58a815029e5c5abf31dbdc0ae371a884c35141c19fa227ecd118d8ec5c7977c3", - "id": "fls_9m0gszdle0qb", - "link": "ownership-and-deconstruction.html#fls_9m0gszdle0qb", - "number": "15.7:1" - }, - { - "checksum": "99e01849a304562c74b7705792700bbb8f792f332352a99fbdf6a23f3f2749a0", - "id": "fls_4nkzidytpi6", - "link": "ownership-and-deconstruction.html#fls_4nkzidytpi6", - "number": "15.7:2" - }, - { - "checksum": "780a665cd5b58bccb729342e33dea24cd6f074a7109cee63ca9412cf67d69b2c", - "id": "fls_wzuwapjqtyyy", - "link": "ownership-and-deconstruction.html#fls_wzuwapjqtyyy", - "number": "15.7:3" - }, - { - "checksum": "25fab97c21fc263acb73e4f9c015d9ebb7c96ec0f09fdfa35e4550aed472dde1", - "id": "fls_gfvm70iqu1l4", - "link": "ownership-and-deconstruction.html#fls_gfvm70iqu1l4", - "number": "15.7:4" - }, - { - "checksum": "e33f94694a42a7f211aaec4bd688778421eba9168af369f075a51652a55c55d8", - "id": "fls_l2xkdjeydqtx", - "link": "ownership-and-deconstruction.html#fls_l2xkdjeydqtx", - "number": "15.7:5" + "checksum": "506663a2566f85e068c82d0103f1ca78bd821cb68b77fbdbcd52c1072331ad09", + "id": "fls_e5a5z5yht26l", + "link": "entities-and-resolution.html#fls_e5a5z5yht26l", + "number": "14.9.2:1" }, { - "checksum": "b88a85aa7e4b1891ac3ed4ca23a4b92b4e035715e966e1f848f1e21427d23845", - "id": "fls_bync24y6gp93", - "link": "ownership-and-deconstruction.html#fls_bync24y6gp93", - "number": "15.7:6" + "checksum": "29e3e853007709641b0c2182f3e9bc70020808d3869acd0dcfa131141a8c00a0", + "id": "fls_mbdS0xiNlj92", + "link": "entities-and-resolution.html#fls_mbdS0xiNlj92", + "number": "14.9.2:2" }, { - "checksum": "ce0fe2c8975789b3ccac42978e94c8cf22385f6f021e809465af734f19bbe334", - "id": "fls_jzancf72i95f", - "link": "ownership-and-deconstruction.html#fls_jzancf72i95f", - "number": "15.7:7" + "checksum": "c89b01c453ba585643631dc3b2963abc8b1e4c53933b3721b3dfc7101393d2d2", + "id": "fls_z80ylmlu1f3q", + "link": "entities-and-resolution.html#fls_z80ylmlu1f3q", + "number": "14.9.2:3" }, { - "checksum": "4695a93955bba74f12080fa0db5a0ad04dfa0e9127756fcc2b3dead95ad24f77", - "id": "fls_gjn2jnsal9gs", - "link": "ownership-and-deconstruction.html#fls_gjn2jnsal9gs", - "number": "15.7:8" + "checksum": "0cf16c00febccc266cbb1905aa697021947ef02980576e67df40efe5cf32c0a4", + "id": "fls_e1029pvq706h", + "link": "entities-and-resolution.html#fls_e1029pvq706h", + "number": "14.9.2:4" }, { - "checksum": "27ce20568cf31821f40322d7cbca37c6e7f9a154497206aa2cd66da3c71e14fe", - "id": "fls_ol2w2292frfi", - "link": "ownership-and-deconstruction.html#fls_ol2w2292frfi", - "number": "15.7:9" + "checksum": "7d99ef0d1b75056141e26deea64ad1a7c9653284138e25b5fdad0f5c30a465b0", + "id": "fls_w3ik83d43fr1", + "link": "entities-and-resolution.html#fls_w3ik83d43fr1", + "number": "14.9.2:5" }, { - "checksum": "3a5360261dcb8e8292661839009fff3c71ef4a3d1c9f45a2d85f861f4b707310", - "id": "fls_6ii5o68vuymj", - "link": "ownership-and-deconstruction.html#fls_6ii5o68vuymj", - "number": "15.7:10" + "checksum": "8e912acb711b0d365e2b30de2a8c67f34bfc9132756c0ce068ee0cccafb9d8d4", + "id": "fls_pybv4krsvktv", + "link": "entities-and-resolution.html#fls_pybv4krsvktv", + "number": "14.9.2:6" }, { - "checksum": "2b1fcd39b5b78562ea6e359c8e98307553d5ffc8a1c9188166ffd9fc296d0a8d", - "id": "fls_sup43es8ps8r", - "link": "ownership-and-deconstruction.html#fls_sup43es8ps8r", - "number": "15.7:11" + "checksum": "2f5e3e2a6ede7f8249c7456439a30d94da8d1baf1c53135ae9fbdde73ae46077", + "id": "fls_m2njj6no0p1i", + "link": "entities-and-resolution.html#fls_m2njj6no0p1i", + "number": "14.9.2:7" }, { - "checksum": "622d773bd119d86de850502b8e92eee57b581476e14eff7478ba5d558629c86f", - "id": "fls_y9q0eqr865b3", - "link": "ownership-and-deconstruction.html#fls_y9q0eqr865b3", - "number": "15.7:12" + "checksum": "72843d941971110fefb9a9b6995ef90cd5b564d6639651630b631e41ee4e491e", + "id": "fls_16l2q1wpcnbp", + "link": "entities-and-resolution.html#fls_16l2q1wpcnbp", + "number": "14.9.2:8" }, { - "checksum": "3df5eb307218abb09b95ebf9bab92992011b11bb1c5d1e246c3160e5a71afd94", - "id": "fls_kdqng6eovxns", - "link": "ownership-and-deconstruction.html#fls_kdqng6eovxns", - "number": "15.7:13" + "checksum": "6d27f6b19310c77c713cea4e5c7bb344db8cbb3fdf479386c7233e155236809f", + "id": "fls_fcnahkqxomuo", + "link": "entities-and-resolution.html#fls_fcnahkqxomuo", + "number": "14.9.2:9" }, { - "checksum": "a023dc4d0c98b62c2d9576d70e51108a0fdb29ba3f14769c7d87ab09d1f0938a", - "id": "fls_ag249y74jg6c", - "link": "ownership-and-deconstruction.html#fls_ag249y74jg6c", - "number": "15.7:14" + "checksum": "cb39263ec9ad7cd027df0b037770018592ea1b93c52e02c1e911048464995f86", + "id": "fls_ii0fdpekn1qt", + "link": "entities-and-resolution.html#fls_ii0fdpekn1qt", + "number": "14.9.2:10" }, { - "checksum": "69b3fa403d23f715b291788890e24ebd09e6b009e67a21e5edf9c25f29e037eb", - "id": "fls_tw36n3g32a0y", - "link": "ownership-and-deconstruction.html#fls_tw36n3g32a0y", - "number": "15.7:15" + "checksum": "e7745cebb4a1cfb9d08a5ec1b5827d5cb745348158eb1ab0b3677d950118e4c7", + "id": "fls_ohjmxhbw3nx3", + "link": "entities-and-resolution.html#fls_ohjmxhbw3nx3", + "number": "14.9.2:11" }, { - "checksum": "9a3ba3981279a98242813d19ecc21dcce387bb841f1be9398442ba10e7d9addd", - "id": "fls_fmn33zhorkf", - "link": "ownership-and-deconstruction.html#fls_fmn33zhorkf", - "number": "15.7:16" - } - ], - "title": "Destructors" - }, - { - "id": "fls_rm4ncoopcdvj", - "informational": false, - "link": "ownership-and-deconstruction.html#drop-scopes", - "number": "15.8", - "paragraphs": [ - { - "checksum": "fa365174cbf5682b2addb5a47523a886be22f15c45e88cb5d01ef13a92c0d9b2", - "id": "fls_7uav7vkcv4pz", - "link": "ownership-and-deconstruction.html#fls_7uav7vkcv4pz", - "number": "15.8:1" + "checksum": "d9a23a581045be380483ef64273fb0970eb3a080cffb32f83f6ff8e0922acd5c", + "id": "fls_lgpdicxxwq13", + "link": "entities-and-resolution.html#fls_lgpdicxxwq13", + "number": "14.9.2:12" }, { - "checksum": "7fcf965dcbfb27fdef8959d59a625da3fa44a0d931095edf51cd4b5db75eb01c", - "id": "fls_txvxrn6wbyql", - "link": "ownership-and-deconstruction.html#fls_txvxrn6wbyql", - "number": "15.8:2" + "checksum": "d684bf9d23507a618c9721f7ea2ec85c2736c06dbee658fa864d22a5e32c5d44", + "id": "fls_ugl3x4y3lli2", + "link": "entities-and-resolution.html#fls_ugl3x4y3lli2", + "number": "14.9.2:13" }, { - "checksum": "aa4f1d2f9b7ef3586caec5b558b58e35cb2b8cd615ba21adcf9234ba7283bec9", - "id": "fls_n6y6brm6pghr", - "link": "ownership-and-deconstruction.html#fls_n6y6brm6pghr", - "number": "15.8:3" + "checksum": "8e4b764f8f8a09f136954a2bfc947ddc4eda420f1148fad92fc5fa7a24c12c13", + "id": "fls_bb4cbmvui8fk", + "link": "entities-and-resolution.html#fls_bb4cbmvui8fk", + "number": "14.9.2:14" }, { - "checksum": "557bd0675e3046e13b7a004ef78b8b0f946bcca78bc242cb76364e3f991f1d3a", - "id": "fls_gdh6wwvi7ci6", - "link": "ownership-and-deconstruction.html#fls_gdh6wwvi7ci6", - "number": "15.8:4" + "checksum": "66ebe678e1ae9102e4d8521706cef63daeae10b848e263ab3766e4aa6cc9a1d9", + "id": "fls_5wny1yxbyuz0", + "link": "entities-and-resolution.html#fls_5wny1yxbyuz0", + "number": "14.9.2:15" }, { - "checksum": "89634ecd6c544d1fbf8a649d790b5173d2a09803e97525c89f9d827df73ab265", - "id": "fls_owqk2fcpvc4s", - "link": "ownership-and-deconstruction.html#fls_owqk2fcpvc4s", - "number": "15.8:5" + "checksum": "17c39504c2086447190664d68d7a3eae124510357ebc7f144f3d1b25f7026518", + "id": "fls_gsc8pt4tlsqv", + "link": "entities-and-resolution.html#fls_gsc8pt4tlsqv", + "number": "14.9.2:16" }, { - "checksum": "62c1b4c8906b7867a552ab0c166e92d1673c4e6c996c6e82c55fb18155b337a5", - "id": "fls_ckh8wkq0y5ja", - "link": "ownership-and-deconstruction.html#fls_ckh8wkq0y5ja", - "number": "15.8:6" + "checksum": "9aed381332445df712568775e61af445c35e6c942e4722e7a4d38140ff9b3fa3", + "id": "fls_tfglce1wuq5q", + "link": "entities-and-resolution.html#fls_tfglce1wuq5q", + "number": "14.9.2:17" }, { - "checksum": "e18cce725d7e3e2f64db15d95adeb42a7a9e3d806e422bcad663e4734ed8ebab", - "id": "fls_2zwwnzepgmje", - "link": "ownership-and-deconstruction.html#fls_2zwwnzepgmje", - "number": "15.8:7" + "checksum": "04832d6c14ff2380c1a19024f980142d165adb58520a40c0c8af80ffe1fb55c8", + "id": "fls_64bfcn9okeve", + "link": "entities-and-resolution.html#fls_64bfcn9okeve", + "number": "14.9.2:18" }, { - "checksum": "06a9f847c5fd97acf184ee400bbbe296accf5d480e6cef5d1766f5fef9270c17", - "id": "fls_vlbx5ukw5c8l", - "link": "ownership-and-deconstruction.html#fls_vlbx5ukw5c8l", - "number": "15.8:8" + "checksum": "3700640ef18118308cf59e04636cc5b6f1c574e9b6d802e29ef33f0d5814bf71", + "id": "fls_om90v9re8b2l", + "link": "entities-and-resolution.html#fls_om90v9re8b2l", + "number": "14.9.2:19" }, { - "checksum": "7478e15bd141efde6f849fddd3e660a3198328261280e11802f707445106483f", - "id": "fls_d5yg6w8gv6aq", - "link": "ownership-and-deconstruction.html#fls_d5yg6w8gv6aq", - "number": "15.8:9" + "checksum": "f01b3809e31f3e711b3d853cd5dc8d1acdc431c60f9d0c04452abeccb6190de6", + "id": "fls_bsf4hy9x7c2e", + "link": "entities-and-resolution.html#fls_bsf4hy9x7c2e", + "number": "14.9.2:20" }, { - "checksum": "2210651d12709f733124d5831bdb32f27f192f93fa3be518eb6b7334a6531966", - "id": "fls_qidma4fpkhb0", - "link": "ownership-and-deconstruction.html#fls_qidma4fpkhb0", - "number": "15.8:10" + "checksum": "e610b83f08f3c1ffeb1693c97a13b45472e77067e9caff1501681e6e4ff7633c", + "id": "fls_cnn5hkf1z5q4", + "link": "entities-and-resolution.html#fls_cnn5hkf1z5q4", + "number": "14.9.2:21" }, { - "checksum": "04f68fcba1cc880a1b5acaefe6abf4f93cac7e0150747463062aef3aecedc14f", - "id": "fls_1o9ye6cwoyiq", - "link": "ownership-and-deconstruction.html#fls_1o9ye6cwoyiq", - "number": "15.8:11" + "checksum": "4380ca472e236c4d884dc55c2ab8da71766dbea2d3cc0895b010c2c48c9df55b", + "id": "fls_j9ho6xc2fj0w", + "link": "entities-and-resolution.html#fls_j9ho6xc2fj0w", + "number": "14.9.2:22" }, { - "checksum": "aa403db4f036453bba3271f4509a2302488c5fd4205bfdc4f54cde71db726013", - "id": "fls_16htxf824xbk", - "link": "ownership-and-deconstruction.html#fls_16htxf824xbk", - "number": "15.8:12" + "checksum": "2cd76e3de2de59d6830b6ea7fef86225bf36e7cfcecb6ebec75030fcfb2efc73", + "id": "fls_1y94elgpg0uk", + "link": "entities-and-resolution.html#fls_1y94elgpg0uk", + "number": "14.9.2:23" }, { - "checksum": "070c24d985ad07d4a8affc27d166c94f9beaee7f6c83f82c5249ef04eb845c8a", - "id": "fls_lbsfhg42yiqy", - "link": "ownership-and-deconstruction.html#fls_lbsfhg42yiqy", - "number": "15.8:13" + "checksum": "4b79cb54112a7172b9965ddcc0ee752f7f3490dd18a445e79bd25bddb06908c0", + "id": "fls_npsdxrtcslcf", + "link": "entities-and-resolution.html#fls_npsdxrtcslcf", + "number": "14.9.2:24" }, { - "checksum": "3f43cdcc50a1a53af990b4beeb71262819034dd32a82fcb54484c052b19b44c6", - "id": "fls_5m3u3k6f00bd", - "link": "ownership-and-deconstruction.html#fls_5m3u3k6f00bd", - "number": "15.8:14" + "checksum": "5f2072da8fa1e8d184be3da512c28aed044a859e8fcd3ffe93c952419a84e822", + "id": "fls_yv5l823lwdsv", + "link": "entities-and-resolution.html#fls_yv5l823lwdsv", + "number": "14.9.2:25" }, { - "checksum": "38a763a575ed36ac0f33c0e4c97708f46f5634a6bfca712737225d732788d27f", - "id": "fls_m86ljncnmo7j", - "link": "ownership-and-deconstruction.html#fls_m86ljncnmo7j", - "number": "15.8:15" + "checksum": "c3218b553ed9d95d1235f5fc0fd5281890f0bf12ace5c8649ae55530225d53e1", + "id": "fls_ckdoyvbaybe0", + "link": "entities-and-resolution.html#fls_ckdoyvbaybe0", + "number": "14.9.2:26" }, { - "checksum": "05382bcb6a5dca3a3b33debf11b819bbb3b2b29f4559e6beeaad692d688c87c8", - "id": "fls_bewcu5xceu8i", - "link": "ownership-and-deconstruction.html#fls_bewcu5xceu8i", - "number": "15.8:16" + "checksum": "64374639950e73d231a40ccb24e159d9cd1d7dd8e03cfab109bd17a57ea06805", + "id": "fls_1azkiu20r0e4", + "link": "entities-and-resolution.html#fls_1azkiu20r0e4", + "number": "14.9.2:27" }, { - "checksum": "c38a81f8df72f87ccafba9e41d742d2b2de00acfe77e463a03ec5d0e96082258", - "id": "fls_iHHhlxCiqNWZ", - "link": "ownership-and-deconstruction.html#fls_iHHhlxCiqNWZ", - "number": "15.8:17" + "checksum": "63fa9c1342877193573e6c29b715d5deac6bfb9eb8ea49fa19c104c6063e6b4c", + "id": "fls_ose5m4bhkg57", + "link": "entities-and-resolution.html#fls_ose5m4bhkg57", + "number": "14.9.2:28" }, { - "checksum": "08316305ae1b30302790dfc45803df2f527f449747eb34b2a91b407beec878b4", - "id": "fls_swXXkbZGLPKa", - "link": "ownership-and-deconstruction.html#fls_swXXkbZGLPKa", - "number": "15.8:18" - }, + "checksum": "9977dc1614c53e251615c07e9e08e617d47505e3cdbc9b1f313d120c80533919", + "id": "fls_jw2yv23cduu4", + "link": "entities-and-resolution.html#fls_jw2yv23cduu4", + "number": "14.9.2:29" + } + ], + "title": "Method Resolution" + }, + { + "id": "fls_telbknkodx3d", + "informational": false, + "link": "entities-and-resolution.html#call-resolution", + "number": "14.9.3", + "paragraphs": [ { - "checksum": "770bb15fa73a6c965d591c00604c440c7de7c3e3257c6f0946cf7be4e1cb394b", - "id": "fls_vrqgac634wpr", - "link": "ownership-and-deconstruction.html#fls_vrqgac634wpr", - "number": "15.8:19" + "checksum": "e00de8ccb48972edb964b7efe2cdb1840c249fb526d14c3331b293a3cf8cf79d", + "id": "fls_ZjJ7y9r6QQMW", + "link": "entities-and-resolution.html#fls_ZjJ7y9r6QQMW", + "number": "14.9.3:1" }, { - "checksum": "a35aa796d1ab80a5df343d39e3e12923a8a46fd0df82325bf334669986e7d03a", - "id": "fls_fnvr5w2wzxns", - "link": "ownership-and-deconstruction.html#fls_fnvr5w2wzxns", - "number": "15.8:20" + "checksum": "18e91497757f3ccef6d3e37f0ab6fa99650ef04419b74aa5cb0faaf8d646c8c1", + "id": "fls_zBSloU2Gjv7x", + "link": "entities-and-resolution.html#fls_zBSloU2Gjv7x", + "number": "14.9.3:2" }, { - "checksum": "67a942cc52a66f9f90a3a9131beae16808a17f3f4d7d49fdb08ff42f734ce302", - "id": "fls_1vZFa5mDEbXW", - "link": "ownership-and-deconstruction.html#fls_1vZFa5mDEbXW", - "number": "15.8:21" + "checksum": "d569ecf4ccc75ebed4ac9a1fd2482c4db16dcb459e321909013b8a42be1b4bf4", + "id": "fls_XBakDylF7aOG", + "link": "entities-and-resolution.html#fls_XBakDylF7aOG", + "number": "14.9.3:3" }, { - "checksum": "1dec6a051b1fff3a1bdeae59f4b0c13eae973e22d62993017cf9176ae6c41ef8", - "id": "fls_3jtU8grRaMM9", - "link": "ownership-and-deconstruction.html#fls_3jtU8grRaMM9", - "number": "15.8:22" + "checksum": "de3fc414790cfa7bddbc44846eff2f3816840e6b0249dd0e743b4ee447d3a909", + "id": "fls_kzovwqIw7ghc", + "link": "entities-and-resolution.html#fls_kzovwqIw7ghc", + "number": "14.9.3:4" }, { - "checksum": "92013afe5f273d886cf33357afa293c5145b38dd0ea03a2758611998c0ac0b32", - "id": "fls_8r39duatupxw", - "link": "ownership-and-deconstruction.html#fls_8r39duatupxw", - "number": "15.8:23" + "checksum": "f9822b671141f854469f2aa328c18a596471e264a51e4f3c1630f47b3b828d76", + "id": "fls_4iBdtE0Ji4Tf", + "link": "entities-and-resolution.html#fls_4iBdtE0Ji4Tf", + "number": "14.9.3:5" }, { - "checksum": "c3e4aa156bda861e6ef22a6fa15d0e213a08ac8a395097b3bae8fb744bcac564", - "id": "fls_2peq1tihglnr", - "link": "ownership-and-deconstruction.html#fls_2peq1tihglnr", - "number": "15.8:24" - }, + "checksum": "c7df7b2b6d7b1fbd91b08418341411ea2e0a68ee04bf8ab36bcf50f60d63f1b8", + "id": "fls_TWR5GIUxPcaJ", + "link": "entities-and-resolution.html#fls_TWR5GIUxPcaJ", + "number": "14.9.3:6" + } + ], + "title": "Call Resolution" + }, + { + "id": "fls_i6qzga6dyaee", + "informational": false, + "link": "entities-and-resolution.html#path-resolution", + "number": "14.9.4", + "paragraphs": [ { - "checksum": "1f13a26446ed0aea0e199436fd0adb97cab249490f3e88c061ca7f09229125e7", - "id": "fls_il09n7sq0a3k", - "link": "ownership-and-deconstruction.html#fls_il09n7sq0a3k", - "number": "15.8:25" + "checksum": "55361e7d41c5c24510d526001510c8bad4aef48dbd51f81ef0d1fe0e7aa4128c", + "id": "fls_8slvisr3jfja", + "link": "entities-and-resolution.html#fls_8slvisr3jfja", + "number": "14.9.4:1" }, { - "checksum": "942aadd6edb16dc7e80b6f2cd3392f324ea9d00f9e61794c8fa5ec6b65b30d82", - "id": "fls_s1wbqld139gz", - "link": "ownership-and-deconstruction.html#fls_s1wbqld139gz", - "number": "15.8:26" + "checksum": "8cd576cd86befd359dfe276c5f05be757f54ff4b4b44768f9a4b71ec18287156", + "id": "fls_nmev0tnzgw35", + "link": "entities-and-resolution.html#fls_nmev0tnzgw35", + "number": "14.9.4:2" }, { - "checksum": "750908f479d13e69946a538f5a81e56c3d71d08e5c2493c6129c77568fe3de66", - "id": "fls_asvuef2pc3m0", - "link": "ownership-and-deconstruction.html#fls_asvuef2pc3m0", - "number": "15.8:27" + "checksum": "a8a99061757e28c4776e91bccebf019763e6c7876ea81a92d9a1c528165aaf18", + "id": "fls_p23q1ob2qitz", + "link": "entities-and-resolution.html#fls_p23q1ob2qitz", + "number": "14.9.4:3" }, { - "checksum": "690427f07bce841d8ea70b8cb0864491ae88e1b7d4cfcf9ca2fb45657c8c0d87", - "id": "fls_560437qmeqtr", - "link": "ownership-and-deconstruction.html#fls_560437qmeqtr", - "number": "15.8:28" + "checksum": "537b298a9d54dde06ac687138d86b76d85a21e75ce6b341493c8b394b5239199", + "id": "fls_e9rv8dfa0arl", + "link": "entities-and-resolution.html#fls_e9rv8dfa0arl", + "number": "14.9.4:4" }, { - "checksum": "96c9a3b88a3cf78a0260b534ac8bac2c2676692e6504f28abb95658a7c723ea2", - "id": "fls_8cunkfc6x24q", - "link": "ownership-and-deconstruction.html#fls_8cunkfc6x24q", - "number": "15.8:29" + "checksum": "48938e27d06bad943ff87be97fcc21540da3b77868b16a71eb117137d09d23fc", + "id": "fls_yule33qm1ok", + "link": "entities-and-resolution.html#fls_yule33qm1ok", + "number": "14.9.4:5" }, { - "checksum": "41cab1feee08b6f4c71f9ce9cb18a539787287a0d87320b8477673fd090b1221", - "id": "fls_n108lvc4otoc", - "link": "ownership-and-deconstruction.html#fls_n108lvc4otoc", - "number": "15.8:30" + "checksum": "725b600fb8ecdc9eddfb8e2568ae2102893884fcb7c6aa4e1963c948b242dabe", + "id": "fls_7xmhm2lf2h8f", + "link": "entities-and-resolution.html#fls_7xmhm2lf2h8f", + "number": "14.9.4:6" }, { - "checksum": "d23e4c8eeb0eed8bb71e11637e19abe752abf0f59454434c187efb281833b269", - "id": "fls_ptk6yibqyfzi", - "link": "ownership-and-deconstruction.html#fls_ptk6yibqyfzi", - "number": "15.8:31" + "checksum": "d67675a77fe3ac8c7c6a93c703b80fb47b1307005a8bca11ae42fe53388b0e18", + "id": "fls_ec4wo8odusqp", + "link": "entities-and-resolution.html#fls_ec4wo8odusqp", + "number": "14.9.4:7" }, { - "checksum": "571f4508156e008bb63439e1b7abe647297d7fbafe515b8c2a3a24456a52e99b", - "id": "fls_dltmd8e8c5ia", - "link": "ownership-and-deconstruction.html#fls_dltmd8e8c5ia", - "number": "15.8:32" - } - ], - "title": "Drop Scopes" - }, - { - "id": "fls_5eima0pd31c0", - "informational": false, - "link": "ownership-and-deconstruction.html#drop-scope-extension", - "number": "15.8.1", - "paragraphs": [ - { - "checksum": "5c3d236f194a8a669a999473090570b17a9c5b62ef5f44c8025bcba89e97b82f", - "id": "fls_kflqez2mtbit", - "link": "ownership-and-deconstruction.html#fls_kflqez2mtbit", - "number": "15.8.1:1" + "checksum": "ed06a87167d12f3592d3f1f3fe6b1b8dee1445d3084186ca2eb0c64e38d0a195", + "id": "fls_9tedg9lpewqa", + "link": "entities-and-resolution.html#fls_9tedg9lpewqa", + "number": "14.9.4:8" }, { - "checksum": "3261611ac54de3039f960d9c6a30c641626dd062081332cd0f8cb2e0db746c2f", - "id": "fls_xjw82bujm148", - "link": "ownership-and-deconstruction.html#fls_xjw82bujm148", - "number": "15.8.1:2" + "checksum": "836b27b41fb978acddad30353606e589d3d7a0f959fd869e0363f5f4da38a1e2", + "id": "fls_mvymlhp7192e", + "link": "entities-and-resolution.html#fls_mvymlhp7192e", + "number": "14.9.4:9" }, { - "checksum": "95c5c42ea8af76e5ec13e3556a9f4104b818bf056900164658866e0c491b17bb", - "id": "fls_965wt48ooqyw", - "link": "ownership-and-deconstruction.html#fls_965wt48ooqyw", - "number": "15.8.1:3" + "checksum": "8bc4d6164a3454b1f92bc7c3b987ebd35d035f0b570af2120160837269eb6880", + "id": "fls_cs485plo4z49", + "link": "entities-and-resolution.html#fls_cs485plo4z49", + "number": "14.9.4:10" }, { - "checksum": "5034d75dc865e69bdf2efb3059a8f9cf91eff07283969d9f524cbfabd137b3c7", - "id": "fls_r8nt0zp8dnyp", - "link": "ownership-and-deconstruction.html#fls_r8nt0zp8dnyp", - "number": "15.8.1:4" + "checksum": "28e96aa4ab999e28d472ac597a459b4f3cf5355e1f3d4e36729f80527e38a9a3", + "id": "fls_yrpem8vhxpr5", + "link": "entities-and-resolution.html#fls_yrpem8vhxpr5", + "number": "14.9.4:11" }, { - "checksum": "cab57995b7b7f326223f4f7bb819e8eea5a8ad676cdbed06ce58b66817c6a3c9", - "id": "fls_3ycn4u1fe9h", - "link": "ownership-and-deconstruction.html#fls_3ycn4u1fe9h", - "number": "15.8.1:5" + "checksum": "12d01e64bed3fb1d93c2baaba1c990b876b01143f65e13549b16e2f86722c1f8", + "id": "fls_ri50nc2dg7c4", + "link": "entities-and-resolution.html#fls_ri50nc2dg7c4", + "number": "14.9.4:12" }, { - "checksum": "3e3ddee5e272d848e4a7f855e0958e6cae76731b52ed7b3755bede5d03752f5f", - "id": "fls_YeeZWqTdMivX", - "link": "ownership-and-deconstruction.html#fls_YeeZWqTdMivX", - "number": "15.8.1:6" + "checksum": "2f1892ee92de8c871fc2ae0a0108195b7e93d76975e2ed4557897be68ac335ea", + "id": "fls_to52oma1bvx3", + "link": "entities-and-resolution.html#fls_to52oma1bvx3", + "number": "14.9.4:13" }, { - "checksum": "b73020efe316a10a7ebe19452ab0c49d32e022305cbdc0ffeffe39f1f1c49ce4", - "id": "fls_wyzau8hhq74d", - "link": "ownership-and-deconstruction.html#fls_wyzau8hhq74d", - "number": "15.8.1:7" + "checksum": "e2260bf8d1cd6ed1f8f560bde5a04eda03749ba3b9698be0e9e830379cc526ac", + "id": "fls_kpn2y7xb3s8q", + "link": "entities-and-resolution.html#fls_kpn2y7xb3s8q", + "number": "14.9.4:14" }, { - "checksum": "9866d8c9d89c6ce74866c072f17480e836646226238ffb97d01caa057d3c433b", - "id": "fls_ju7cpftd8r2g", - "link": "ownership-and-deconstruction.html#fls_ju7cpftd8r2g", - "number": "15.8.1:8" + "checksum": "e869f661950b3fb36bb49810ed4fa5897bab056d9061a5614f1f983004a68702", + "id": "fls_z71op1vdnazq", + "link": "entities-and-resolution.html#fls_z71op1vdnazq", + "number": "14.9.4:15" }, { - "checksum": "65a8e01501aa93c5c0390794552d8464ba66252c1f16ef70cebbe188a818aeb6", - "id": "fls_gjd1ow3l7swe", - "link": "ownership-and-deconstruction.html#fls_gjd1ow3l7swe", - "number": "15.8.1:9" + "checksum": "f1bf72c3463050e387b72d2514a1d90f3ce47bceb98da6b23877d0cf3a67ad9a", + "id": "fls_2km29ekj9464", + "link": "entities-and-resolution.html#fls_2km29ekj9464", + "number": "14.9.4:16" }, { - "checksum": "0eeb96ccaefbfba629a6270d8f8c4fb6932a85dcaba82547b73663bb5d4fb954", - "id": "fls_Rj9zhVutfQod", - "link": "ownership-and-deconstruction.html#fls_Rj9zhVutfQod", - "number": "15.8.1:10" + "checksum": "8048713e7609c52f8f92a383762dac9c9e3c99f7aa1e987e0d6e2fe1af9c998a", + "id": "fls_l2y464skbuta", + "link": "entities-and-resolution.html#fls_l2y464skbuta", + "number": "14.9.4:17" }, { - "checksum": "8a0dd3acb9fbefddfa8ada43770d247ab7c282b9a44c8d16ad53d22d93837c77", - "id": "fls_oodpp3LpXC13", - "link": "ownership-and-deconstruction.html#fls_oodpp3LpXC13", - "number": "15.8.1:11" + "checksum": "1bf727a1dcaab6f5e8df8590d6bb1ba357978668aa0b347d821b88b023b5731b", + "id": "fls_n2x13sg5szbl", + "link": "entities-and-resolution.html#fls_n2x13sg5szbl", + "number": "14.9.4:18" }, { - "checksum": "a107c1e8191264e8fe4459fcca34d39259cfdb2efd2fd252b46b3b5f14007bb7", - "id": "fls_xGThCPoTUSAi", - "link": "ownership-and-deconstruction.html#fls_xGThCPoTUSAi", - "number": "15.8.1:12" + "checksum": "288a6623bd127a3853dec6c8c81b675f11276f49d7085cdc4252566638ef0cad", + "id": "fls_53kd7eb1qzuz", + "link": "entities-and-resolution.html#fls_53kd7eb1qzuz", + "number": "14.9.4:19" }, { - "checksum": "f9d27231592868802964cc99b81ef283fb6e96cd3abae622cbd859e5d1dd4caa", - "id": "fls_iqw0d1l1lj3i", - "link": "ownership-and-deconstruction.html#fls_iqw0d1l1lj3i", - "number": "15.8.1:13" + "checksum": "8d75724256d8acc0107bb9928323bf6eebea21f3cb90023f550e681bd0f4c068", + "id": "fls_3spnlz9tqnhj", + "link": "entities-and-resolution.html#fls_3spnlz9tqnhj", + "number": "14.9.4:20" }, { - "checksum": "592addbb57be3cb76f33b59e4e1956090c29e9a0d89327fabc53e40e6c62ef21", - "id": "fls_eeaJtK4w5gVK", - "link": "ownership-and-deconstruction.html#fls_eeaJtK4w5gVK", - "number": "15.8.1:14" + "checksum": "b3276319834228734d7f152a0a6ee88d4760ced117b6e497f7775f79453e1247", + "id": "fls_Mt6xKI08viYw", + "link": "entities-and-resolution.html#fls_Mt6xKI08viYw", + "number": "14.9.4:21" }, { - "checksum": "ac60d872c2d76afebdf1240a5b1a41539175dcd095ba1374d768e8bce3e15b83", - "id": "fls_aq01wjpkxhq9", - "link": "ownership-and-deconstruction.html#fls_aq01wjpkxhq9", - "number": "15.8.1:15" + "checksum": "bba6464e5850bc807bb84138c417abccf4d8e863b7cc927a7dc6c4f574742d9f", + "id": "fls_7hK059djudGN", + "link": "entities-and-resolution.html#fls_7hK059djudGN", + "number": "14.9.4:22" }, { - "checksum": "46590c95c001116b2b295d3fc9fa40d5606614d0db1cfd11751f0a52118c3ef7", - "id": "fls_VDPi1dJzJMUb", - "link": "ownership-and-deconstruction.html#fls_VDPi1dJzJMUb", - "number": "15.8.1:16" + "checksum": "d976b884515464965ab2b569d6e256a24cbdd97a74803de060faefb3dcf17a9a", + "id": "fls_lxa7uhmdoy9d", + "link": "entities-and-resolution.html#fls_lxa7uhmdoy9d", + "number": "14.9.4:23" }, { - "checksum": "d24a178200c6d509f3d2649dd64705b91fb52709df2446bfa5b4e8fa8ad62f32", - "id": "fls_elGH7HSawMRb", - "link": "ownership-and-deconstruction.html#fls_elGH7HSawMRb", - "number": "15.8.1:17" + "checksum": "3ceffa06884bb95ce527ddb9ed4cc31c620d57a422f0938a8257f78e4c4f844a", + "id": "fls_EcaZauRXEGcs", + "link": "entities-and-resolution.html#fls_EcaZauRXEGcs", + "number": "14.9.4:24" }, { - "checksum": "0baddc916a2237fc255082bc2e1dedb989c57b2c82b53885416321f4058213e5", - "id": "fls_29y59x8bmw75", - "link": "ownership-and-deconstruction.html#fls_29y59x8bmw75", - "number": "15.8.1:18" + "checksum": "10ebf431ca0e173e09d0e95cc99d83fa560ba88808dfbb3c22cb34e450e43483", + "id": "fls_xujlscsir05f", + "link": "entities-and-resolution.html#fls_xujlscsir05f", + "number": "14.9.4:25" }, { - "checksum": "42a669613202095a2133d66d495e0e881e4d7819d202aae70dc008b2a1f19724", - "id": "fls_subo2w7ln43q", - "link": "ownership-and-deconstruction.html#fls_subo2w7ln43q", - "number": "15.8.1:19" + "checksum": "d4f7baf7b1415e0ddd6d71a215ed14fec816b15d9e58dcc2454d7f27e69004bd", + "id": "fls_wypnvfklnmc1", + "link": "entities-and-resolution.html#fls_wypnvfklnmc1", + "number": "14.9.4:26" + }, + { + "checksum": "cd2421eeddd4a05db76bdfc69b504e9b4de55915c2ef18bfaa6e927a3e72ce16", + "id": "fls_zi46lmwsn4rg", + "link": "entities-and-resolution.html#fls_zi46lmwsn4rg", + "number": "14.9.4:27" + }, + { + "checksum": "5090abee767f23b1aea068bdf5cf40ac88c35f6bab4a3fe0c8da122c592d1093", + "id": "fls_bATFGtxjKq0B", + "link": "entities-and-resolution.html#fls_bATFGtxjKq0B", + "number": "14.9.4:28" } ], - "title": "Drop Scope Extension" + "title": "Path Resolution" }, { - "id": "fls_afafmafz4hf2", + "id": "fls_bbso3c45kr9z", "informational": false, - "link": "ownership-and-deconstruction.html#drop-order", - "number": "15.9", + "link": "entities-and-resolution.html#simple-path-resolution", + "number": "14.9.4.1", "paragraphs": [ { - "checksum": "6d998923ce960019bd73240feb0f4f1e3e9d705bbedbaf213c73707899dfd504", - "id": "fls_n6o1xzjiz8cv", - "link": "ownership-and-deconstruction.html#fls_n6o1xzjiz8cv", - "number": "15.9:1" + "checksum": "c1ebb5064acb14aea9a28d26b01046f531294fbbd5ca6410cce82bf98b8d7261", + "id": "fls_uml24jw5jo7a", + "link": "entities-and-resolution.html#fls_uml24jw5jo7a", + "number": "14.9.4.1:1" }, { - "checksum": "0c3b33caf3b9c961df39107ec74da60de4eb20ab99798df119cb2faed79efe15", - "id": "fls_jwofws3022ar", - "link": "ownership-and-deconstruction.html#fls_jwofws3022ar", - "number": "15.9:2" + "checksum": "253940a0312803793da657e8bb0a7e478cbc89857c2a237d03548618de1ec256", + "id": "fls_59wd7loxst43", + "link": "entities-and-resolution.html#fls_59wd7loxst43", + "number": "14.9.4.1:2" }, { - "checksum": "0c98ebb3d2ca90465f8ced9ebdf2d3743e1a59cb248f2af3e3e5653cf2ca76b7", - "id": "fls_W2S2FrkuedYC", - "link": "ownership-and-deconstruction.html#fls_W2S2FrkuedYC", - "number": "15.9:3" + "checksum": "8459bc1ffb9ad72f9f9ce23cfd6b93c218419c6681b8e4a7686dae5b8ac9e309", + "id": "fls_mk0ufkeggot6", + "link": "entities-and-resolution.html#fls_mk0ufkeggot6", + "number": "14.9.4.1:3" }, { - "checksum": "14fdcee19609abf8aeed5f7e20952a848faecfba256dd405eca3cc49a24b338f", - "id": "fls_a5tmilqxdb6f", - "link": "ownership-and-deconstruction.html#fls_a5tmilqxdb6f", - "number": "15.9:4" + "checksum": "b3db0521d2ccad1a7ff3c83e38d7bfada206a50d8fced514e5605a753bcacab8", + "id": "fls_ayv8okec9fwb", + "link": "entities-and-resolution.html#fls_ayv8okec9fwb", + "number": "14.9.4.1:4" }, { - "checksum": "08645dd3230efc38b718c5ab02247a8a4fc278d67fea4fd1d696c57033eff69e", - "id": "fls_g07zq3n55094", - "link": "ownership-and-deconstruction.html#fls_g07zq3n55094", - "number": "15.9:5" + "checksum": "1650a4f513fab2754fe189d6a8993c9c7855378082856282662768a8528dc575", + "id": "fls_ppoc6wcplab6", + "link": "entities-and-resolution.html#fls_ppoc6wcplab6", + "number": "14.9.4.1:5" }, { - "checksum": "717f7f256a61d53de344e5a6553b5529ee34e430a67efc99ceab0162e8631a2a", - "id": "fls_zQGkVGWIzMQ7", - "link": "ownership-and-deconstruction.html#fls_zQGkVGWIzMQ7", - "number": "15.9:6" + "checksum": "f036f3d708d6307e0393c1e54c59023aa992026100787bff26b010ac6a22a467", + "id": "fls_dc0yv4306p82", + "link": "entities-and-resolution.html#fls_dc0yv4306p82", + "number": "14.9.4.1:6" }, { - "checksum": "8da2ed90a465a8e65f20c991391be8fcdc1d7b7d35ec5e06dfef767d9b68cae4", - "id": "fls_K8whhUwAUVqR", - "link": "ownership-and-deconstruction.html#fls_K8whhUwAUVqR", - "number": "15.9:7" + "checksum": "5ac0651992734c49cdd19081a8d8fe0dc4836c3ac46e8efe50194b2f7e10729f", + "id": "fls_jhivcca0xcqj", + "link": "entities-and-resolution.html#fls_jhivcca0xcqj", + "number": "14.9.4.1:7" }, { - "checksum": "df8e0a488a8a05da58f4656abffbbd5eb240a8a0ff05293049814a5fdf5acb4b", - "id": "fls_NpCrm4t03oQg", - "link": "ownership-and-deconstruction.html#fls_NpCrm4t03oQg", - "number": "15.9:8" - }, + "checksum": "07a8a2eb540fa895244a934632b5bf01b28d65714143604e47094193df81fafb", + "id": "fls_tfsgutcpube2", + "link": "entities-and-resolution.html#fls_tfsgutcpube2", + "number": "14.9.4.1:8" + } + ], + "title": "Simple Path Resolution" + }, + { + "id": "fls_o9u2h5m17kpz", + "informational": false, + "link": "entities-and-resolution.html#path-expression-resolution", + "number": "14.9.4.2", + "paragraphs": [ { - "checksum": "4b4f51cb30705d0cc7632e1f0e4132697ef1cdbe4cb781a401e1baf0ec958fba", - "id": "fls_3i348l3pbtrx", - "link": "ownership-and-deconstruction.html#fls_3i348l3pbtrx", - "number": "15.9:9" + "checksum": "89af848f7138b85e851a6fce2956808b14f21021ce84337948db5477059f0233", + "id": "fls_akjlqm3a2lb1", + "link": "entities-and-resolution.html#fls_akjlqm3a2lb1", + "number": "14.9.4.2:1" }, { - "checksum": "7dca09bc4bd21d1c460e1631dceb6a2e06827b223efdf7b8728a52a26de3d225", - "id": "fls_oe8l81y0wnao", - "link": "ownership-and-deconstruction.html#fls_oe8l81y0wnao", - "number": "15.9:10" + "checksum": "36e698199bc9da0c6a50931da2b24831e8a0c0750e8ce52aeac1278944de27a5", + "id": "fls_xyzdajtf4u2t", + "link": "entities-and-resolution.html#fls_xyzdajtf4u2t", + "number": "14.9.4.2:2" }, { - "checksum": "61f14bc12e0d1c55c7aa99cf8c4e500d1effa786d1f50cc8ac8463693abeb1fa", - "id": "fls_4sgca9wcl8h0", - "link": "ownership-and-deconstruction.html#fls_4sgca9wcl8h0", - "number": "15.9:11" + "checksum": "947403e0d43bb6cfb3077d98850a6352967094b8f5cb8b7f5c87720092f9c7ce", + "id": "fls_d45vu3iazi3", + "link": "entities-and-resolution.html#fls_d45vu3iazi3", + "number": "14.9.4.2:3" }, { - "checksum": "2ac52c80f7a45d49874b8b452b7d5541e0200bef9c38d6f5b32c0e20bf9296b3", - "id": "fls_a2m4ibzhgupa", - "link": "ownership-and-deconstruction.html#fls_a2m4ibzhgupa", - "number": "15.9:12" + "checksum": "d10ea4290290cedc3b93488c82b41a90c8b0f81d39b7086609ec5f0800847605", + "id": "fls_9pjhok9rctty", + "link": "entities-and-resolution.html#fls_9pjhok9rctty", + "number": "14.9.4.2:4" }, { - "checksum": "5cbdd388b3d5de9f7fcb63733ebb916504fce634b415f2fcd87d3683cdcb52c6", - "id": "fls_go3bvd23vzi9", - "link": "ownership-and-deconstruction.html#fls_go3bvd23vzi9", - "number": "15.9:13" + "checksum": "b61a1df0de63c17349e99968166779c0689667f68065641b1e2150f748f65308", + "id": "fls_2wbpr9lvz5yq", + "link": "entities-and-resolution.html#fls_2wbpr9lvz5yq", + "number": "14.9.4.2:5" }, { - "checksum": "095cc1d51b861087a618aa1e4e9fc0377405a71c6d4d02c44502cfbfb34e962e", - "id": "fls_7rwo0he8x143", - "link": "ownership-and-deconstruction.html#fls_7rwo0he8x143", - "number": "15.9:14" + "checksum": "aab266bf1130186940d08a2295560fbf7ce71f175fd96c37a30e54fc2c971f1e", + "id": "fls_u5nyv6ii1g2l", + "link": "entities-and-resolution.html#fls_u5nyv6ii1g2l", + "number": "14.9.4.2:6" }, { - "checksum": "beaea22f979a7a281290ec83d8d0ac0786a9365b64f973a51a5c48539df32310", - "id": "fls_43yqlxjr3a10", - "link": "ownership-and-deconstruction.html#fls_43yqlxjr3a10", - "number": "15.9:15" + "checksum": "f48db4f42fe8c1ac1316b5e2c8512944ed025def1e5c0e37911a3eca62f9d2d0", + "id": "fls_bscg48os5otx", + "link": "entities-and-resolution.html#fls_bscg48os5otx", + "number": "14.9.4.2:7" }, { - "checksum": "b0246b982f33561e6c330d7de3f8c8d00db491873c9db1eda52117c58981edbc", - "id": "fls_a7lsq2kkzkk4", - "link": "ownership-and-deconstruction.html#fls_a7lsq2kkzkk4", - "number": "15.9:16" + "checksum": "82ccbb720ea64a252b72a8aec77019bb5127c99487c3f35d9e6dadc715a2490d", + "id": "fls_qbmp0blpoxx9", + "link": "entities-and-resolution.html#fls_qbmp0blpoxx9", + "number": "14.9.4.2:8" }, { - "checksum": "50a6c72b40cc9859cd38ff5a24c35677fdd68698fea0be3c96f0f0c910021008", - "id": "fls_dhfIPP4yR3Tt", - "link": "ownership-and-deconstruction.html#fls_dhfIPP4yR3Tt", - "number": "15.9:17" + "checksum": "bbeb20abca91e618378dccd524e3d3a000f6f9e7f3141dc46ca1370b9efb0aeb", + "id": "fls_ydni5laqv6gp", + "link": "entities-and-resolution.html#fls_ydni5laqv6gp", + "number": "14.9.4.2:9" }, { - "checksum": "11982a419dd799bae7bb0dc8cec5d03b09cc19e27def8ff9c62098ebfb3d06c3", - "id": "fls_zxFM7EoE2Xq8", - "link": "ownership-and-deconstruction.html#fls_zxFM7EoE2Xq8", - "number": "15.9:18" + "checksum": "447a699862b4725e7b22ef7e24b1ab0783fec4db871aa6703ba6a7001eab753e", + "id": "fls_lsxbl6ep3150", + "link": "entities-and-resolution.html#fls_lsxbl6ep3150", + "number": "14.9.4.2:10" }, { - "checksum": "61083f1dbe74584643880f67bbc58f8821e4a546ebd5064b350c5e383546b22b", - "id": "fls_093YxG6YXQz2", - "link": "ownership-and-deconstruction.html#fls_093YxG6YXQz2", - "number": "15.9:19" + "checksum": "82acf15a92dae67f822e95bc38c6423ae258c77c41fe7312c0a6d9fbb981207e", + "id": "fls_x1n7w8w6lwm", + "link": "entities-and-resolution.html#fls_x1n7w8w6lwm", + "number": "14.9.4.2:11" }, { - "checksum": "9e8d95121910f51f870a1f2ecaa4380de9bec07541f9e0432cb14042f6428351", - "id": "fls_gNWXh61ZXXt8", - "link": "ownership-and-deconstruction.html#fls_gNWXh61ZXXt8", - "number": "15.9:20" - } - ], - "title": "Drop Order" - } - ], - "title": "Ownership and Destruction" - }, - { - "informational": false, - "link": "macros.html", - "sections": [ - { - "id": "fls_83182bfa9uqb", - "informational": false, - "link": "macros.html", - "number": "20", - "paragraphs": [ + "checksum": "15004350a8a539c4ce31613578db742e17945883ac27c5d3bf184e757faed1c6", + "id": "fls_v1h4frnbqruu", + "link": "entities-and-resolution.html#fls_v1h4frnbqruu", + "number": "14.9.4.2:12" + }, { - "checksum": "23ad7afdee8d11ed607ec24d3a0e15437d673156ba4b127587b5669f1832653b", - "id": "fls_j1jc83erljo0", - "link": "macros.html#fls_j1jc83erljo0", - "number": "20:1" + "checksum": "9ac0b1cdc63a7e767baf563d906d9f2d9c9f560ba05a020309fdea70f031bf47", + "id": "fls_utfpnwlo0v99", + "link": "entities-and-resolution.html#fls_utfpnwlo0v99", + "number": "14.9.4.2:13" }, { - "checksum": "f3350d5be59f16503e8f202eed548f9142e23d0995f540a0fe8dbfa0790eb114", - "id": "fls_23eapx3ckymf", - "link": "macros.html#fls_23eapx3ckymf", - "number": "20:2" + "checksum": "39b97104cbe16fdf9cdc1acf3f079ade2fd32c3ac33e7858ae07e79a0bbc7907", + "id": "fls_1p8ocf1w5bp4", + "link": "entities-and-resolution.html#fls_1p8ocf1w5bp4", + "number": "14.9.4.2:14" }, { - "checksum": "b37827025945e3ebe09fb51af413ab507e55bef20cbd1cdc0d88bda1e5b31868", - "id": "fls_a5uemz2hnbi8", - "link": "macros.html#fls_a5uemz2hnbi8", - "number": "20:3" + "checksum": "57570b48e6c6c789ee1bcbc956a0f24f79597a3ef49766fcff1d1600dad9b834", + "id": "fls_qb5yo7j5gnvf", + "link": "entities-and-resolution.html#fls_qb5yo7j5gnvf", + "number": "14.9.4.2:15" }, { - "checksum": "f947401b4192d1cb447cdbc6dfa662250896c68e1cb0477bdfdbc7dfaf9b809c", - "id": "fls_rnty1c8l5495", - "link": "macros.html#fls_rnty1c8l5495", - "number": "20:4" + "checksum": "f5d76df40ae4cadd8e2d9663fbe3c5b810519b7c72d0c10a68e00aa5f027118b", + "id": "fls_o1g0forw6xw", + "link": "entities-and-resolution.html#fls_o1g0forw6xw", + "number": "14.9.4.2:16" + }, + { + "checksum": "ce270746d595abe4f9d7b06360f034018301bd6709bc75fe5c1fde0bc1629b78", + "id": "fls_bcqe13q696zg", + "link": "entities-and-resolution.html#fls_bcqe13q696zg", + "number": "14.9.4.2:17" + }, + { + "checksum": "91a7d385f03fde69b74c5358096b89c7db65599637389124b36104c6f45d2b53", + "id": "fls_3sceutaqpqha", + "link": "entities-and-resolution.html#fls_3sceutaqpqha", + "number": "14.9.4.2:18" + }, + { + "checksum": "6737364b72e672ce8f63321ee00914ec76f3c688ef520853fa9e96db1550f2e5", + "id": "fls_6q9cwqlvxmd1", + "link": "entities-and-resolution.html#fls_6q9cwqlvxmd1", + "number": "14.9.4.2:19" + }, + { + "checksum": "a6ecb99abfccb4f9199fc7b11b6264c36f5e5074d9a587814517d58bb28b0397", + "id": "fls_qeym3vbi36iv", + "link": "entities-and-resolution.html#fls_qeym3vbi36iv", + "number": "14.9.4.2:20" + }, + { + "checksum": "b5e402b7df0a8601f86f4a9fe385165d3998a9a9d0eda91d847641c00cac3469", + "id": "fls_8x0pqwpm80sj", + "link": "entities-and-resolution.html#fls_8x0pqwpm80sj", + "number": "14.9.4.2:21" + }, + { + "checksum": "e6032bec4cb34591f1ba4d23d2ce789f2b98a66582dd5c3aa70faf8101bd90f7", + "id": "fls_pp09gmrnasjp", + "link": "entities-and-resolution.html#fls_pp09gmrnasjp", + "number": "14.9.4.2:22" + }, + { + "checksum": "b1754db3d3ab0eb3fe598859fff3a8179aa499eb840ad97eba99417063259cb9", + "id": "fls_q0jt6n2j1hsx", + "link": "entities-and-resolution.html#fls_q0jt6n2j1hsx", + "number": "14.9.4.2:23" } ], - "title": "Macros" + "title": "Path Expression Resolution" }, { - "id": "fls_xa7lp0zg1ol2", + "id": "fls_1h0olpc7vbui", "informational": false, - "link": "macros.html#declarative-macros", - "number": "20.1", + "link": "entities-and-resolution.html#type-path-resolution", + "number": "14.9.4.3", "paragraphs": [ { - "checksum": "8cffdd9962e92466f819713aab704c780ae0d98dac229021c576afa2742830ba", - "id": "fls_ikzjsq8heyk6", - "link": "macros.html#fls_ikzjsq8heyk6", - "number": "20.1:1" + "checksum": "148fc3beb5377b51b5edacfbb4ace5e23c7411944c711b49cd4964375b6fc241", + "id": "fls_2zuncql8ir5k", + "link": "entities-and-resolution.html#fls_2zuncql8ir5k", + "number": "14.9.4.3:1" }, { - "checksum": "4e9e7f12e6633dbb05180c17c6a222b11c2760152ee1a091050e70af0a2f4238", - "id": "fls_w44hav7mw3ao", - "link": "macros.html#fls_w44hav7mw3ao", - "number": "20.1:2" + "checksum": "fbaeca87efc96f46effc5215cbf8bc344900a3226501a59b1a547bb863112ec9", + "id": "fls_bv5cj918dqqe", + "link": "entities-and-resolution.html#fls_bv5cj918dqqe", + "number": "14.9.4.3:2" }, { - "checksum": "af67fc9d046886fb7c793c0a9eef775cd020529cf8ec85a93167f1e58f4122fa", - "id": "fls_dw1nq4r9ghhd", - "link": "macros.html#fls_dw1nq4r9ghhd", - "number": "20.1:3" + "checksum": "752f0ff05ebaea022db0ecba55adfcb69b6f6c23c86019b367b3543af220a26c", + "id": "fls_bsakzuteuh5s", + "link": "entities-and-resolution.html#fls_bsakzuteuh5s", + "number": "14.9.4.3:3" }, { - "checksum": "13beaa609262aa0b7e0b3212da54ac78e68086106c14cc11e081e7132158286b", - "id": "fls_oq4xn8guos8f", - "link": "macros.html#fls_oq4xn8guos8f", - "number": "20.1:4" + "checksum": "a2d543d3938e4eaf6c88335ab06f4b99d478110d46ea60d86ee04356b323bcd9", + "id": "fls_j1ewjisx0mc2", + "link": "entities-and-resolution.html#fls_j1ewjisx0mc2", + "number": "14.9.4.3:4" }, { - "checksum": "b549b18e6de60d1ec130ab38737806e6ea0cbd0d471501c476916eefe2be7228", - "id": "fls_cdaf8viwmdfe", - "link": "macros.html#fls_cdaf8viwmdfe", - "number": "20.1:5" + "checksum": "12dd80327fd8a5c538a2841bd1c0e0122e4159042245c5ffce28e72f8be6691d", + "id": "fls_o4snu1him277", + "link": "entities-and-resolution.html#fls_o4snu1him277", + "number": "14.9.4.3:5" }, { - "checksum": "238647cd9313b6607667d9993c8bf87f5406351bd4a87045bd0850f5063f6b84", - "id": "fls_ljavs0w61z3j", - "link": "macros.html#fls_ljavs0w61z3j", - "number": "20.1:6" + "checksum": "6689718fd77318ab2f4a94867ff442dd6bfa733fdede8a658be7cdb6eabb0281", + "id": "fls_goe8q52toik2", + "link": "entities-and-resolution.html#fls_goe8q52toik2", + "number": "14.9.4.3:6" }, { - "checksum": "41d7cadc4529963b5fae05cea05d12721cc023eac8db3ab3013c78712f677f92", - "id": "fls_3jspk8obv7sd", - "link": "macros.html#fls_3jspk8obv7sd", - "number": "20.1:7" + "checksum": "38657ebf4baa69cb441b05dfa17a0c65b100642cb9c9c766425c0db3fd888577", + "id": "fls_4rs35f6ydckj", + "link": "entities-and-resolution.html#fls_4rs35f6ydckj", + "number": "14.9.4.3:7" + }, + { + "checksum": "b41f690747dc7316182fec64bdba0c0beb06900a8bbaba23f018fcec1330a29d", + "id": "fls_jh4db1p7or0x", + "link": "entities-and-resolution.html#fls_jh4db1p7or0x", + "number": "14.9.4.3:8" } ], - "title": "Declarative Macros" - }, + "title": "Type Path Resolution" + } + ], + "title": "Entities and Resolution" + }, + { + "informational": false, + "link": "associated-items.html", + "sections": [ { - "id": "fls_8nzypdu9j3ge", + "id": "fls_l21tjqjkkaa0", "informational": false, - "link": "macros.html#metavariables", - "number": "20.1.1", + "link": "associated-items.html", + "number": "10", "paragraphs": [ { - "checksum": "142bdc43ec20b9deed884f97e167a2a47e1cd8cab76ba4b6a81e5b3ec56b69ea", - "id": "fls_g93r3teei8wo", - "link": "macros.html#fls_g93r3teei8wo", - "number": "20.1.1:1" + "checksum": "c48bd96544b685d7b7a7e8a2e676eb1c8ed2066a485c20a48faa1c660ef8bdb7", + "id": "fls_ckzd25qd213t", + "link": "associated-items.html#fls_ckzd25qd213t", + "number": "10:1" }, { - "checksum": "260a8af1053ab13b37c8171151aec029a2b0049a4db4d2fa4c072391caa7c788", - "id": "fls_4zdait30exvn", - "link": "macros.html#fls_4zdait30exvn", - "number": "20.1.1:2" + "checksum": "fd7bf7d0a40ad75d0809a98d1bd273b94708f4a26b1a3be16f7e8dbffbe638ec", + "id": "fls_5y6ae0xqux57", + "link": "associated-items.html#fls_5y6ae0xqux57", + "number": "10:2" }, { - "checksum": "0e475930b62b5e6e2c4eaa5d954c01002c3918fb1243f5ffd3b8b5959e5350f0", - "id": "fls_2HguXbL7DjKH", - "link": "macros.html#fls_2HguXbL7DjKH", - "number": "20.1.1:3" + "checksum": "fda609cdbb71ae52c820019783b3b6858d633fdd6de601cfae585217aeb034aa", + "id": "fls_lj7492aq7fzo", + "link": "associated-items.html#fls_lj7492aq7fzo", + "number": "10:3" }, { - "checksum": "e16b11f619d14521da5d28556e2228fd055de524ae57da0397dc5fb7f244b522", - "id": "fls_8zypylq60zba", - "link": "macros.html#fls_8zypylq60zba", - "number": "20.1.1:4" + "checksum": "d0a97d55dc69fb14de940c4597c8ec6cc4d1bdea2efef1f262ca235d4380a078", + "id": "fls_8cz4rdrklaj4", + "link": "associated-items.html#fls_8cz4rdrklaj4", + "number": "10:4" }, { - "checksum": "a7305e4a6c0db284c01d3b766c9526e183f139d0d6b3417b05a0a25448deb2e1", - "id": "fls_8o9mcV2KrKac", - "link": "macros.html#fls_8o9mcV2KrKac", - "number": "20.1.1:5" + "checksum": "a21d4472dc9edff52ba6084426735486e96547a9916ebbf6041810119a4ca7aa", + "id": "fls_w8nu8suy7t5", + "link": "associated-items.html#fls_w8nu8suy7t5", + "number": "10:5" }, { - "checksum": "bbdb87195b02f153c61cd395d1d5db4690ef89af595efddbf90e07037c6d88fd", - "id": "fls_PxR9vNHsaFnI", - "link": "macros.html#fls_PxR9vNHsaFnI", - "number": "20.1.1:6" + "checksum": "a5074f92b70f64ed6d002c08f65bfa91a814cf5b80b19137743ca62a12bda2b6", + "id": "fls_wasocqdnuzd1", + "link": "associated-items.html#fls_wasocqdnuzd1", + "number": "10:6" }, { - "checksum": "db57f38586128d0f797f87a3bec37a62e9bc1ffe1a0b5b3634c97bbbd3c3a923", - "id": "fls_ePyoTeJJ11N0", - "link": "macros.html#fls_ePyoTeJJ11N0", - "number": "20.1.1:7" + "checksum": "61134011658677b475c3f8e65ac36ab5b479e0746afd064ac6d0084c0441fbb6", + "id": "fls_PeD0DzjK57be", + "link": "associated-items.html#fls_PeD0DzjK57be", + "number": "10:7" }, { - "checksum": "5ab36e5b8b233c50fbb634f2ddb85db4a65676e58884c4e545002ad8ae792716", - "id": "fls_0j7VOV4ewfeY", - "link": "macros.html#fls_0j7VOV4ewfeY", - "number": "20.1.1:8" + "checksum": "0dd68e90f7962b7f3ab0d020e22ee6db5fde4efa6ae628af968af3fe3d76baa8", + "id": "fls_3foYUch29ZtF", + "link": "associated-items.html#fls_3foYUch29ZtF", + "number": "10:8" }, { - "checksum": "7e4c3dc1c34fcadf4bb70df2a2ca55cb16019fe78a19c5f625a3407c7f23de0c", - "id": "fls_80cOMpIMU2gx", - "link": "macros.html#fls_80cOMpIMU2gx", - "number": "20.1.1:9" + "checksum": "6f1791ecd8b47dfbf50561704cc838d8b0e544cf7c69b576dd48120715a47166", + "id": "fls_SnQc0zZS57Cz", + "link": "associated-items.html#fls_SnQc0zZS57Cz", + "number": "10:9" }, { - "checksum": "ae189445a188e528fc320b0716648ea260e44624143f233a7a3d6594c319fc71", - "id": "fls_DFMRwsWI8e5z", - "link": "macros.html#fls_DFMRwsWI8e5z", - "number": "20.1.1:10" + "checksum": "85d03d9e5ceb6390c1024e3f0079563e480155c17b687f8118d280be74b27d92", + "id": "fls_6Z05BK2JSzpP", + "link": "associated-items.html#fls_6Z05BK2JSzpP", + "number": "10:10" }, { - "checksum": "3a94c395e86b915f016782c8e832229ac77ff7d26c4468f604f4d1346d410385", - "id": "fls_BoIGgrFdyhwH", - "link": "macros.html#fls_BoIGgrFdyhwH", - "number": "20.1.1:11" + "checksum": "1a672e4e07310314b3ed72804595330ecf46d9177808d67beee1497db38edc76", + "id": "fls_AtItgS1UvwiX", + "link": "associated-items.html#fls_AtItgS1UvwiX", + "number": "10:11" }, { - "checksum": "10fac2b91182e4a802d41382e138eeea01e0169f14fd98576dec0541757d3d52", - "id": "fls_NBbygZwUxjFp", - "link": "macros.html#fls_NBbygZwUxjFp", - "number": "20.1.1:12" + "checksum": "f83c7a08c44f0dc091a19c1014f202ab5538474a3d818f1583bd138daef33ecf", + "id": "fls_l3iwn56n1uz8", + "link": "associated-items.html#fls_l3iwn56n1uz8", + "number": "10:12" }, { - "checksum": "a397be8475f298d03d342d1d7922a7addc7c4e01708dbc748271e89ff6e4e91a", - "id": "fls_lZ8F1zUJju33", - "link": "macros.html#fls_lZ8F1zUJju33", - "number": "20.1.1:13" - }, - { - "checksum": "6d1e723dc52e63b247ff2a9f36fd2a2448c78a8a6c0785d9bc962cbf164a68ff", - "id": "fls_ephlmLsGTMgw", - "link": "macros.html#fls_ephlmLsGTMgw", - "number": "20.1.1:14" - } - ], - "title": "Metavariables" - }, - { - "id": "fls_k01lsksqtq1r", - "informational": false, - "link": "macros.html#repetition", - "number": "20.1.2", - "paragraphs": [ - { - "checksum": "9be00a29faba4cce56b85af749378910166a176caaf783feb7f21917b113f4fc", - "id": "fls_4ps4x4513xau", - "link": "macros.html#fls_4ps4x4513xau", - "number": "20.1.2:1" - }, - { - "checksum": "0afa98bdb2bc52d07887d1af628eba5846e5110da9bf813ba1906139eba01c98", - "id": "fls_8byjmlgum2f3", - "link": "macros.html#fls_8byjmlgum2f3", - "number": "20.1.2:2" + "checksum": "41db8d1e89a48299f2506f2fd5b398d24cc65ebba4aced317857a727822d1c4e", + "id": "fls_4ftfefcotb4g", + "link": "associated-items.html#fls_4ftfefcotb4g", + "number": "10:13" }, { - "checksum": "43358f7409e5fcf8e712e18e535f05897041a0c7ee20dc9c07eaae0a6a5b463c", - "id": "fls_ltdp3zs60dzr", - "link": "macros.html#fls_ltdp3zs60dzr", - "number": "20.1.2:3" + "checksum": "66b5d7677a5542109bcd8f36408c3ed7ec1aca313c4c747fc28557fb2dd3c6c0", + "id": "fls_qb5qpfe0uwk", + "link": "associated-items.html#fls_qb5qpfe0uwk", + "number": "10:14" }, { - "checksum": "fb90822182dbef1ad0b6848a7d2b4784a72127a44df216ec0a2fc1e4f586298c", - "id": "fls_V1WRuzZUWUGj", - "link": "macros.html#fls_V1WRuzZUWUGj", - "number": "20.1.2:4" + "checksum": "96a142348655f7c0d311daed418fc24259286e2108efc574894af02cf2471c36", + "id": "fls_1zlkeb6fz10j", + "link": "associated-items.html#fls_1zlkeb6fz10j", + "number": "10:15" }, { - "checksum": "cc21d8251493e001ac2bf6dc3b6d9869a4525b75ff8bdd4a6b96569f649bd5a7", - "id": "fls_u86j0zm2jshf", - "link": "macros.html#fls_u86j0zm2jshf", - "number": "20.1.2:5" + "checksum": "b5024c3cf8ba9c9ff0e7a6bd05ae96c8f7c64599e2eaec349fae2ca6767f4bab", + "id": "fls_tw8u0cc5867l", + "link": "associated-items.html#fls_tw8u0cc5867l", + "number": "10:16" }, { - "checksum": "decb59aaa3220c66f8fa18342f46b9dbbd8d79ae738262d7cc8d3276f0d6f871", - "id": "fls_h5f8x4jdnvbu", - "link": "macros.html#fls_h5f8x4jdnvbu", - "number": "20.1.2:6" + "checksum": "a13634951f7946c3028793e284d6d7899f718840e70e5b30992ad30aa0b28a82", + "id": "fls_bx7931x4155h", + "link": "associated-items.html#fls_bx7931x4155h", + "number": "10:17" }, { - "checksum": "8cb8cbde345ddbe65efa4ff3d8e5aa2db38eaa6626cdd39220f0a691c264783d", - "id": "fls_hf4gj5pfl437", - "link": "macros.html#fls_hf4gj5pfl437", - "number": "20.1.2:7" + "checksum": "8fb89c0b3f9ad0065b86652933686cc127a11676e7b59759588bf35bbe56a204", + "id": "fls_bnTcCbDvdp94", + "link": "associated-items.html#fls_bnTcCbDvdp94", + "number": "10:18" }, { - "checksum": "e94a58c9be4a3e503fe157cfb77e90b5e89fe37b134ffb1f6f08c47bd52ea5f9", - "id": "fls_tm0w0680wf4x", - "link": "macros.html#fls_tm0w0680wf4x", - "number": "20.1.2:8" + "checksum": "22be2f18e00bb9e898293564455031809a4ffbdba44f030bff8af6c23ada5048", + "id": "fls_N3cdn4lCZ2Bf", + "link": "associated-items.html#fls_N3cdn4lCZ2Bf", + "number": "10:19" }, { - "checksum": "3cdc79fac2c5194ed7084ae18321fb32b4db88a78ed80cc4f4432b4894648505", - "id": "fls_10lsg5212ffb", - "link": "macros.html#fls_10lsg5212ffb", - "number": "20.1.2:9" + "checksum": "9647e6f192f7a123a465887f651e66f32b17c903edd98aa87c48510b16acd40f", + "id": "fls_x564isbhobym", + "link": "associated-items.html#fls_x564isbhobym", + "number": "10:20" }, { - "checksum": "44e5e09eadfa0ef32f2aa42f5d86aefc29a08264af8ea9678ec830ac5d0d919c", - "id": "fls_UnfvR9NB1Nze", - "link": "macros.html#fls_UnfvR9NB1Nze", - "number": "20.1.2:10" + "checksum": "d7e3897e8d4d866fe24ed708a542d480dd49b7432f4e1ab2b6e9f83e66beb79c", + "id": "fls_b6nns7oqvdpm", + "link": "associated-items.html#fls_b6nns7oqvdpm", + "number": "10:21" }, { - "checksum": "ada08a290b60070c6aae793052fba60f6404e5668f5da364e16a178044452d6a", - "id": "fls_Sm4qVsHKYLY2", - "link": "macros.html#fls_Sm4qVsHKYLY2", - "number": "20.1.2:11" + "checksum": "f6efbcd47f77fb69c490956047b8fa92ddd13a1a56c2c82483cd10efd9f4abc8", + "id": "fls_2TRwCz38kuRz", + "link": "associated-items.html#fls_2TRwCz38kuRz", + "number": "10:22" }, { - "checksum": "ea5e5013c9fbf8d7b889cd0fe21718fb395b30a9d7cd9f10b911a150dc32decd", - "id": "fls_Rdvs8Dz6OUU7", - "link": "macros.html#fls_Rdvs8Dz6OUU7", - "number": "20.1.2:12" + "checksum": "ef01e780fac2b6784fa9e17636be1c17d599f8831949c44ad35251b17052c1a3", + "id": "fls_WnsVATJvUdza", + "link": "associated-items.html#fls_WnsVATJvUdza", + "number": "10:23" }, { - "checksum": "e9e82608e910aea5e4b9b881e3bbc4f832130dd93a971ad1d979ae6b3be81ba8", - "id": "fls_UIlj6Csow81w", - "link": "macros.html#fls_UIlj6Csow81w", - "number": "20.1.2:13" + "checksum": "3b7142bcde18f322ffa41b2c57c18446ab40aa8a6612d601b48ec17273ad3d94", + "id": "fls_yyhebj4qyk34", + "link": "associated-items.html#fls_yyhebj4qyk34", + "number": "10:24" }, { - "checksum": "31f297fdd3706a0010aae6400f9a4d0d7368160c1aebd8e60699f9e9fbdf2639", - "id": "fls_yp2XxDv4DzEi", - "link": "macros.html#fls_yp2XxDv4DzEi", - "number": "20.1.2:14" + "checksum": "53bc991f032b9bfaab3cb72189a8ae6d8772250cc7fbaefa3b80bbd25679141f", + "id": "fls_kl9p3ycl5mzf", + "link": "associated-items.html#fls_kl9p3ycl5mzf", + "number": "10:25" }, { - "checksum": "e542a9b407077073772387ef8c3ca43d8afb0cf939f1285a5c47b7d07ad1979d", - "id": "fls_n5TkJKWiDhCD", - "link": "macros.html#fls_n5TkJKWiDhCD", - "number": "20.1.2:15" - } - ], - "title": "Repetition" - }, - { - "id": "fls_wn1i6hzg2ff7", - "informational": false, - "link": "macros.html#procedural-macros", - "number": "20.2", - "paragraphs": [ - { - "checksum": "40ae367af875e89e010e4d22a7cfe13aa5dbaed9fc46011de6b52d138577092f", - "id": "fls_ejbddhggstd2", - "link": "macros.html#fls_ejbddhggstd2", - "number": "20.2:1" + "checksum": "6e869fbccfae9516ee3421a1571bd880ea5ec5dc5aedb65e32f5a6d2977b7955", + "id": "fls_a5prbmuruma4", + "link": "associated-items.html#fls_a5prbmuruma4", + "number": "10:26" }, { - "checksum": "a154977cec887d19447bb9d7566684f5f07b10108feaad22add360f52cade411", - "id": "fls_pcce9gmjpxba", - "link": "macros.html#fls_pcce9gmjpxba", - "number": "20.2:2" + "checksum": "915d230d994ce8e745b1083bb75cb58b37ac8537af7fd5486a86d1314cb353d0", + "id": "fls_vp2ov6ykueue", + "link": "associated-items.html#fls_vp2ov6ykueue", + "number": "10:27" }, { - "checksum": "0f0739a1ed671f541972aa194b63778a35eef1f8a258625357bad1dfb79126ec", - "id": "fls_vtzuplb1p3s", - "link": "macros.html#fls_vtzuplb1p3s", - "number": "20.2:3" + "checksum": "338988e157f9ff9c04c5a8163cf72e90c0400a0e6ccb6b45e6f105a1b75ac855", + "id": "fls_5uf74nvdm64o", + "link": "associated-items.html#fls_5uf74nvdm64o", + "number": "10:28" }, { - "checksum": "b0d421770e39ab6631e15b540881bf6453ecda3a0bbc8c78183bba9be46e6c77", - "id": "fls_mewfehvgm16r", - "link": "macros.html#fls_mewfehvgm16r", - "number": "20.2:4" - } - ], - "title": "Procedural Macros" - }, - { - "id": "fls_2d6bqnpy6tvs", - "informational": false, - "link": "macros.html#function-like-macros", - "number": "20.2.1", - "paragraphs": [ - { - "checksum": "35211e047d31106ec03bc1b7ccba14cee77f4c950b50ddedbd47d9cbe9dc876f", - "id": "fls_utd3zqczix", - "link": "macros.html#fls_utd3zqczix", - "number": "20.2.1:1" + "checksum": "4e9d22ccfb80acfdb452f21eaa41d99e8b68d03dbf8ffcaca69016ad6b224e0c", + "id": "fls_amWtS80fPtza", + "link": "associated-items.html#fls_amWtS80fPtza", + "number": "10:29" }, { - "checksum": "195f49e7c5486c12cd274b338a19d3eaa1a19f99570f7ee0b52a3b1eeb90251e", - "id": "fls_ojr30lf6jfx0", - "link": "macros.html#fls_ojr30lf6jfx0", - "number": "20.2.1:2" + "checksum": "6b3108c40f22ac87f1e375b4907dc7e2565fc0fdb570d5e8e87d35f36da764fb", + "id": "fls_Cu8FWrisrqz1", + "link": "associated-items.html#fls_Cu8FWrisrqz1", + "number": "10:30" }, { - "checksum": "1d126893f14def3d9f5d85d0190f84dc53a460ce5e6da350afffa20da8714d0b", - "id": "fls_ljkjmegynhiy", - "link": "macros.html#fls_ljkjmegynhiy", - "number": "20.2.1:3" + "checksum": "c67bd6dc496b87f14e94eac06fb1dd53a785b33748660d90a3455b63b8f15dea", + "id": "fls_oy92gzxgc273", + "link": "associated-items.html#fls_oy92gzxgc273", + "number": "10:31" }, { - "checksum": "1a1931bdd5b2a64999e14fa18b646001ec64e8e677c906ce614ba221a5dbf2c6", - "id": "fls_8a8qhzjw5hax", - "link": "macros.html#fls_8a8qhzjw5hax", - "number": "20.2.1:4" + "checksum": "1d3e0acf8e8b25427b6a4179ef6f36b9c9be2060806f8a4532c85d38970a854f", + "id": "fls_WXnCWfJGoQx3", + "link": "associated-items.html#fls_WXnCWfJGoQx3", + "number": "10:32" }, { - "checksum": "589f04afe8c234ee2c179f3f0931eaded30292da86287719098525edb953002c", - "id": "fls_ofzql79i9if", - "link": "macros.html#fls_ofzql79i9if", - "number": "20.2.1:5" + "checksum": "f1864701261d89610bc085bf20379aabb9a7f867190c51deee3b5ab4e0731ab4", + "id": "fls_OaszUw4IFobz", + "link": "associated-items.html#fls_OaszUw4IFobz", + "number": "10:33" }, { - "checksum": "67268d716ea7849d7125350436f4e4a6d5d38effc37ae5216408b2690ab8d033", - "id": "fls_j1wsyzip2qb3", - "link": "macros.html#fls_j1wsyzip2qb3", - "number": "20.2.1:6" + "checksum": "a56737ff1c2a54b21dd0325776e037cb33b634928cad055fc83c168e8513e490", + "id": "fls_Wd2FZRomB5yn", + "link": "associated-items.html#fls_Wd2FZRomB5yn", + "number": "10:34" }, { - "checksum": "ba0099799988b2f6d17799a6f6f9c33b477183b8eeec921be942c1c46fe6e668", - "id": "fls_etyo9bmzxby6", - "link": "macros.html#fls_etyo9bmzxby6", - "number": "20.2.1:7" + "checksum": "8c0ecdddfa88a992b09009b5167f4dc68b5f53f241a872c2a1e85f8eb610f1e5", + "id": "fls_lcEyToYIlcmf", + "link": "associated-items.html#fls_lcEyToYIlcmf", + "number": "10:35" }, { - "checksum": "7de42cd159846bf2a84aba1bcaf2c1f39e0a6092c7db776d8abb5854419ee2c1", - "id": "fls_mkl9b38m0sf1", - "link": "macros.html#fls_mkl9b38m0sf1", - "number": "20.2.1:8" + "checksum": "2313193d9ba1cea774bf3d4a520130dc60870cdf9f64464a5745c228f3b647fa", + "id": "fls_IKSPR7ZQMErU", + "link": "associated-items.html#fls_IKSPR7ZQMErU", + "number": "10:36" }, { - "checksum": "6e54b9a8b956ed9a206672b6145a346a9d87f0734de85341ef04814529bcee31", - "id": "fls_lfmb22bfnrye", - "link": "macros.html#fls_lfmb22bfnrye", - "number": "20.2.1:9" + "checksum": "a9ddbf6bc0c06113d12f75ca1fe4401527ac5ff2a0384f375f53b2b7eedc084d", + "id": "fls_oHxzyaiT7Qm6", + "link": "associated-items.html#fls_oHxzyaiT7Qm6", + "number": "10:37" }, { - "checksum": "78e85689e473377a80352ef6fe022993d405de27b2c6c0393cc70edc6d40e0e8", - "id": "fls_fbgal48cgj44", - "link": "macros.html#fls_fbgal48cgj44", - "number": "20.2.1:10" + "checksum": "17b593b8d0cb4dacc6eb9d143a7e49048a6708d07c811e2595e8cfbb89483827", + "id": "fls_znfADVeOvXHD", + "link": "associated-items.html#fls_znfADVeOvXHD", + "number": "10:38" } ], - "title": "Function-like Macros" - }, + "title": "Associated Items" + } + ], + "title": "Associated Items" + }, + { + "informational": true, + "link": "changelog.html", + "sections": [], + "title": "Changelog" + }, + { + "informational": false, + "link": "concurrency.html", + "sections": [ { - "id": "fls_o8s3r7m90q59", + "id": "fls_3v733mnewssy", "informational": false, - "link": "macros.html#derive-macros", - "number": "20.2.2", + "link": "concurrency.html", + "number": "17", "paragraphs": [ { - "checksum": "aeee3f491d3c18c862f68c8f41eed353a109270ae1f717f9d5620bd9e311fbc6", - "id": "fls_e5x92q2rq8a0", - "link": "macros.html#fls_e5x92q2rq8a0", - "number": "20.2.2:1" - }, - { - "checksum": "5242e5a075162a366a5adf0a32e20e73a4ed4c93a3b63541a8201de95af0cbfe", - "id": "fls_ldw75sy5uj7p", - "link": "macros.html#fls_ldw75sy5uj7p", - "number": "20.2.2:2" - }, - { - "checksum": "77dd4bd0709e93df0c9294bd185bd0cb487a9e1991042f0cb0ecffb5df13bc60", - "id": "fls_7gcnui9beky", - "link": "macros.html#fls_7gcnui9beky", - "number": "20.2.2:3" + "checksum": "58e23287dc1c3ffbe6b2c4672501d222916a129910befc8fadc364de5171c351", + "id": "fls_opt7v0mopxc8", + "link": "concurrency.html#fls_opt7v0mopxc8", + "number": "17:1" }, { - "checksum": "7fa2a69e91d4ee885cc21d70cb2d326b60305f03bba8a17a36209a53457749bb", - "id": "fls_ef30ropg7dhx", - "link": "macros.html#fls_ef30ropg7dhx", - "number": "20.2.2:4" + "checksum": "60786d66db9926333e9768b0cc8aa490ff764f600fdbdb389912f9e85add0678", + "id": "fls_tx4b8r6i93n4", + "link": "concurrency.html#fls_tx4b8r6i93n4", + "number": "17:2" }, { - "checksum": "700ee188839152335fdb7422422a37ae9cb40c8bb9bc9940787cd260d61e7276", - "id": "fls_mo00vqm9xfqc", - "link": "macros.html#fls_mo00vqm9xfqc", - "number": "20.2.2:5" - }, + "checksum": "5a06090908c3a726a0df6203b3e9f7c460c35609c758b6db09cf2d5c5c753c8e", + "id": "fls_isypweqewe78", + "link": "concurrency.html#fls_isypweqewe78", + "number": "17:3" + } + ], + "title": "Concurrency" + }, + { + "id": "fls_eiw4by8z75di", + "informational": false, + "link": "concurrency.html#send-and-sync", + "number": "17.1", + "paragraphs": [ { - "checksum": "d8d72a951c13aa5fe8714a16539462fbbaf7ead2c1ad8ecd97cf3071f5db6ab8", - "id": "fls_gr9wugeqyb3b", - "link": "macros.html#fls_gr9wugeqyb3b", - "number": "20.2.2:6" + "checksum": "18cfbb89a43fe476f2c0e5f3299b552fe8abafac2b759f081654338c7597f688", + "id": "fls_n5l17mlglq11", + "link": "concurrency.html#fls_n5l17mlglq11", + "number": "17.1:1" }, { - "checksum": "6a638cc0c5ef1383ed2612ba3ab85735c70a35cbabcde5839d61b426d3411406", - "id": "fls_npnze2cg8ae", - "link": "macros.html#fls_npnze2cg8ae", - "number": "20.2.2:7" + "checksum": "2c6d410f6594c15bcbb289be0b5a44ae8b19c478583342e5405aef3abbdb8951", + "id": "fls_2jujsujpjp3w", + "link": "concurrency.html#fls_2jujsujpjp3w", + "number": "17.1:2" }, { - "checksum": "704edbd352cfa964eac3571f6d328a271b59ec3801b0d4deb1b3ff167d9edcbf", - "id": "fls_w2h4lk6bmht", - "link": "macros.html#fls_w2h4lk6bmht", - "number": "20.2.2:8" + "checksum": "11baae79e2b2bec1ba1dccff05f08bedf2b603f0b2748f345dafa59243aab72f", + "id": "fls_cax6fe4em23k", + "link": "concurrency.html#fls_cax6fe4em23k", + "number": "17.1:3" }, { - "checksum": "f217c6bc6e853a2b8556dbc0b41314003eaa542eca75e8a17d1573264d4e2151", - "id": "fls_x96a0xzcyrko", - "link": "macros.html#fls_x96a0xzcyrko", - "number": "20.2.2:9" + "checksum": "14b867fe8baaef726910084c46d92291fffbfe3b462586c4a7ecd134f8152d96", + "id": "fls_4ypqdehn7b0v", + "link": "concurrency.html#fls_4ypqdehn7b0v", + "number": "17.1:4" }, { - "checksum": "a284d52790bdad317fdaac6d6bb30541a4dba804471aa74bf317849a3eba4ca2", - "id": "fls_caa16usjxryg", - "link": "macros.html#fls_caa16usjxryg", - "number": "20.2.2:10" + "checksum": "3e1f608593a27c63b70db641f71e71a331b116739b77b1bc9ce207481f0dd8be", + "id": "fls_dekskhk4g895", + "link": "concurrency.html#fls_dekskhk4g895", + "number": "17.1:5" }, { - "checksum": "81811154b26e520b237186a9289476a52adca1dac91598bea29f6d2f8ad5c0af", - "id": "fls_H5ipqqlH3pJh", - "link": "macros.html#fls_H5ipqqlH3pJh", - "number": "20.2.2:11" + "checksum": "37c5ee82be489b2f1bd2eb9efdd02a9e9734ac2867c6bfc04f37f9877a9ab760", + "id": "fls_y0iqr5ibnbfe", + "link": "concurrency.html#fls_y0iqr5ibnbfe", + "number": "17.1:6" }, { - "checksum": "99a2dde0b9166346d32d075006f6fff461527d79389470e64b255fb7a9f63733", - "id": "fls_mobky5ck1mi", - "link": "macros.html#fls_mobky5ck1mi", - "number": "20.2.2:12" + "checksum": "a206190ea6bc9a6672886ed000599cd972f4abec22f754db4c4d9acbc6754a4c", + "id": "fls_zgemofbs5q2x", + "link": "concurrency.html#fls_zgemofbs5q2x", + "number": "17.1:7" } ], - "title": "Derive Macros" + "title": "Send and Sync" }, { - "id": "fls_4vjbkm4ceymk", + "id": "fls_vyc9vcuamlph", "informational": false, - "link": "macros.html#attribute-macros", - "number": "20.2.3", + "link": "concurrency.html#atomics", + "number": "17.2", "paragraphs": [ { - "checksum": "a507e2ee0a755f9e9bef9483a14b746466426c98185a2634629bff10acf93070", - "id": "fls_l3epi1dqpi8o", - "link": "macros.html#fls_l3epi1dqpi8o", - "number": "20.2.3:1" + "checksum": "aca31f1758bf227f88cdd68e64370874768f51dd10e9211ec344537b1d7ba6cd", + "id": "fls_3pjla9s93mhd", + "link": "concurrency.html#fls_3pjla9s93mhd", + "number": "17.2:1" }, { - "checksum": "ad536eef612c80a23662ccb5976e09fc7a7cd564b3099362de958cb078de4636", - "id": "fls_3sublbi9bz7k", - "link": "macros.html#fls_3sublbi9bz7k", - "number": "20.2.3:2" + "checksum": "ce72ccfc2f3e59228fae42edd3521e8333bddb6951896be2347ec2387355262a", + "id": "fls_wn4ynaio8u47", + "link": "concurrency.html#fls_wn4ynaio8u47", + "number": "17.2:2" }, { - "checksum": "0e6bc1537645755a80173fb2e360056fe473f07b84fa83e420a2119802b5688e", - "id": "fls_eb8jxl70wmeh", - "link": "macros.html#fls_eb8jxl70wmeh", - "number": "20.2.3:3" + "checksum": "f04e33b2cedb200500a0733c23e46638bb219c9422ea4796905e57f57c0bc457", + "id": "fls_q7mn6pdd8bix", + "link": "concurrency.html#fls_q7mn6pdd8bix", + "number": "17.2:3" }, { - "checksum": "65f907abc0b4741ed3d04dfb54bf57f7733475bd00f7eb52a681cf2df6dd26cf", - "id": "fls_7ugtmobgb2t9", - "link": "macros.html#fls_7ugtmobgb2t9", - "number": "20.2.3:4" + "checksum": "fa9d632297e07c71321455d7ac82181279f4349b2878ed60e0088fb76453fa21", + "id": "fls_jx0784jzxy00", + "link": "concurrency.html#fls_jx0784jzxy00", + "number": "17.2:4" }, { - "checksum": "ef5cb13d4e805e4e81816f26647b316792ceb249829578bfaa57070acb415879", - "id": "fls_y700oif45wum", - "link": "macros.html#fls_y700oif45wum", - "number": "20.2.3:5" + "checksum": "17b8684e84e891ce119a119dcdf3b5cc1df0bd9132fc39002ef445333a7386cf", + "id": "fls_vzuwnpx7mt08", + "link": "concurrency.html#fls_vzuwnpx7mt08", + "number": "17.2:5" }, { - "checksum": "a67726cd1b3c0fa718910126228b74a584cd8d14685cff5a51c66aecafa25a2e", - "id": "fls_hhsf1a9p6o55", - "link": "macros.html#fls_hhsf1a9p6o55", - "number": "20.2.3:6" + "checksum": "3df9c673e62071885a6938566ce59609242470b1c63272f358bf5f999f2324fe", + "id": "fls_cpcd0vexfbhj", + "link": "concurrency.html#fls_cpcd0vexfbhj", + "number": "17.2:6" }, { - "checksum": "85bf012ed3c7f1fe9e724f38a2d891b1697ec17ecde0a3277e037fe496069d6a", - "id": "fls_4g932k8ueyqp", - "link": "macros.html#fls_4g932k8ueyqp", - "number": "20.2.3:7" + "checksum": "9c9a349252f3cb141e008067381e5a19c45f3b1738132bfebc02583b25c36873", + "id": "fls_jt7rfq9atbiv", + "link": "concurrency.html#fls_jt7rfq9atbiv", + "number": "17.2:7" }, { - "checksum": "301c590c9b06bc5965288c7611ce54546237f2ab270ad8d08ada06606d3129bf", - "id": "fls_f5qy1pnlbpng", - "link": "macros.html#fls_f5qy1pnlbpng", - "number": "20.2.3:8" + "checksum": "72763ab828f9c57a7ba7ae57c095d30f4ef5bb34e5319df45f176f6570588e00", + "id": "fls_2hqmfwswc6k", + "link": "concurrency.html#fls_2hqmfwswc6k", + "number": "17.2:8" }, { - "checksum": "db387e1df17fe1b34f0785313cbd032471da2d489046fbbd57783da324ddd87b", - "id": "fls_rzn48xylk4yj", - "link": "macros.html#fls_rzn48xylk4yj", - "number": "20.2.3:9" + "checksum": "051b06b8e012ef9e9c02369b0cb8074a99acd99781938a746c9de16fa2ce4dcd", + "id": "fls_5ab2sw3gwmt3", + "link": "concurrency.html#fls_5ab2sw3gwmt3", + "number": "17.2:9" }, { - "checksum": "ae2d00ba71cdeeebcbc5ba13db842b607d9367375f608225d84eeb55ce0afb90", - "id": "fls_78400zh02sdq", - "link": "macros.html#fls_78400zh02sdq", - "number": "20.2.3:10" + "checksum": "a8ab421b6894b056917c74396d9ac843c73dad27e12268a43fad03a877c8009c", + "id": "fls_w2mw833g28eb", + "link": "concurrency.html#fls_w2mw833g28eb", + "number": "17.2:10" }, { - "checksum": "130f778e0740bb2eac48f0b045d7c7d7854a7f114684fd426c548a5289d530b9", - "id": "fls_eyesmvuwpjn1", - "link": "macros.html#fls_eyesmvuwpjn1", - "number": "20.2.3:11" + "checksum": "f182e5451b68bf0b4094178052d33c3a59b7d40c5282e4bf77de423b44b77f8d", + "id": "fls_mjq1l1y0vmz4", + "link": "concurrency.html#fls_mjq1l1y0vmz4", + "number": "17.2:11" }, { - "checksum": "a9469713d62e5ab294be5b5463896a680482fe532ef2db60c9a17dd040147917", - "id": "fls_fku5beu3mr4c", - "link": "macros.html#fls_fku5beu3mr4c", - "number": "20.2.3:12" + "checksum": "3b07334d52a95685b653abaecb58f071b99276e911b0f4bc4ab80cda4ef4ff1f", + "id": "fls_906978wtss6n", + "link": "concurrency.html#fls_906978wtss6n", + "number": "17.2:12" }, { - "checksum": "1f2493e72099adeeabdbdb5af0e9a9021fe478c82c788215781e06312b59c4e4", - "id": "fls_knjsslplv5ri", - "link": "macros.html#fls_knjsslplv5ri", - "number": "20.2.3:13" - } - ], - "title": "Attribute Macros" - }, - { - "id": "fls_vnvt40pa48n8", - "informational": false, - "link": "macros.html#macro-invocation", - "number": "20.3", - "paragraphs": [ - { - "checksum": "2d88086f369da9c00a710d6478ff80775749dcb789b8f28b671fa96d22b5c01e", - "id": "fls_wushtmw9qt3y", - "link": "macros.html#fls_wushtmw9qt3y", - "number": "20.3:1" + "checksum": "5b5d7f2d72de32a5c8c9e36b4111885655abad9b351cd2bdd65dac06dcdf6660", + "id": "fls_4urmnh4mfehl", + "link": "concurrency.html#fls_4urmnh4mfehl", + "number": "17.2:13" }, { - "checksum": "974d06554ff28647c4f903fa86768f1e175bc518e3c538c7d26f09ca32580f3a", - "id": "fls_snpxxcqhtjfv", - "link": "macros.html#fls_snpxxcqhtjfv", - "number": "20.3:2" + "checksum": "b14552f886fa68d1904ccff6320f2d395f90fee027a70f30588a9ba68a53b172", + "id": "fls_2qkrcd5eovpe", + "link": "concurrency.html#fls_2qkrcd5eovpe", + "number": "17.2:14" }, { - "checksum": "9331eac9772bb3030cfa592afd0542c15f135b1445df9e764e483111e6e799e5", - "id": "fls_6v06zvi1ctub", - "link": "macros.html#fls_6v06zvi1ctub", - "number": "20.3:3" + "checksum": "1bf7e1661a0f7e7762a5549fe82988a4afbe4436720588214b9ad3857ecc02a7", + "id": "fls_cry1e78gp19q", + "link": "concurrency.html#fls_cry1e78gp19q", + "number": "17.2:15" + } + ], + "title": "Atomics" + }, + { + "id": "fls_mtuwzinpfvkl", + "informational": false, + "link": "concurrency.html#asynchronous-computation", + "number": "17.3", + "paragraphs": [ + { + "checksum": "dfbc3bd3bcdae5a3f76ab60f5c1fbcb25458c3975778746db7eea952015a2f79", + "id": "fls_g40xp4andj5g", + "link": "concurrency.html#fls_g40xp4andj5g", + "number": "17.3:1" }, { - "checksum": "7b9182c06cf00fd455b1965ab8a30dda3df6d618c935ed284f6ca291707e0599", - "id": "fls_338rmbazl67o", - "link": "macros.html#fls_338rmbazl67o", - "number": "20.3:4" + "checksum": "0a94602025c891b37d5f98e1ccd525f0cd66c06df02df85b06336668d0711cbe", + "id": "fls_fte085hi1yqj", + "link": "concurrency.html#fls_fte085hi1yqj", + "number": "17.3:2" }, { - "checksum": "f0d9a1f0b0eab822233723ad23f52e94ac8af7a075116203900e841cce37b8ed", - "id": "fls_lrr7gg8tian", - "link": "macros.html#fls_lrr7gg8tian", - "number": "20.3:5" + "checksum": "306a3b2eabc2ff31f77f6446060185578f9427fcc497d1588cb51a4c94ae4819", + "id": "fls_7muubin2wn1v", + "link": "concurrency.html#fls_7muubin2wn1v", + "number": "17.3:3" }, { - "checksum": "2f1f05665a7725eb786fccfba3d43fc78ce77ef15cfcf47af07e6cb0c9b8b6ff", - "id": "fls_8qxwwf4trnl", - "link": "macros.html#fls_8qxwwf4trnl", - "number": "20.3:6" + "checksum": "d6389f23a8eff8343451429941d835b9e1b00fa45cf260112041467021172752", + "id": "fls_ftzey2156ha", + "link": "concurrency.html#fls_ftzey2156ha", + "number": "17.3:4" + } + ], + "title": "Asynchronous Computation" + } + ], + "title": "Concurrency" + }, + { + "informational": true, + "link": "background.html", + "sections": [ + { + "id": "fls_mlo6b3ewf50j", + "informational": false, + "link": "background.html", + "number": "F", + "paragraphs": [ + { + "checksum": "badbea10d9e5d4d6735483d5537945203ca32a824fe13ae5b80a7355f54d8a1c", + "id": "fls_p7Jiyppmg0FU", + "link": "background.html#fls_p7Jiyppmg0FU", + "number": "F:1" }, { - "checksum": "c0738db650ac62715e9597df801371e725fa1a1faf26794dba7b764fe0e1c70b", - "id": "fls_8z1sgtvchhhw", - "link": "macros.html#fls_8z1sgtvchhhw", - "number": "20.3:7" + "checksum": "0a1f874197f6ef38b9e620caf7600f0f4706708cc4b997fc79096eab9e0dc405", + "id": "fls_Uvf5tHsKSV19", + "link": "background.html#fls_Uvf5tHsKSV19", + "number": "F:2" }, { - "checksum": "27e64c85ad3e00def820e0904a4aa2d4beaac06c9fb0feccce519d3b3d81492d", - "id": "fls_d9w3dn2yn7mo", - "link": "macros.html#fls_d9w3dn2yn7mo", - "number": "20.3:8" + "checksum": "c8c5400f5c46557eb1d3753398731ab78073710aa598c36e1ca00db60fe5d4e7", + "id": "fls_J7ZI7mBXffzZ", + "link": "background.html#fls_J7ZI7mBXffzZ", + "number": "F:3" }, { - "checksum": "a5ebe407976f7eb1733d25250fff1be138a66bf342fac15d3226e09e6ffbcb31", - "id": "fls_1tftbd91yfpd", - "link": "macros.html#fls_1tftbd91yfpd", - "number": "20.3:9" + "checksum": "bfe08f4cf50f227c85776d3d0c57a39f9e9157bd89f18d0a2d992fc499c52a77", + "id": "fls_ffv8XSbBOMkR", + "link": "background.html#fls_ffv8XSbBOMkR", + "number": "F:4" } ], - "title": "Macro Invocation" + "title": "FLS Background" }, { - "id": "fls_wjldgtio5o75", + "id": "fls_umsvnulqzd99", "informational": false, - "link": "macros.html#macro-expansion", - "number": "20.4", + "link": "background.html#acknowledging-ferrous-systems", + "number": "F.1", "paragraphs": [ { - "checksum": "8026222297277fbad4ed1549d49f0c3e75d7e5157adbb94d90360ceb716f2a3d", - "id": "fls_xscdaxvs4wx4", - "link": "macros.html#fls_xscdaxvs4wx4", - "number": "20.4:1" - }, - { - "checksum": "81bdfdb7218e324273181015cf2a0058601e53a6f07875e9c55f6b9ccd110600", - "id": "fls_nz5stwcc41gk", - "link": "macros.html#fls_nz5stwcc41gk", - "number": "20.4:2" - }, - { - "checksum": "bf49814afca1afc4a1ea61da7acaf9bb0ac3da4e8a3d40a8a0ad770017aaf4e2", - "id": "fls_40xq8Ri1OMZZ", - "link": "macros.html#fls_40xq8Ri1OMZZ", - "number": "20.4:3" + "checksum": "cdcc8b0a0f82ccc5266f4b961c6cda964f76470688e198bd6714fc14e1de17dc", + "id": "fls_lmlLUAdtfCo5", + "link": "background.html#fls_lmlLUAdtfCo5", + "number": "F.1:1" }, { - "checksum": "f2e3b68bee58b99bd43a9238014702fed0da098493aea6cd5ee8c35d689b2cfb", - "id": "fls_76prdp6k1fga", - "link": "macros.html#fls_76prdp6k1fga", - "number": "20.4:4" - }, + "checksum": "d38387d8b265d10e97153f7ab7bd680dbe5cbbb2af4db501ff7a00f57c4a27b7", + "id": "fls_FZRrMT5AYsAQ", + "link": "background.html#fls_FZRrMT5AYsAQ", + "number": "F.1:2" + } + ], + "title": "Acknowledging Ferrous Systems" + } + ], + "title": "FLS Background" + }, + { + "informational": false, + "link": "attributes.html", + "sections": [ + { + "id": "fls_gvwd0kf72jt", + "informational": false, + "link": "attributes.html", + "number": "13", + "paragraphs": [ { - "checksum": "74b6ccc123fd68357e50a1ba7a24fcfb694a68271bd2c7156e558945c62bb744", - "id": "fls_76u274l4kew8", - "link": "macros.html#fls_76u274l4kew8", - "number": "20.4:5" + "checksum": "eff2f3d8085ca2ad43e10aa74acd5fb4de1a71393ec1e3bb69b4da6eca0bac43", + "id": "fls_rnzxj1t0hehl", + "link": "attributes.html#fls_rnzxj1t0hehl", + "number": "13:1" }, { - "checksum": "41396541fe1131f8e3a05e47eb4dc795aca82ca7744db28c5808acbbcec8c548", - "id": "fls_lakpily1zwfl", - "link": "macros.html#fls_lakpily1zwfl", - "number": "20.4:6" + "checksum": "dd933aff5339727903dca2ff3f8484b0c000fdda5ab4ef729319db3dd76c9f10", + "id": "fls_yd0ehw5csaur", + "link": "attributes.html#fls_yd0ehw5csaur", + "number": "13:2" }, { - "checksum": "145096281559894c5fdd9c34f4c59020e8018a542a295c4718a2158eb1c24db6", - "id": "fls_y20pmwo3v3uu", - "link": "macros.html#fls_y20pmwo3v3uu", - "number": "20.4:7" + "checksum": "08694050309ada68c0f9d36f4cf7bf427a740fb2dd1175bd0acf555bedcf4729", + "id": "fls_8o6vmzbw1b1j", + "link": "attributes.html#fls_8o6vmzbw1b1j", + "number": "13:3" }, { - "checksum": "73e7c3d42509b26c6836a37c592ce6b5f8b4b8d51bdd13b1e69c781dbdb074ac", - "id": "fls_nsh2vwx8oiw", - "link": "macros.html#fls_nsh2vwx8oiw", - "number": "20.4:8" - }, + "checksum": "80ab4794df7b97c498adf667d822f03e67802cfa46ac84a9c38967818009cfd1", + "id": "fls_9TMRVlQwAdTB", + "link": "attributes.html#fls_9TMRVlQwAdTB", + "number": "13:4" + } + ], + "title": "Attributes" + }, + { + "id": "fls_i52cujixq9qs", + "informational": false, + "link": "attributes.html#attribute-properties", + "number": "13.1", + "paragraphs": [ { - "checksum": "2e54f1caa1c75bb8c551cb037444610dab84c77e38bccd44a64d81885462bfb0", - "id": "fls_tu6kmwm4v9nj", - "link": "macros.html#fls_tu6kmwm4v9nj", - "number": "20.4:9" + "checksum": "37140f4cac4f331316da767c72dc2e92fb5dfe383af30c5eb742a269163ccd01", + "id": "fls_p4potvq7x532", + "link": "attributes.html#fls_p4potvq7x532", + "number": "13.1:1" }, { - "checksum": "3b01afcc0d4c9f2696ea110f2556ea0c37503ac92544dd77a5853e2bc4cc735c", - "id": "fls_3zn4dz19nyvq", - "link": "macros.html#fls_3zn4dz19nyvq", - "number": "20.4:10" + "checksum": "e3cc14af5a12637d401f768630f9f0596ea7cd5c05458d5e49c6242fc7e178d8", + "id": "fls_xk7lb2g02sy7", + "link": "attributes.html#fls_xk7lb2g02sy7", + "number": "13.1:2" }, { - "checksum": "39601be41eaf38c2fe68a562e68628c4c897e8c99f95ec75e583dbefab3af157", - "id": "fls_t89sw6az99z7", - "link": "macros.html#fls_t89sw6az99z7", - "number": "20.4:11" + "checksum": "cebd4af12f069c0f4ce742eabdd258723662de0e0f1065a6bcfe8772e5e7194b", + "id": "fls_q8wl7pidx2za", + "link": "attributes.html#fls_q8wl7pidx2za", + "number": "13.1:3" }, { - "checksum": "48dbce0bae72db846dd146bad2ff656a10cf5dd99c403720ad8a65ff71bf965e", - "id": "fls_417hvhvj2554", - "link": "macros.html#fls_417hvhvj2554", - "number": "20.4:12" + "checksum": "3c8df296f170a3b399899cfa57fc5da2cb24c804bc939039d3e9458c342b6334", + "id": "fls_jottio69o9e7", + "link": "attributes.html#fls_jottio69o9e7", + "number": "13.1:4" }, { - "checksum": "10c68086f5de7156157c189ebd2864036d21287d9219fdce6f5e9df7658f3e2a", - "id": "fls_nNrs4EC3ff5T", - "link": "macros.html#fls_nNrs4EC3ff5T", - "number": "20.4:13" + "checksum": "9ea853c8e48bec469214a2d9b52929d6510733dbfd7da35ad72428a27b205528", + "id": "fls_gzyx9lfi5pvd", + "link": "attributes.html#fls_gzyx9lfi5pvd", + "number": "13.1:5" }, { - "checksum": "303da91f4606ee986ceddcb3a6dd2990ea1a5d6f4d8ff9eba1411b05f9cfa29c", - "id": "fls_srtqkdceaz5t", - "link": "macros.html#fls_srtqkdceaz5t", - "number": "20.4:14" + "checksum": "b4754f2c355f2072357c0a755bb404f6751667b43571d332db5c965114f6b906", + "id": "fls_elsfqsiqor1y", + "link": "attributes.html#fls_elsfqsiqor1y", + "number": "13.1:6" }, { - "checksum": "57347ca4d51ad58880a851280bfb8d8e529e1b9951a52ca4929e47b94f19be4e", - "id": "fls_mi92etjtpamu", - "link": "macros.html#fls_mi92etjtpamu", - "number": "20.4:15" + "checksum": "ddc3357e11fde52528c561fe21918b1245bd5e8f4b51c556ad29d044506d4dd5", + "id": "fls_4xu1rwecd9au", + "link": "attributes.html#fls_4xu1rwecd9au", + "number": "13.1:7" }, { - "checksum": "9a60784d14b99e43a10674a97c9f853c1fda603311694eaa435886353f7102ab", - "id": "fls_n8beqlt54rhy", - "link": "macros.html#fls_n8beqlt54rhy", - "number": "20.4:16" - }, + "checksum": "f304103b55d7d87cca0e19cc4a96ed5406fa81e32277ba35a91b20c6ac85614f", + "id": "fls_n3737i320qum", + "link": "attributes.html#fls_n3737i320qum", + "number": "13.1:8" + } + ], + "title": "Attribute Properties" + }, + { + "id": "fls_ahmnqhm8anlb", + "informational": false, + "link": "attributes.html#built-in-attributes", + "number": "13.2", + "paragraphs": [ { - "checksum": "04d7c36c59b8db5f9cd9d437097d1287b1462b3d1a28b1af866979139758cf22", - "id": "fls_vd3dzvr6re19", - "link": "macros.html#fls_vd3dzvr6re19", - "number": "20.4:17" + "checksum": "8831d99bc357aecc925ce16cd58e47b873e3b06390d5fb7a155f553383be820f", + "id": "fls_92tqo8uas8kd", + "link": "attributes.html#fls_92tqo8uas8kd", + "number": "13.2:1" }, { - "checksum": "8738a7304a24b50b3140d449d1bfca2c9baf6da9f0d078a51401795554fd4474", - "id": "fls_l8j2jiuuao4f", - "link": "macros.html#fls_l8j2jiuuao4f", - "number": "20.4:18" + "checksum": "819b429cae29c60933aa33ed73c325f87704b027ac0198d05fae9f4bf69e4f9e", + "id": "fls_bxucstrfcco8", + "link": "attributes.html#fls_bxucstrfcco8", + "number": "13.2:2" }, { - "checksum": "d26201e9ec1b8ce4e8d0ce918281c597b4207dd5c717ab9d2f690dc2d3fa69f6", - "id": "fls_xvemyqj5gc6g", - "link": "macros.html#fls_xvemyqj5gc6g", - "number": "20.4:19" + "checksum": "44b470444d5c0ced9e021ec324c2d117754a70bf3f44d00e5303fef3bee9f401", + "id": "fls_wle815gb9ai2", + "link": "attributes.html#fls_wle815gb9ai2", + "number": "13.2:3" }, { - "checksum": "9cd6b3fdd12de5819ef5f034cd512142d9ea7275dd351bb3364534aede42230d", - "id": "fls_stseor6tln22", - "link": "macros.html#fls_stseor6tln22", - "number": "20.4:20" + "checksum": "2e27368f11256b44dd2ff0e2e21daa255d6bed7e9e9c923ff8043070e7560521", + "id": "fls_tvn08dtuilue", + "link": "attributes.html#fls_tvn08dtuilue", + "number": "13.2:4" }, { - "checksum": "785c20cd4ee512f170f8821ae84ac3e0164356afb744c5eb7766d1b428f43f95", - "id": "fls_u11o90szy68s", - "link": "macros.html#fls_u11o90szy68s", - "number": "20.4:21" + "checksum": "6269ed848875ae982e4a9072782f1cfa3780e55c8ea94a205bbb145084c27786", + "id": "fls_eOJS3mxa9xgu", + "link": "attributes.html#fls_eOJS3mxa9xgu", + "number": "13.2:5" }, { - "checksum": "42ff8c1ac6cc0dad7129ac746debad5fa96d773994a30fdf5b71496e0fe894be", - "id": "fls_qi5kyvj1e8th", - "link": "macros.html#fls_qi5kyvj1e8th", - "number": "20.4:22" + "checksum": "2f27a78e70b3ce7bcc2fa8fbd9d7e48bcf5f81303e22b4473c9ad67be8183e5c", + "id": "fls_q4c023zdsfgn", + "link": "attributes.html#fls_q4c023zdsfgn", + "number": "13.2:6" }, { - "checksum": "75411eced63ec88091cc61ad48545c56af50d13cdfbb58da468cfcd471b1d753", - "id": "fls_vqIZaEl4EKu5", - "link": "macros.html#fls_vqIZaEl4EKu5", - "number": "20.4:23" + "checksum": "baed00b7e77547d373721bf0e05d646d88cb96ded2722b7505577f118cac0b81", + "id": "fls_xtu3p0kzwn7b", + "link": "attributes.html#fls_xtu3p0kzwn7b", + "number": "13.2:7" }, { - "checksum": "8336e9a3dc539d94ebc4c3f4d1db0a002daac4a11ed04f66f0c8944f7c59dadc", - "id": "fls_grtiwf7q8jah", - "link": "macros.html#fls_grtiwf7q8jah", - "number": "20.4:24" + "checksum": "a1e7b39f6c99e32a187f228c19c2009de7fe6fdb93f751ce5336f2008300d9e9", + "id": "fls_gxxbf6eag3et", + "link": "attributes.html#fls_gxxbf6eag3et", + "number": "13.2:8" }, { - "checksum": "862660c9a7927486f4ab97a5219efca956468b25a372e7f0b8778d938fea614f", - "id": "fls_tbe2qq7whq10", - "link": "macros.html#fls_tbe2qq7whq10", - "number": "20.4:25" + "checksum": "d49e8eff84e5ce88a93c8e6b4f4a4ba514b0028bb3d1b7d17254dc7f599ddb15", + "id": "fls_87o6n9et9jio", + "link": "attributes.html#fls_87o6n9et9jio", + "number": "13.2:9" }, { - "checksum": "56c2e17cefa41716653ddebd7376d5e765866b6b48613007cbd6fa266ec588a1", - "id": "fls_my93neopj9x0", - "link": "macros.html#fls_my93neopj9x0", - "number": "20.4:26" + "checksum": "8bdce4eb7ab793087e487d41f14318daea55698317ec8c2b834f93716676d8ba", + "id": "fls_ui0i3rpt5v5u", + "link": "attributes.html#fls_ui0i3rpt5v5u", + "number": "13.2:10" }, { - "checksum": "6f0481d9039a52034b6e9c95142a26b01f4af587546d9d2ca76ccdcdce2cf14c", - "id": "fls_zat7kwi5vc5c", - "link": "macros.html#fls_zat7kwi5vc5c", - "number": "20.4:27" + "checksum": "02a0c085b317b40311d3351f46a1962249d001ea8178f149b1db9a76bfa770f6", + "id": "fls_6utorag4adlv", + "link": "attributes.html#fls_6utorag4adlv", + "number": "13.2:11" }, { - "checksum": "4ed573b9e2ba6991e0c8cd77151c8f7fb439090236dff6dcba9b8dd9d06d2604", - "id": "fls_tjn92evtlflq", - "link": "macros.html#fls_tjn92evtlflq", - "number": "20.4:28" + "checksum": "124eb05c66c007133b357ef970ea0ae8451920e4a7920d8b84d5ec680201b0ed", + "id": "fls_d8spdkjzp496", + "link": "attributes.html#fls_d8spdkjzp496", + "number": "13.2:12" }, { - "checksum": "9e9dd4998f98b6a17410d0f5050a5640b8a7d12b16d5ccc1fbf5ddfc4fa2c910", - "id": "fls_AJmPrhHfZo6J", - "link": "macros.html#fls_AJmPrhHfZo6J", - "number": "20.4:29" + "checksum": "0b3817e206a78ddd0a8e8b4ed610b9f55b12348279d741c6b73e04f718ad7af6", + "id": "fls_vidbcv25dyud", + "link": "attributes.html#fls_vidbcv25dyud", + "number": "13.2:13" }, { - "checksum": "d54d6148b8f96f7ae615b20d921e9536dec2d40191387304db5912c07457039c", - "id": "fls_mpgh22bi8caz", - "link": "macros.html#fls_mpgh22bi8caz", - "number": "20.4:30" + "checksum": "323fb400bdae437ffcf0829ed2261a1544d5ce01aec7bf434039ecdd05963f85", + "id": "fls_d0298bmlyuu4", + "link": "attributes.html#fls_d0298bmlyuu4", + "number": "13.2:14" }, { - "checksum": "ecc18764aaf923e05323ff7d8dbc3196f9c2a677d000895c4350a34de447d370", - "id": "fls_ul7nhfyvyzh", - "link": "macros.html#fls_ul7nhfyvyzh", - "number": "20.4:31" + "checksum": "b475a70894edc2d24474d317e039db5c901e1875b816cab114567e81bd8ab90b", + "id": "fls_dtb3t5ht5ngf", + "link": "attributes.html#fls_dtb3t5ht5ngf", + "number": "13.2:15" }, { - "checksum": "61b9b13308b8c7583de5aab70fe1644dee9867664a812351e4a713132df8f246", - "id": "fls_z6xfhf71w10a", - "link": "macros.html#fls_z6xfhf71w10a", - "number": "20.4:32" - } - ], - "title": "Macro Expansion" - }, - { - "id": "fls_4apk1exafxii", - "informational": false, - "link": "macros.html#macro-matching", - "number": "20.4.1", - "paragraphs": [ - { - "checksum": "6b8396da1325228a967d0d28720f37811bfec9f46d17c5b5d20ab8838236c8d1", - "id": "fls_ZmQZ8HQWv77L", - "link": "macros.html#fls_ZmQZ8HQWv77L", - "number": "20.4.1:1" - } - ], - "title": "Macro Matching" - }, - { - "id": "fls_n3ktmjqf87qb", - "informational": false, - "link": "macros.html#rule-matching", - "number": "20.4.1.1", - "paragraphs": [ - { - "checksum": "86e93126d0e6c567afa83d3532a9dda63596c2a3a1b8fa60469481ff4891e786", - "id": "fls_77ucvwu6idms", - "link": "macros.html#fls_77ucvwu6idms", - "number": "20.4.1.1:1" + "checksum": "9b687112a84218dfa3ca1c0398bd960887a07dd5a6bc8638b5b8d2cb839101cc", + "id": "fls_c5n4gzgs79vv", + "link": "attributes.html#fls_c5n4gzgs79vv", + "number": "13.2:16" }, { - "checksum": "4c41fb4fb8823d556cfb539659a3c4703c73a1c321684dd511b7f178f2ee5439", - "id": "fls_6h1jqhxzku5v", - "link": "macros.html#fls_6h1jqhxzku5v", - "number": "20.4.1.1:2" + "checksum": "21c4c3f4bc27f88194a4d70c18fea7e0c0dd30914282d878277d915ff3696c35", + "id": "fls_xheohvupr8kb", + "link": "attributes.html#fls_xheohvupr8kb", + "number": "13.2:17" }, { - "checksum": "6ff3d3159841229f8392ab25c21c4749d84f44f9448851671427adf3fa2c97f8", - "id": "fls_r6i1ykrhb49j", - "link": "macros.html#fls_r6i1ykrhb49j", - "number": "20.4.1.1:3" + "checksum": "d69474814037b5865fc97976164e1adab1e4e2d50965ee20568962b021550b4b", + "id": "fls_s5z2q5pl14p4", + "link": "attributes.html#fls_s5z2q5pl14p4", + "number": "13.2:18" }, { - "checksum": "d6cdc5aaa72a1fe038b82e1c7963c4446c2bc7668ed2fae5540f1dd6416a865f", - "id": "fls_3qzes4lr8yuv", - "link": "macros.html#fls_3qzes4lr8yuv", - "number": "20.4.1.1:4" + "checksum": "a2d51f51657b328e4b3665e2f3a218dfe3cff69eb7b92a4c28eadc7343980475", + "id": "fls_NrTL2FruARAv", + "link": "attributes.html#fls_NrTL2FruARAv", + "number": "13.2:19" }, { - "checksum": "b69c21d1eb5931b6f4848c2e230a9199999a3b94ade1ff93a4c66ba685343958", - "id": "fls_r878ysvsy4jb", - "link": "macros.html#fls_r878ysvsy4jb", - "number": "20.4.1.1:5" - } - ], - "title": "Rule Matching" - }, - { - "id": "fls_qpx6lgapce57", - "informational": false, - "link": "macros.html#token-matching", - "number": "20.4.1.2", - "paragraphs": [ + "checksum": "e79694d1fca990a102620dc1063eff04ea1dd5c1dfbe2141a22745bbe30aa92d", + "id": "fls_5ko0q9jnxv5a", + "link": "attributes.html#fls_5ko0q9jnxv5a", + "number": "13.2:20" + }, { - "checksum": "939454db28503818aeec06820dacf2ab2a82bb11e9ebd50d2b097213c7223ae4", - "id": "fls_k6a24sbon5v9", - "link": "macros.html#fls_k6a24sbon5v9", - "number": "20.4.1.2:1" + "checksum": "e7a9d3069445027dd0df2d0ca2be3467b745bfedc8fda368fdea9ae8714a07b1", + "id": "fls_rgjf5ibhurda", + "link": "attributes.html#fls_rgjf5ibhurda", + "number": "13.2:21" }, { - "checksum": "69d0900d72789c738bb16a2cd4d7cd0938784373e5835a4c87c5655968e72f06", - "id": "fls_6uuxv91xgmfz", - "link": "macros.html#fls_6uuxv91xgmfz", - "number": "20.4.1.2:2" + "checksum": "876d87894c56c458cc8ab3b579b184d2e05b39c1d9795d16d057ce55b6efec98", + "id": "fls_4d2ArC50kNWL", + "link": "attributes.html#fls_4d2ArC50kNWL", + "number": "13.2:22" }, { - "checksum": "9676823aa002fa0b34ed9ceb6cdf528e036dd054cc9680fd769fbfbabbdf2f33", - "id": "fls_g1rml9tavh8v", - "link": "macros.html#fls_g1rml9tavh8v", - "number": "20.4.1.2:3" + "checksum": "bdd154ea0f22fca637f51b6c83cb046352335e01a835e7578c6131621efacee3", + "id": "fls_29y8icoou1gx", + "link": "attributes.html#fls_29y8icoou1gx", + "number": "13.2:23" }, { - "checksum": "485dc2a03abcfc575a18d12f44392c25e70e3d6c8c7defe90be8b259721e9cf2", - "id": "fls_h7x3tc208zpk", - "link": "macros.html#fls_h7x3tc208zpk", - "number": "20.4.1.2:4" + "checksum": "994a15d14865c9ac299a469fa02491a079be98f08e532c1842561696a2b061a9", + "id": "fls_3fxhz0olhbcy", + "link": "attributes.html#fls_3fxhz0olhbcy", + "number": "13.2:24" }, { - "checksum": "4e3b1a4397f68e72da8d33b18764d344b2cdc18f83b97ddefee8dbdef401dc87", - "id": "fls_p9eqa17d3dx", - "link": "macros.html#fls_p9eqa17d3dx", - "number": "20.4.1.2:5" + "checksum": "3ee32e5e73d9f2f1211fb65ca1c0f0d9d62156fe5ed050f460f1912ce3b036e3", + "id": "fls_oexj0952o05u", + "link": "attributes.html#fls_oexj0952o05u", + "number": "13.2:25" }, { - "checksum": "d54b6dc3d7995358cb1bb0e6672591cf1d8416a887f73cf8f3f2d2d7347d499b", - "id": "fls_k00bck2k8tde", - "link": "macros.html#fls_k00bck2k8tde", - "number": "20.4.1.2:6" + "checksum": "cc38d8d58ef7f13fe4397cce653d8d17edbbf4e2f403bfbe1a8b43ec4ec6bc5b", + "id": "fls_q579e97n1m8j", + "link": "attributes.html#fls_q579e97n1m8j", + "number": "13.2:26" }, { - "checksum": "9beee8340868a57b1052f0fc0b375a60221fffc91fe52f2b2a049ecea579e62e", - "id": "fls_pf0qrz5nadl2", - "link": "macros.html#fls_pf0qrz5nadl2", - "number": "20.4.1.2:7" + "checksum": "c162024d8e33899473123cc87be077f31a4f25434418bb05acf6f8b0020d6d36", + "id": "fls_sn43rofpq6ld", + "link": "attributes.html#fls_sn43rofpq6ld", + "number": "13.2:27" }, { - "checksum": "944fc29dfd11666e10802285b2515966d675e8dd3dba1e06c0e50e2db56199cf", - "id": "fls_9fioah171ojx", - "link": "macros.html#fls_9fioah171ojx", - "number": "20.4.1.2:8" + "checksum": "ec2aaeb5be71ab80801008bbbe736f283e0d5966c61fb962f3e0eeca5c099c6c", + "id": "fls_56d70gkmin4p", + "link": "attributes.html#fls_56d70gkmin4p", + "number": "13.2:28" }, { - "checksum": "3a4366c2f44243bd2284234dca5b270e4dc4a86d1f32a617a269adb290fb8985", - "id": "fls_j2o0f52zyvyb", - "link": "macros.html#fls_j2o0f52zyvyb", - "number": "20.4.1.2:9" + "checksum": "7272a1c6f97c65a1a242fddb8442c6a6775d841ecf8e6dca74890ea2b21b90ad", + "id": "fls_mgb1xipm0qwo", + "link": "attributes.html#fls_mgb1xipm0qwo", + "number": "13.2:29" }, { - "checksum": "e4f9ae7e642ed779024a8e69cea6afc9bd400fd2105ce496a574bcc9be0bf3d7", - "id": "fls_w5dzv3z4zd5a", - "link": "macros.html#fls_w5dzv3z4zd5a", - "number": "20.4.1.2:10" + "checksum": "7488d316ab0bbccd6abaa4b7e9fac3051db8b6b7e7abde9eea639f605061ae24", + "id": "fls_rmhlssasdtkj", + "link": "attributes.html#fls_rmhlssasdtkj", + "number": "13.2:30" }, { - "checksum": "a6923eaff6739eb4d6492f970c783cfdabc71c275843e5ef6405fff5af5b0432", - "id": "fls_wtol98rrqka5", - "link": "macros.html#fls_wtol98rrqka5", - "number": "20.4.1.2:11" + "checksum": "e318a8fb125755feb4d3ff217ad94ecfbed347ec2d3b5fc6b915785092ad356f", + "id": "fls_josaywt6g3rq", + "link": "attributes.html#fls_josaywt6g3rq", + "number": "13.2:31" }, { - "checksum": "80a04645ad370bbb071324afb5d55d19b18f915e9fadfab568496d6a3a18fa5f", - "id": "fls_iorqt9q4ie9j", - "link": "macros.html#fls_iorqt9q4ie9j", - "number": "20.4.1.2:12" + "checksum": "45520f65cdd77c53947a088461ef31fee2930bb2ed25f58133a20ba182466c85", + "id": "fls_qk4vkn42c2jh", + "link": "attributes.html#fls_qk4vkn42c2jh", + "number": "13.2:32" }, { - "checksum": "b45c38305f3c3ffa6bf27034b89cc6b8baac7a4e72914a0cfdf7eeee7da69266", - "id": "fls_2zjed913qpvi", - "link": "macros.html#fls_2zjed913qpvi", - "number": "20.4.1.2:13" + "checksum": "8c88e030229dcaf650154b169b91390821d8dfd437a0dbe05acfc8e30cba2e0d", + "id": "fls_f21azsygoovw", + "link": "attributes.html#fls_f21azsygoovw", + "number": "13.2:33" }, { - "checksum": "d58dc97395dae9ccb1d8fe2a21911f6287c2676be72fea50a94301b4b85614fb", - "id": "fls_3zdts0fsa36u", - "link": "macros.html#fls_3zdts0fsa36u", - "number": "20.4.1.2:14" + "checksum": "f89ffdf2e417fe247529cb8ada441fde754938c091666370fb4a4907947ff762", + "id": "fls_4d31lwzblg91", + "link": "attributes.html#fls_4d31lwzblg91", + "number": "13.2:34" }, { - "checksum": "0f22177e56930189a013cc165f27159e88950b6309aec5aa8f36dad4fc00070b", - "id": "fls_mb3yr1j7npv5", - "link": "macros.html#fls_mb3yr1j7npv5", - "number": "20.4.1.2:15" + "checksum": "f975580a1b931d94c926c545e6f59accc6089800804e55bf0d717b7fc1f63015", + "id": "fls_muucfla1s8yn", + "link": "attributes.html#fls_muucfla1s8yn", + "number": "13.2:35" }, { - "checksum": "8b44cfe64ffb38c055354fd03755c38a67d369899996d279000749b6938b4130", - "id": "fls_xbuixjt9pum6", - "link": "macros.html#fls_xbuixjt9pum6", - "number": "20.4.1.2:16" + "checksum": "561ac627fa6ab3ebf3be1dfa0723e9ea019b53340fbf84caaf46c3b23415887a", + "id": "fls_wbdtpntjr95w", + "link": "attributes.html#fls_wbdtpntjr95w", + "number": "13.2:36" }, { - "checksum": "2868c75bd1038a056153d19675c38e727498801f62dcf998f97e79d1feb762aa", - "id": "fls_6annifhk6cd8", - "link": "macros.html#fls_6annifhk6cd8", - "number": "20.4.1.2:17" + "checksum": "f1727bb25344abffe152fe96b9ae047c50c7467805d094633f7c5ba7a263e76a", + "id": "fls_lglwcbsvi9yj", + "link": "attributes.html#fls_lglwcbsvi9yj", + "number": "13.2:37" }, { - "checksum": "8a369cdda5e38c9d0c9235244570dc6350c8f7513c2a5dc0ce5b97503f92b7ef", - "id": "fls_2zu22efr6ncy", - "link": "macros.html#fls_2zu22efr6ncy", - "number": "20.4.1.2:18" + "checksum": "f207e9ce744c5b946fc10ed77f0e4d48c9805dbebca1dbdb5557c6a076c1e78f", + "id": "fls_1gyg8hfb13n7", + "link": "attributes.html#fls_1gyg8hfb13n7", + "number": "13.2:38" }, { - "checksum": "235bfe3d83907a5ee61a001a2b2acac3b0e021e603831aed451c1cc984b6a784", - "id": "fls_dqroklsaayzb", - "link": "macros.html#fls_dqroklsaayzb", - "number": "20.4.1.2:19" + "checksum": "dfd603b48e4714c2db10e165507f36aabf589e751ffecb158b59124480fe5b80", + "id": "fls_6005g57evfbp", + "link": "attributes.html#fls_6005g57evfbp", + "number": "13.2:39" }, { - "checksum": "273716f2c264d37ea340ce796a784d1e2b3cb3a16578a954da0dcab1900b6ca9", - "id": "fls_ghqjk6xj85ng", - "link": "macros.html#fls_ghqjk6xj85ng", - "number": "20.4.1.2:20" + "checksum": "9d7b1a8681c416a8fb1ffb7f988ebc46ebe4d70caee8109ff9f24e727f1f94fa", + "id": "fls_3y4o8kq58dt8", + "link": "attributes.html#fls_3y4o8kq58dt8", + "number": "13.2:40" }, { - "checksum": "1ff9a6a6d1f6eb7b4c668c7940b8c2e1490c37f93b536e4b0f4468a49095216f", - "id": "fls_lzwl4en5wcw0", - "link": "macros.html#fls_lzwl4en5wcw0", - "number": "20.4.1.2:21" + "checksum": "469d4af2bf4bb8bf7d70abbc6b17c1c1bb6fad4e1fefe9cabd15d3451fcdc72e", + "id": "fls_vsix3pqf519x", + "link": "attributes.html#fls_vsix3pqf519x", + "number": "13.2:41" }, { - "checksum": "622a0124d322fc811ce341d23c250c1d22df2be7065199c523a053e04f93655b", - "id": "fls_cz44evkjzv29", - "link": "macros.html#fls_cz44evkjzv29", - "number": "20.4.1.2:22" + "checksum": "83ee9ec2496eca5f9f1bf7225a184b339394c8fcd2aa146177bb9bffc0eac79a", + "id": "fls_c8uqw8p0qrh5", + "link": "attributes.html#fls_c8uqw8p0qrh5", + "number": "13.2:42" }, { - "checksum": "61ca7e8337e433a1f27f8ca82eda551971c4361a34cf11fb7c23f53368ee2593", - "id": "fls_o2exsai4m0gy", - "link": "macros.html#fls_o2exsai4m0gy", - "number": "20.4.1.2:23" + "checksum": "953d885e7fb1e0ea03c0ade5eaa5934bf60dd032fd3261d585adf05d7da0b2e8", + "id": "fls_b3jobjxmqppy", + "link": "attributes.html#fls_b3jobjxmqppy", + "number": "13.2:43" }, { - "checksum": "2b361f50a42eadd00c2c0ca748063e0d34342f34e39bae540bb8802a83f14882", - "id": "fls_1ch299zp8h7", - "link": "macros.html#fls_1ch299zp8h7", - "number": "20.4.1.2:24" + "checksum": "276f7f393035ddb571a6f1e00ad388e9854d9e0dcd79acd44e10b7e2f2939fcc", + "id": "fls_xyhoxm30i7wn", + "link": "attributes.html#fls_xyhoxm30i7wn", + "number": "13.2:44" }, { - "checksum": "225d33e4ff2e8e37f77d0d4ba4811088ee088d027a6ae4a1a4882ca9b039fa98", - "id": "fls_55ptfjlvoo8o", - "link": "macros.html#fls_55ptfjlvoo8o", - "number": "20.4.1.2:25" + "checksum": "b5faaae83480acf33dea941d05390fe8ad7ccec79bfab2b30e87b0b938047593", + "id": "fls_nowfw1ffhupd", + "link": "attributes.html#fls_nowfw1ffhupd", + "number": "13.2:45" }, { - "checksum": "17b373ea759d5c509466cee1cd0f9941a470f136a1109fb7f745dd87e096bcf9", - "id": "fls_finzfb5ljkf8", - "link": "macros.html#fls_finzfb5ljkf8", - "number": "20.4.1.2:26" + "checksum": "5ce7389b0120f2188d0456318c21e6e97516400b08b9931e9fc08d0e36c18a35", + "id": "fls_5i27houut1mu", + "link": "attributes.html#fls_5i27houut1mu", + "number": "13.2:46" }, { - "checksum": "34a0742ef58a8b7fe215596c5252975ec70b4b86687272048bf5ccbee1c21eda", - "id": "fls_s1ccs6jocsgr", - "link": "macros.html#fls_s1ccs6jocsgr", - "number": "20.4.1.2:27" + "checksum": "ef068eea3b4195d63c4faeaf8ddec06130b9c97886f65d6a735641fdfa0bbe99", + "id": "fls_1v9p4vr1nszn", + "link": "attributes.html#fls_1v9p4vr1nszn", + "number": "13.2:47" }, { - "checksum": "408711d561d0aa646b34d91388c4abb3e0e292eecb966dc171c8368efaca46c5", - "id": "fls_wpi2i6hoj3li", - "link": "macros.html#fls_wpi2i6hoj3li", - "number": "20.4.1.2:28" + "checksum": "85810ed8c5e753c8587f859f84c0c189be76078846edaa0d4b9844829fc5646f", + "id": "fls_jvkgtnulrqgh", + "link": "attributes.html#fls_jvkgtnulrqgh", + "number": "13.2:48" }, { - "checksum": "c8977ccaa62d4c406898dba6432a60322e5b401d6bd0f7cda07b4b0979bb29f6", - "id": "fls_uuey421a8n96", - "link": "macros.html#fls_uuey421a8n96", - "number": "20.4.1.2:29" + "checksum": "634ed90b7e9a1e93528b551b7e87ccc2f19cabaabd8cfe119731630c1555b2f8", + "id": "fls_k9p2xrs3dotn", + "link": "attributes.html#fls_k9p2xrs3dotn", + "number": "13.2:49" }, { - "checksum": "4538f5121ec15ee6fbd7d3cb0bb690cc955c3d8655feceab5333045461750c15", - "id": "fls_b5u47tuu136r", - "link": "macros.html#fls_b5u47tuu136r", - "number": "20.4.1.2:30" + "checksum": "cd700e1e3816ad09e427ed80ad2362eeff80ffa5182360299496b6583c87ec3e", + "id": "fls_73n30xdcx8e", + "link": "attributes.html#fls_73n30xdcx8e", + "number": "13.2:50" }, { - "checksum": "38cb0f7a2daa5611bc22dac0b50c555f9d3ffb1127900a2f86ba5a79ff08498a", - "id": "fls_rb1tu4e7dpma", - "link": "macros.html#fls_rb1tu4e7dpma", - "number": "20.4.1.2:31" + "checksum": "415ad2d08817bd284d6e72dfa33b0e8321eb4afb36b1cad3da54238b9df07fd6", + "id": "fls_e7zusnfka5dt", + "link": "attributes.html#fls_e7zusnfka5dt", + "number": "13.2:51" }, { - "checksum": "3f33b5ba4b848845ef3c885d9b0b6d3309d5d903bf48ba507cc368dc25cad1ce", - "id": "fls_c76sdvos5xeo", - "link": "macros.html#fls_c76sdvos5xeo", - "number": "20.4.1.2:32" - } - ], - "title": "Token Matching" - }, - { - "id": "fls_ym00b6ewf4n3", - "informational": false, - "link": "macros.html#macro-transcription", - "number": "20.4.2", - "paragraphs": [ - { - "checksum": "0625ef0bcc1537bbd7e48bbb52e650fac001e4ce41c99c213d6d9c46b45e74e0", - "id": "fls_y21i8062mft0", - "link": "macros.html#fls_y21i8062mft0", - "number": "20.4.2:1" + "checksum": "b241235f00eb9bda3db61fcee7dba7a5a395a008dda75dba1c18489f5ec52c23", + "id": "fls_85ul6x76ew9", + "link": "attributes.html#fls_85ul6x76ew9", + "number": "13.2:52" }, { - "checksum": "3a923fd62e7cb8e223448548b2aa38404e63cc3e8b41254c70fa1d13076ad338", - "id": "fls_n2dx4ug5nd5w", - "link": "macros.html#fls_n2dx4ug5nd5w", - "number": "20.4.2:2" + "checksum": "ad228252c455413a9d11054129ea0a307eb4301392fb4dc7aaaba0db8b102e6c", + "id": "fls_xkhm1sht2ju5", + "link": "attributes.html#fls_xkhm1sht2ju5", + "number": "13.2:53" }, { - "checksum": "3a3dab26920d694cfc94f0c1b2f51523cd3b42dbae5b61643d7c56834c7220cf", - "id": "fls_iw7322ycvhkc", - "link": "macros.html#fls_iw7322ycvhkc", - "number": "20.4.2:3" + "checksum": "3a524553119d48abe6bc7fdc94871d5862f0815aab4e5cfb1f63f69d71656f2b", + "id": "fls_w9za4moh6gb3", + "link": "attributes.html#fls_w9za4moh6gb3", + "number": "13.2:54" }, { - "checksum": "0138c8400ce4f71c65b6aa2722efe3060cbd1d41df1c72e0d5f3b34a20f300b5", - "id": "fls_jgitbqmyixem", - "link": "macros.html#fls_jgitbqmyixem", - "number": "20.4.2:4" + "checksum": "8aafb6d088ea34e1340f6dc625d312ae87a35784cb19b992667687abc8f9bb36", + "id": "fls_3vubhygy9jje", + "link": "attributes.html#fls_3vubhygy9jje", + "number": "13.2:55" }, { - "checksum": "5c84ed6a518944136878abb4c88f566d332adfa28adc88939272040b8b6376d9", - "id": "fls_ihcwl6taptas", - "link": "macros.html#fls_ihcwl6taptas", - "number": "20.4.2:5" + "checksum": "90292e11d3564cdfea3b3a715f63a2a2bc7ff5c0e0a7bf7394d45a73612e1542", + "id": "fls_mhaplbf40j02", + "link": "attributes.html#fls_mhaplbf40j02", + "number": "13.2:56" }, { - "checksum": "ccd3909779e2ae52e078ac9ec0171a31569a1a5f27a764f1116aa514d4ab1e76", - "id": "fls_g3dtpw4rtgdr", - "link": "macros.html#fls_g3dtpw4rtgdr", - "number": "20.4.2:6" + "checksum": "044666250ed6ab719cc195deadec75751cf88ccd03d5d74f0424888448852461", + "id": "fls_23huzf3c4arx", + "link": "attributes.html#fls_23huzf3c4arx", + "number": "13.2:57" }, { - "checksum": "cfa58e13c7d71f34271957f476e066a784af6a8ed0072bb215252e5c076b03c3", - "id": "fls_pvp6dxykuv66", - "link": "macros.html#fls_pvp6dxykuv66", - "number": "20.4.2:7" + "checksum": "5b250c789906dbcf10378e94398f0b4e5540359c213277369259c48af0bbe0f9", + "id": "fls_i63y9xnnwq2z", + "link": "attributes.html#fls_i63y9xnnwq2z", + "number": "13.2:58" }, { - "checksum": "c46a8e07cda751526560f7fc97ae3c1b78414ebc17c1ce3cd210620619ebbf25", - "id": "fls_bd673n5awwbz", - "link": "macros.html#fls_bd673n5awwbz", - "number": "20.4.2:8" + "checksum": "546608d35bfe5a90d5a66a076a3d517f618f561c82800080bab893cd87c9f7aa", + "id": "fls_yic8ksed28no", + "link": "attributes.html#fls_yic8ksed28no", + "number": "13.2:59" }, { - "checksum": "89ea10945ee36c0a8d05b165f2dab20d4778edf60bc0f8b3696a46d9199743b8", - "id": "fls_zbtwrtcy7pzf", - "link": "macros.html#fls_zbtwrtcy7pzf", - "number": "20.4.2:9" + "checksum": "bc97a9ab750dc2ab4e8945d338a63dc76c13eb9d172e00871b1bc6678c592cfc", + "id": "fls_p1ugiol1e5v5", + "link": "attributes.html#fls_p1ugiol1e5v5", + "number": "13.2:60" }, { - "checksum": "928fa72878c7bba4b1bce0f51d05314cb2480974eb15a3baa42edf4802016c4b", - "id": "fls_eacyb6jap9ru", - "link": "macros.html#fls_eacyb6jap9ru", - "number": "20.4.2:10" - }, + "checksum": "6212b84580c14ced11ff1299672bb121d44059cb744a474b7e412ee35d653f43", + "id": "fls_7xh2iphiteam", + "link": "attributes.html#fls_7xh2iphiteam", + "number": "13.2:61" + } + ], + "title": "Built-in Attributes" + }, + { + "id": "fls_h4k49eadninz", + "informational": false, + "link": "attributes.html#code-generation-attributes", + "number": "13.2.1", + "paragraphs": [], + "title": "Code Generation Attributes" + }, + { + "id": "fls_kpwbpp5hc00s", + "informational": false, + "link": "attributes.html#attribute-cold", + "number": "13.2.1.1", + "paragraphs": [ { - "checksum": "923b9e5ad7c41a86ba16ba5fc020547055484598d353ee7d4600ac28f7464960", - "id": "fls_y4podc7ee8lf", - "link": "macros.html#fls_y4podc7ee8lf", - "number": "20.4.2:11" + "checksum": "fb40c1f17f95fed4bda4668940636f75c4436a94cdf00ee6fd2ae36e0ea1c564", + "id": "fls_x860jl4103p", + "link": "attributes.html#fls_x860jl4103p", + "number": "13.2.1.1:1" }, { - "checksum": "b20e0ffba294f223744cb483c484961c3e57930764952f5beab1e3448b7f8cde", - "id": "fls_wbys0m4a1omg", - "link": "macros.html#fls_wbys0m4a1omg", - "number": "20.4.2:12" - }, + "checksum": "e8b558243f7bf0325a129db3b6a9dcfdeaf35a9b8aee0f97064da28323c33ce0", + "id": "fls_8zdexi5lgm2f", + "link": "attributes.html#fls_8zdexi5lgm2f", + "number": "13.2.1.1:2" + } + ], + "title": "Attribute cold" + }, + { + "id": "fls_ypio6boj3pwf", + "informational": false, + "link": "attributes.html#attribute-inline", + "number": "13.2.1.2", + "paragraphs": [ { - "checksum": "ffbe98be6393f55c5b73a7849f41caadf322b1ac4e018fa42c59e7305db89820", - "id": "fls_g445ovedgo4q", - "link": "macros.html#fls_g445ovedgo4q", - "number": "20.4.2:13" + "checksum": "8af34535fc44405dd0d1185fc8ff42acca2853c54da038fc47f8bf644aeedc58", + "id": "fls_jwyhky49ssup", + "link": "attributes.html#fls_jwyhky49ssup", + "number": "13.2.1.2:1" }, { - "checksum": "4f0f8d199b56a8e97493384b9448af0cf2c03e0ee0924725a9973869747db80f", - "id": "fls_ctzthi6keit2", - "link": "macros.html#fls_ctzthi6keit2", - "number": "20.4.2:14" + "checksum": "4d894bc4ab40b2e9fb23428a9ce2913d411c123546417eb804e57322674bae21", + "id": "fls_s7bf7tf9206d", + "link": "attributes.html#fls_s7bf7tf9206d", + "number": "13.2.1.2:2" }, { - "checksum": "924ac328fa7b1c896d1efd435201562db8738164a53715c1956c4c2da5a4012c", - "id": "fls_9n46ugmcqmix", - "link": "macros.html#fls_9n46ugmcqmix", - "number": "20.4.2:15" + "checksum": "132910b8fef15bd7f371019b12f4f3ff9e87a2bad1099553cefb639d39ab7d5c", + "id": "fls_930o6urn669w", + "link": "attributes.html#fls_930o6urn669w", + "number": "13.2.1.2:3" }, { - "checksum": "7c1fe9eccd483e7a420e8b7c1d1c1164588a91adea0ff813953d9cf07171ea38", - "id": "fls_JinrPA0pMZCr", - "link": "macros.html#fls_JinrPA0pMZCr", - "number": "20.4.2:16" + "checksum": "c0e37dc273ef977b6bdcb9424c48613b76cb248df209151bdc9e32a5e5e9d029", + "id": "fls_z7ufiqqujgdh", + "link": "attributes.html#fls_z7ufiqqujgdh", + "number": "13.2.1.2:4" }, { - "checksum": "7b97106af69b3585b467ae757b209987fcfa719d9d0056adfca9fbc914a2d73b", - "id": "fls_95rn4cvgznmd", - "link": "macros.html#fls_95rn4cvgznmd", - "number": "20.4.2:17" + "checksum": "02ef19bf80c5200669e96e008b98fccff3c5fa277c7fad57dece77bdf97528fc", + "id": "fls_f0n4g5uky9tp", + "link": "attributes.html#fls_f0n4g5uky9tp", + "number": "13.2.1.2:5" }, { - "checksum": "a990d3628235419098482e04aad2d699f6205b88761be1c1cea45b6dad882cd1", - "id": "fls_yg4c9x7049y4", - "link": "macros.html#fls_yg4c9x7049y4", - "number": "20.4.2:18" + "checksum": "cd14e98a49d9891ed4fcd4b3b9a2704b3acc39acbfd6deb0698c5b2810227f7e", + "id": "fls_r3p4din7rjz8", + "link": "attributes.html#fls_r3p4din7rjz8", + "number": "13.2.1.2:6" + } + ], + "title": "Attribute inline" + }, + { + "id": "fls_zakwockktml8", + "informational": false, + "link": "attributes.html#attribute-no-builtins", + "number": "13.2.1.3", + "paragraphs": [ + { + "checksum": "8009c0049e3490fddc7e24a87c11b90f80ecb8de0220062b76efe87d97c52f20", + "id": "fls_x36c6j1ivbvp", + "link": "attributes.html#fls_x36c6j1ivbvp", + "number": "13.2.1.3:1" }, { - "checksum": "30c1a84b2185098caed8510493324650b62ebcbc98651ffdef52e7656b859109", - "id": "fls_o9rwz9z0a2h4", - "link": "macros.html#fls_o9rwz9z0a2h4", - "number": "20.4.2:19" + "checksum": "7089a3f20c79b60dbcbc66b2e3df9d1c5911e691c738cd1846adf88d4ac1e68b", + "id": "fls_k2k10qtn6f0g", + "link": "attributes.html#fls_k2k10qtn6f0g", + "number": "13.2.1.3:2" } ], - "title": "Macro Transcription" + "title": "Attribute no_builtins" }, { - "id": "fls_xlfo7di0gsqz", + "id": "fls_spdmit5fy7el", "informational": false, - "link": "macros.html#hygiene", - "number": "20.5", + "link": "attributes.html#attribute-target-feature", + "number": "13.2.1.4", "paragraphs": [ { - "checksum": "dd8bbb6eef9dde4a235a404d36082ffd706c870db1421a4681408cf45d6a6bcc", - "id": "fls_7ezc7ncs678f", - "link": "macros.html#fls_7ezc7ncs678f", - "number": "20.5:1" + "checksum": "22c8e64d5070f5f98dfe2031a0d503ba976e3328bcb924c00ebffd8a8fa115e3", + "id": "fls_3qj3jvmtxvx6", + "link": "attributes.html#fls_3qj3jvmtxvx6", + "number": "13.2.1.4:1" }, { - "checksum": "950f5e9f05dfb90a7771b7b56a2c154f09546fdb6ee7b4947d3598357e680977", - "id": "fls_3axjf28xb1nt", - "link": "macros.html#fls_3axjf28xb1nt", - "number": "20.5:2" + "checksum": "585e4a1fa321aa3303ab0bd5552d8d1721f633efa71ec8860f55edcc75647378", + "id": "fls_agpkz1v3c281", + "link": "attributes.html#fls_agpkz1v3c281", + "number": "13.2.1.4:2" }, { - "checksum": "341839374d80c8018873df9d4ddc4bc416d5bc2dac1e9b09bc7a56b94374252a", - "id": "fls_dz2mvodl818d", - "link": "macros.html#fls_dz2mvodl818d", - "number": "20.5:3" + "checksum": "e472ceda7230cf454f09e04024a0eaaa04baecb00eb12585de90bdece33f7f1d", + "id": "fls_91b7nd6qslsb", + "link": "attributes.html#fls_91b7nd6qslsb", + "number": "13.2.1.4:3" }, { - "checksum": "e4746196f1fb7989f818766502b19a237f5b3740241baf2dfd8cb4211c05f424", - "id": "fls_puqhytfzfsg6", - "link": "macros.html#fls_puqhytfzfsg6", - "number": "20.5:4" + "checksum": "f31211e5b9f382a935e4e483c3fc10fc958066dcb3b64be6772d797738339919", + "id": "fls_yz4itbk700ot", + "link": "attributes.html#fls_yz4itbk700ot", + "number": "13.2.1.4:4" }, { - "checksum": "93e969fde4270a39d3cee8c4a0b19185228c33e8b24df9d0ef270765e9824d81", - "id": "fls_uyvnq88y9gk3", - "link": "macros.html#fls_uyvnq88y9gk3", - "number": "20.5:5" + "checksum": "094f219b2ede048c90c66692b41e7a30763694b917cfced20fa4eb76afc6a626", + "id": "fls_pdyotoq8uqi2", + "link": "attributes.html#fls_pdyotoq8uqi2", + "number": "13.2.1.4:5" }, { - "checksum": "20c309825d7777388bd6af5804c0c7762b4fe76a90f7d2eb118d92ba8f547c1c", - "id": "fls_yxqcr19dig18", - "link": "macros.html#fls_yxqcr19dig18", - "number": "20.5:6" + "checksum": "78bb46c24f2ca946ed0b4e7c6089e40d13e6c39431bb0fee1bab3542dde16f26", + "id": "fls_vdbjoy6gbk7l", + "link": "attributes.html#fls_vdbjoy6gbk7l", + "number": "13.2.1.4:6" }, { - "checksum": "64745f1bbadaf610e425de55287699402147860a4554484ba0071db41bc7ed34", - "id": "fls_kx25olky1jov", - "link": "macros.html#fls_kx25olky1jov", - "number": "20.5:7" + "checksum": "897555f2c7487adb369d557e88e95f68598fd643686dc9402f4f295226b210f6", + "id": "fls_k3szii6nviza", + "link": "attributes.html#fls_k3szii6nviza", + "number": "13.2.1.4:7" }, { - "checksum": "143bbe1668afc34dcafc68eddd7634b71e3847e8f9416dc8dedcee0d2d5198f5", - "id": "fls_v46v0t2vh6x4", - "link": "macros.html#fls_v46v0t2vh6x4", - "number": "20.5:8" + "checksum": "eb4568cb53c1a9e06aea725449fa9eba6f2a425e98354dca4df23d483faa90e2", + "id": "fls_xsdkkfgv0pz6", + "link": "attributes.html#fls_xsdkkfgv0pz6", + "number": "13.2.1.4:8" }, { - "checksum": "82fe4be1744d0478a0bcbed88a26bf676ea9c239bd2e996210ce59ad724a104b", - "id": "fls_7eqqk2cj0clr", - "link": "macros.html#fls_7eqqk2cj0clr", - "number": "20.5:9" - } - ], - "title": "Hygiene" - } - ], - "title": "Macros" - }, - { - "informational": false, - "link": "inline-assembly.html", - "sections": [ - { - "id": "fls_z1il3w9nulzy", - "informational": true, - "link": "inline-assembly.html", - "number": "22", - "paragraphs": [ - { - "checksum": "2ecbb717faf9cd41966fd3fa61c9973605adda7174928d76aeec692e2f57f1e1", - "id": "fls_leamdp1r3hez", - "link": "inline-assembly.html#fls_leamdp1r3hez", - "number": "22:1" + "checksum": "608441af0bf33fcb985d864c6438f0424b8c0024d8e8d616516acbf8feb8fee0", + "id": "fls_8HPcvIaahaB9", + "link": "attributes.html#fls_8HPcvIaahaB9", + "number": "13.2.1.4:9" }, { - "checksum": "00ed2a429f932fb29fe5c4c1f6e590de43efcbdaccd713e8bdc9bfd3b22cfd12", - "id": "fls_3fg60jblx0xb", - "link": "inline-assembly.html#fls_3fg60jblx0xb", - "number": "22:2" + "checksum": "ad80d367854d025876fe951c841bb59171240dc3569f2634f8af6ed33ff9e914", + "id": "fls_lxcfj6XCKyMh", + "link": "attributes.html#fls_lxcfj6XCKyMh", + "number": "13.2.1.4:10" }, { - "checksum": "fb5d9789deafd6c411d8abdd1a60bfdfea453bfbfe713df9016b5b07c26df2c9", - "id": "fls_helnk2iz8qhp", - "link": "inline-assembly.html#fls_helnk2iz8qhp", - "number": "22:3" + "checksum": "dc4cd53cd07e16818bffb2ecd22fd3f7eb8a09f1e4abea7359ba3227288a44b1", + "id": "fls_fh7i1Pcmqjdm", + "link": "attributes.html#fls_fh7i1Pcmqjdm", + "number": "13.2.1.4:11" }, { - "checksum": "4b4a76da9d4b5011fcbcd054fffc111ce72238f797e8cf7b2bbbccec650c24c3", - "id": "fls_vanmt2dv7hzd", - "link": "inline-assembly.html#fls_vanmt2dv7hzd", - "number": "22:4" + "checksum": "d04661f1ac206b79927599e6d6a0bfe1136b68c96a331d7cc8b49bb7b5d07f00", + "id": "fls_M9ne2sqhRB5L", + "link": "attributes.html#fls_M9ne2sqhRB5L", + "number": "13.2.1.4:12" }, { - "checksum": "55a932bf32ca4b5f1f13535ebf918728efa7830451bde120075904c8cc821ff0", - "id": "fls_g8fuy1qnebxe", - "link": "inline-assembly.html#fls_g8fuy1qnebxe", - "number": "22:5" + "checksum": "94b07f6d75030423999a90142907265ec0a64635257aee11028e5d641063a50c", + "id": "fls_xWcGvl83bVWC", + "link": "attributes.html#fls_xWcGvl83bVWC", + "number": "13.2.1.4:13" }, { - "checksum": "2932ba93879a19d635e09c54bc40e411c50ad1cd415e1048e6a594284226a29f", - "id": "fls_2n9el98anp8c", - "link": "inline-assembly.html#fls_2n9el98anp8c", - "number": "22:6" + "checksum": "d3d0d81c7000536ec1b34ad7e74c9d33eb39d2f4a11c957088da3c60ca76e3af", + "id": "fls_5CVTkOoLHAvW", + "link": "attributes.html#fls_5CVTkOoLHAvW", + "number": "13.2.1.4:14" }, { - "checksum": "c16628799da95c60a31934e0f2f6de7cb46011a537bb8effa46c86007e1dc6d7", - "id": "fls_pnoh31rvu9a6", - "link": "inline-assembly.html#fls_pnoh31rvu9a6", - "number": "22:7" - } - ], - "title": "Inline Assembly" - }, - { - "id": "fls_j9l8wn6wgm06", - "informational": false, - "link": "inline-assembly.html#registers", - "number": "22.1", - "paragraphs": [ - { - "checksum": "39d60a2f6181ef9205caf904fd95f7ba0a390aeb492700ff04033297298fff41", - "id": "fls_w5a6rybvptn6", - "link": "inline-assembly.html#fls_w5a6rybvptn6", - "number": "22.1:1" + "checksum": "3845bcb7567d8d9e66bcfcc0a654e06bad83bebb46a876b54c509242e1d3f66b", + "id": "fls_hThb8qn820wJ", + "link": "attributes.html#fls_hThb8qn820wJ", + "number": "13.2.1.4:15" }, { - "checksum": "73d6369a132933d1c5b8a2cfddbb5f48324fe1b292208ff88d01629411bb9802", - "id": "fls_rr8rsy7oilf0", - "link": "inline-assembly.html#fls_rr8rsy7oilf0", - "number": "22.1:2" + "checksum": "49ec6a3537a6d434982bebfc93e4b2dab76e0408afca075eb02d51e368697849", + "id": "fls_1T0VYMtG3loD", + "link": "attributes.html#fls_1T0VYMtG3loD", + "number": "13.2.1.4:16" }, { - "checksum": "12f179e5416873944a2cc637d0784da3593495a2183782ee5eb02950eeae9da6", - "id": "fls_5ruu8n6r9mvp", - "link": "inline-assembly.html#fls_5ruu8n6r9mvp", - "number": "22.1:3" + "checksum": "c58720c6b68a1afc328578498e55c128aa688af5b89a0af7c37bb3457a6a17c4", + "id": "fls_AlBD9ckWKecQ", + "link": "attributes.html#fls_AlBD9ckWKecQ", + "number": "13.2.1.4:17" }, { - "checksum": "b9873fd50a4bcfa74822967e3a60e5e0de2ddcfdaf3e20b8687b32685ff64c0b", - "id": "fls_HV3Y1A2xn0zr", - "link": "inline-assembly.html#fls_HV3Y1A2xn0zr", - "number": "22.1:4" + "checksum": "2ea2a829b952259c5e3afbcd3380fa54a7fc88125892652cbb201913984b7bc5", + "id": "fls_72cwAExPDMQ8", + "link": "attributes.html#fls_72cwAExPDMQ8", + "number": "13.2.1.4:18" }, { - "checksum": "0c60e99f8efc707247c3e040ba544bc81e25f9c00c6018150077ad6e1e2b7015", - "id": "fls_vesfzh8h6qzu", - "link": "inline-assembly.html#fls_vesfzh8h6qzu", - "number": "22.1:5" + "checksum": "d09120b12c65a7fe812199037d86b39d1d38ce1b024ccd0a13802593f093668b", + "id": "fls_pSSIwmUAnUly", + "link": "attributes.html#fls_pSSIwmUAnUly", + "number": "13.2.1.4:19" }, { - "checksum": "a070ce88d95ce414fcca9658f1ecaa93e57637fe94c94a90c0d5f7c2674a098b", - "id": "fls_ffwqxlh60i5w", - "link": "inline-assembly.html#fls_ffwqxlh60i5w", - "number": "22.1:6" + "checksum": "13794029852993d23ab1cec2d6742f1e1aca6a0a824cffc6e3742074ab90817f", + "id": "fls_YOW8lcBQ5lNI", + "link": "attributes.html#fls_YOW8lcBQ5lNI", + "number": "13.2.1.4:20" }, { - "checksum": "bc22aaa1e0a33be94f875c5a08d1d776471f1e063929ab385e062a1114dde726", - "id": "fls_3p8akc7gcsnx", - "link": "inline-assembly.html#fls_3p8akc7gcsnx", - "number": "22.1:7" + "checksum": "805e0ccd6302372755ec7d7d9c166ede1fe33dc04d90e681338c1609c7433820", + "id": "fls_8PrvVL6dkr31", + "link": "attributes.html#fls_8PrvVL6dkr31", + "number": "13.2.1.4:21" }, { - "checksum": "7c3fa30aaa8c0f3bc6f842ae4f591e7d875e79c277ac8f86b165d0913d3060a2", - "id": "fls_7dlx7nt77xk", - "link": "inline-assembly.html#fls_7dlx7nt77xk", - "number": "22.1:8" + "checksum": "2a750f04a08177128570c4f41ac7048b7ff5f8b40de571baef0b44fee9aabcdb", + "id": "fls_hKvaSExdlKgd", + "link": "attributes.html#fls_hKvaSExdlKgd", + "number": "13.2.1.4:22" }, { - "checksum": "948be818103f72ba1df6ed3e5e0ca91049b4ccb4cd3b6ef73f2a2555d14fa31d", - "id": "fls_w4z7yh5qyyed", - "link": "inline-assembly.html#fls_w4z7yh5qyyed", - "number": "22.1:9" + "checksum": "545ee5833860bcaa0b09932ad5620f836dff0b2e2f410c4d31c3e1d565600e42", + "id": "fls_r5KEicrmEGJt", + "link": "attributes.html#fls_r5KEicrmEGJt", + "number": "13.2.1.4:23" }, { - "checksum": "763f676df4ed30cc6df7cd82179c4d111f881ba1c05d0d9cb16b54676f276e17", - "id": "fls_rzp8eg6z6x3q", - "link": "inline-assembly.html#fls_rzp8eg6z6x3q", - "number": "22.1:10" + "checksum": "f29c40b7092bacf659efd63cdb93521672f99f5df0f785d45a8159f7a9028860", + "id": "fls_Qnx9nNiJeIAX", + "link": "attributes.html#fls_Qnx9nNiJeIAX", + "number": "13.2.1.4:24" }, { - "checksum": "34cd0c382f4f6e1a64167e53b3bd89895d35af1d3829d9d540bf6db843f635f8", - "id": "fls_e1w41918j49", - "link": "inline-assembly.html#fls_e1w41918j49", - "number": "22.1:11" + "checksum": "e3f949f5ca26f7ab9cb8273e2a8cd0d22e6358e4a3b7226b22d76c4edc7cb296", + "id": "fls_ZrcPWy4pxlTX", + "link": "attributes.html#fls_ZrcPWy4pxlTX", + "number": "13.2.1.4:25" }, { - "checksum": "29847045f981474ebef77cf27630d2c65b6b9cf14a6df2196c16f636c42d885f", - "id": "fls_q0s90h7xmnn4", - "link": "inline-assembly.html#fls_q0s90h7xmnn4", - "number": "22.1:12" + "checksum": "c89d3584820e4afd9fa4879a368d118a3269eb7fcf5d7548f5812f5d3b037214", + "id": "fls_YW7otDxAliSj", + "link": "attributes.html#fls_YW7otDxAliSj", + "number": "13.2.1.4:26" }, { - "checksum": "f7001d16fded0766de3bb30882a297cbb7bd22abe36f34fae3214176bfd9411d", - "id": "fls_3pt63w76isay", - "link": "inline-assembly.html#fls_3pt63w76isay", - "number": "22.1:13" + "checksum": "3f4b80364c4cc96da4e299a7e6771581713daa9ebfa980924f1c654128b1b34c", + "id": "fls_G4aOoX8a7i7r", + "link": "attributes.html#fls_G4aOoX8a7i7r", + "number": "13.2.1.4:27" }, { - "checksum": "3344b7fd47aab10734e11041c74314f0ed34b6cdaf296d6d01a6c691f0c13183", - "id": "fls_f3clxd3vidhh", - "link": "inline-assembly.html#fls_f3clxd3vidhh", - "number": "22.1:14" + "checksum": "27fceb61f07a31faf22bccecf31e526e2d22bf0615fbe8998ffcd1dd528e8e35", + "id": "fls_eglaup5zyfsk", + "link": "attributes.html#fls_eglaup5zyfsk", + "number": "13.2.1.4:28" }, { - "checksum": "a8af143fd34f01e8f65e9f888d7d760a3b031967381aee5176d597fde23be59f", - "id": "fls_vyeczg1cjxys", - "link": "inline-assembly.html#fls_vyeczg1cjxys", - "number": "22.1:15" + "checksum": "e37b94209979512d83141b81df8da7e950dc0f59ee4df88e7a1977cf6547eb6a", + "id": "fls_tkayqh7li81o", + "link": "attributes.html#fls_tkayqh7li81o", + "number": "13.2.1.4:29" }, { - "checksum": "988c5d9c0ec05e872a195f4cee6b0aa813fa0e0d2ddd4954cb63400dda9c839b", - "id": "fls_h5t153uhzoq3", - "link": "inline-assembly.html#fls_h5t153uhzoq3", - "number": "22.1:16" + "checksum": "f04d0e556354ad85a7cb772b90a5bea40dfc80d7258b591367ef1c4299bd26cd", + "id": "fls_vv8o31s0zmli", + "link": "attributes.html#fls_vv8o31s0zmli", + "number": "13.2.1.4:30" }, { - "checksum": "cc0d67b9c4b0380c9a19ee2cc5f245a03585452dc63cab72a429c021d97dc83a", - "id": "fls_jhph577nqds1", - "link": "inline-assembly.html#fls_jhph577nqds1", - "number": "22.1:17" + "checksum": "75c3e60c0e86babf3644778265ff04d82086fd20428ca94add2ab7fa29a4b786", + "id": "fls_g0762ekvl2ah", + "link": "attributes.html#fls_g0762ekvl2ah", + "number": "13.2.1.4:31" }, { - "checksum": "1c7dc5e2b8e5778cfaca9e65467b897d26825ba493e2eb3cec5435253680cdfb", - "id": "fls_mobj1y67vxvb", - "link": "inline-assembly.html#fls_mobj1y67vxvb", - "number": "22.1:18" + "checksum": "f941c343b6b7d9d96bcda5e79fe20a1b97031d28a96a15c7ec8d6bb9487a79ca", + "id": "fls_HGKiXfM5fGVo", + "link": "attributes.html#fls_HGKiXfM5fGVo", + "number": "13.2.1.4:32" }, { - "checksum": "1cbe768ef7b25e59f62f8866acbe1cdbfc5a93786922d54a7ef4a8fb4496c6a0", - "id": "fls_9ke412je1hqn", - "link": "inline-assembly.html#fls_9ke412je1hqn", - "number": "22.1:19" + "checksum": "f1f41a196874835015ff054c202bbeaca27370ac8300bea5bf53d03d074d72df", + "id": "fls_gtoLNHFmmBzd", + "link": "attributes.html#fls_gtoLNHFmmBzd", + "number": "13.2.1.4:33" }, { - "checksum": "2950a79b4c2b514e58a5e7111b5028fd5c547bfac4ea6877f6a9c0caa7e705ef", - "id": "fls_hndlas58937e", - "link": "inline-assembly.html#fls_hndlas58937e", - "number": "22.1:20" + "checksum": "e9b710a4e7a5d951c56b3f75f26e35b7a826e3d5c1958e4e71d5502857541ad0", + "id": "fls_406ltkru11tk", + "link": "attributes.html#fls_406ltkru11tk", + "number": "13.2.1.4:34" }, { - "checksum": "550d7fbd6817adc4a359813352ec379bf58efa791fd796c493eeaa84183afeda", - "id": "fls_5x0yvjil3z8p", - "link": "inline-assembly.html#fls_5x0yvjil3z8p", - "number": "22.1:21" + "checksum": "4e93115858ebed6f679bcd9090c45327132a80d56ebcefa7ba556b066fc97d72", + "id": "fls_4mhyvwj8pbpk", + "link": "attributes.html#fls_4mhyvwj8pbpk", + "number": "13.2.1.4:35" }, { - "checksum": "e940f5a23bca352679d2dc94ddb731f3a7a16e94572cc09fb942ecfe75186792", - "id": "fls_gxvlvnqs436h", - "link": "inline-assembly.html#fls_gxvlvnqs436h", - "number": "22.1:22" + "checksum": "5140f4b9a43b91808591e2b954a2980ff337e13a4e8335e3e29f7498a06a869e", + "id": "fls_ijsmqk7pgyiz", + "link": "attributes.html#fls_ijsmqk7pgyiz", + "number": "13.2.1.4:36" }, { - "checksum": "2910e2dc873a5bfe39300faefa630bf1d91844c07f36271ac590d8320e12b73d", - "id": "fls_mra7zuu7uzmb", - "link": "inline-assembly.html#fls_mra7zuu7uzmb", - "number": "22.1:23" + "checksum": "7858b9613091a34085b288d29faa10cc8eba087a29ddb9ec313ffbdd3f6db23e", + "id": "fls_a1jggfetycxh", + "link": "attributes.html#fls_a1jggfetycxh", + "number": "13.2.1.4:37" }, { - "checksum": "5d80d07626a153308f8addc2b5e9471836ba12ce1bdb076f4af716b9a4e18c2c", - "id": "fls_maa7w0jwvat2", - "link": "inline-assembly.html#fls_maa7w0jwvat2", - "number": "22.1:24" + "checksum": "417c3d70bcfa0a289a99c07935492966631944ffed968fac67820222ee2f9f1d", + "id": "fls_rmbadmoeaoxu", + "link": "attributes.html#fls_rmbadmoeaoxu", + "number": "13.2.1.4:38" }, { - "checksum": "c65f4c6b8db4f51f6eaf6ddacab1fdb08f9479f2b15b0b5f882da8c81c84054f", - "id": "fls_az7kcaq70h4d", - "link": "inline-assembly.html#fls_az7kcaq70h4d", - "number": "22.1:25" + "checksum": "a692dae36ad20687143af0ddd8381c71260bb5e4917fbc7b203f47f6e384c986", + "id": "fls_f9ydn049isbv", + "link": "attributes.html#fls_f9ydn049isbv", + "number": "13.2.1.4:39" }, { - "checksum": "57205b0e3779a9c0f99384a2e74b444075d08f48e6ca14466b92c6288be50961", - "id": "fls_xudmsflrhvo3", - "link": "inline-assembly.html#fls_xudmsflrhvo3", - "number": "22.1:26" + "checksum": "077e96f720e94612bd7c2f3a598cf2ebc2834093f5091304e84625b0bdcb8845", + "id": "fls_UPhNSZS89sYr", + "link": "attributes.html#fls_UPhNSZS89sYr", + "number": "13.2.1.4:40" }, { - "checksum": "68737b33213261aa2bc36b9d8e4d651c615017faf9304e654e7db3705b897e44", - "id": "fls_px77cr1k8coy", - "link": "inline-assembly.html#fls_px77cr1k8coy", - "number": "22.1:27" + "checksum": "48d324179e28ebc6815701ef7ddfbb70bd16b5165022acadd3f55eac59fab408", + "id": "fls_HoNwaLtDzwZi", + "link": "attributes.html#fls_HoNwaLtDzwZi", + "number": "13.2.1.4:41" }, { - "checksum": "a05b1f6d34aed3cf3fc1a88b0e4ffde453b1c30cc42ba145fc45a0eb33ae7654", - "id": "fls_y1m7tlqk2dv7", - "link": "inline-assembly.html#fls_y1m7tlqk2dv7", - "number": "22.1:28" + "checksum": "f54827bbb9be1b9aa7a97109fbb8f561186d61fce03a00dbef0a3d56e2dd2ffe", + "id": "fls_VqjfIihpRe9m", + "link": "attributes.html#fls_VqjfIihpRe9m", + "number": "13.2.1.4:42" }, { - "checksum": "7c1cd463725b7565f2460515d804abdd2d46fec45b4ac95a28c0b1ff8e554c5c", - "id": "fls_3dqq9319okv5", - "link": "inline-assembly.html#fls_3dqq9319okv5", - "number": "22.1:29" + "checksum": "f975dc2d5c0c2c15b963eccd4795930fa5cce62aa848ff640e9de66a52ea3cbc", + "id": "fls_7c31e21g6bdj", + "link": "attributes.html#fls_7c31e21g6bdj", + "number": "13.2.1.4:43" }, { - "checksum": "2b98ca72c3cc9d2473a8d91c2d66d0d938ba4fb430280f13c5a0d23e30124b4a", - "id": "fls_5l9qo5c0gek8", - "link": "inline-assembly.html#fls_5l9qo5c0gek8", - "number": "22.1:30" + "checksum": "4bcec9bb92108de19e67a173df0098642ddb36e508bfcd6556bd6ee10daced06", + "id": "fls_6d5a375j2775", + "link": "attributes.html#fls_6d5a375j2775", + "number": "13.2.1.4:44" }, { - "checksum": "4230c88382f71375962eef4624b0fc37fa2ae2ebcaa2c7b401f490fc1fa26ce4", - "id": "fls_1m68zqsxjuyp", - "link": "inline-assembly.html#fls_1m68zqsxjuyp", - "number": "22.1:31" + "checksum": "2e1bf630eca35a69526f65ac5f8fbdfe0c2f92ca2b3ac6d6be31c9e1440e29a3", + "id": "fls_xthidh2nyrno", + "link": "attributes.html#fls_xthidh2nyrno", + "number": "13.2.1.4:45" }, { - "checksum": "b481fc8900e3203d5e51d7456b51a12825e1ffc5c307841c43da63a0b09eefb9", - "id": "fls_bck9slu4hsn1", - "link": "inline-assembly.html#fls_bck9slu4hsn1", - "number": "22.1:32" + "checksum": "cbb68dabefc941e4d38db9be75022e8aef69299cd2368f981991501d97245972", + "id": "fls_w02pk6kf9w9e", + "link": "attributes.html#fls_w02pk6kf9w9e", + "number": "13.2.1.4:46" }, { - "checksum": "e2996fb9db79d277b33c4f337dc25fa4d319a5b8869182db47869241a7d6deec", - "id": "fls_3x72q39c8iwt", - "link": "inline-assembly.html#fls_3x72q39c8iwt", - "number": "22.1:33" + "checksum": "2765819a612a9117bbe617d8b73b4ff6a353b2f5cd2953bc1c894701ba157e98", + "id": "fls_lzl1gpco3osx", + "link": "attributes.html#fls_lzl1gpco3osx", + "number": "13.2.1.4:47" }, { - "checksum": "e6bbea8fcf8570922fb2dbb84419a008ed40c04e0333e8759a764083b6de6314", - "id": "fls_yfbrla8c801g", - "link": "inline-assembly.html#fls_yfbrla8c801g", - "number": "22.1:34" + "checksum": "39dab62220a7b89e525ec4893fc94dc76846950ca49a8ea1faaf23d8edccfa93", + "id": "fls_pGHKFrgGlFtg", + "link": "attributes.html#fls_pGHKFrgGlFtg", + "number": "13.2.1.4:48" }, { - "checksum": "d8849406f6b817683c149ab81aefd6d29b706724c6023b66047d2edba3924f38", - "id": "fls_3nuf1gcldamv", - "link": "inline-assembly.html#fls_3nuf1gcldamv", - "number": "22.1:35" + "checksum": "9a1429077ce3fd1214a1e364203492d57772729632414f91b3c2cd2f3d0559ac", + "id": "fls_9x2on8w44k4f", + "link": "attributes.html#fls_9x2on8w44k4f", + "number": "13.2.1.4:49" }, { - "checksum": "a32c2a79d6b620d5519660e13751e5cc22eea8de15c0b390067701e98f30a2a7", - "id": "fls_nquf1uaoezx6", - "link": "inline-assembly.html#fls_nquf1uaoezx6", - "number": "22.1:36" + "checksum": "9f65c9f29e3107f40aa936d5e59e18a3d0286fa072e95a7d207f4d4d2aabce0e", + "id": "fls_K6TM6oTLL1BA", + "link": "attributes.html#fls_K6TM6oTLL1BA", + "number": "13.2.1.4:50" }, { - "checksum": "c8194fd8db5397f0ac15a419724a3e5891b702d2969fb1bcb372ce18c98f8601", - "id": "fls_91oeyxc75vu5", - "link": "inline-assembly.html#fls_91oeyxc75vu5", - "number": "22.1:37" + "checksum": "fd611c50b61fad5a73b3dd0e1310a52ad1ec01c9d148b72a8c7eeef524ba5eb0", + "id": "fls_gRf8F9PIGySt", + "link": "attributes.html#fls_gRf8F9PIGySt", + "number": "13.2.1.4:51" }, { - "checksum": "1fb8568338e94e2bd2cef7b0aa0fb1faadd19bbab344274b1b54a2f404f8e783", - "id": "fls_r5btazdpwqtw", - "link": "inline-assembly.html#fls_r5btazdpwqtw", - "number": "22.1:38" + "checksum": "8d9df987fc1e3ee77cdeb2bc55f5b6880c9fd1a50967d27e3e311e35006d00c6", + "id": "fls_NNj4H6A9VTR8", + "link": "attributes.html#fls_NNj4H6A9VTR8", + "number": "13.2.1.4:52" }, { - "checksum": "2c4843ee0d6e3e1ad8d3a6bc4c387786a63a146301b093178a14861ed7e86461", - "id": "fls_vpibsan8aful", - "link": "inline-assembly.html#fls_vpibsan8aful", - "number": "22.1:39" + "checksum": "ef7995c90eb7ac743ab249f3f128110c3326402f81541d8d1d54a811badef3d6", + "id": "fls_qZO82VdU5Iz9", + "link": "attributes.html#fls_qZO82VdU5Iz9", + "number": "13.2.1.4:53" }, { - "checksum": "fdcc738daa05173b5609e74f2b89807360390675b83c3a52323024cf32c4c93a", - "id": "fls_lj6xcaaecokk", - "link": "inline-assembly.html#fls_lj6xcaaecokk", - "number": "22.1:40" + "checksum": "dd2313cfedf44267dbb54d88f3314ca7c7076bfc167b691f549028e3e4965bef", + "id": "fls_rilqwazchfpp", + "link": "attributes.html#fls_rilqwazchfpp", + "number": "13.2.1.4:54" }, { - "checksum": "d7006f1141083f004dce3c7fcfd3553336d9f21e9cddcd91cd327d990f2b43ad", - "id": "fls_bb1qjfin4zjc", - "link": "inline-assembly.html#fls_bb1qjfin4zjc", - "number": "22.1:41" + "checksum": "c66a6be45c57e16772d12a0c52d3d765424f9d564f839da0cb77fc8c01347d4f", + "id": "fls_f0of1395z9pn", + "link": "attributes.html#fls_f0of1395z9pn", + "number": "13.2.1.4:55" }, { - "checksum": "32c579a792bd4dfc3a9a6d6af1d96694c344082c3e65ab27cea73e72c5b24be5", - "id": "fls_7qj6pxuq2x9e", - "link": "inline-assembly.html#fls_7qj6pxuq2x9e", - "number": "22.1:42" + "checksum": "2cc4b06fbda49374ef17f4974acd16d167b72b9b46c9b13f70587ab9a098587c", + "id": "fls_v9gf6selc17l", + "link": "attributes.html#fls_v9gf6selc17l", + "number": "13.2.1.4:56" }, { - "checksum": "2ad948609b5dfb44bd6f0f3b491e171298e449c937fe7d727429e71a03180fe6", - "id": "fls_2xkw4nqt1s5a", - "link": "inline-assembly.html#fls_2xkw4nqt1s5a", - "number": "22.1:43" + "checksum": "55fa6aaa332a37320f9c7223150099eba092430986498c5ae328de57fa2e9487", + "id": "fls_jyb5s2r8w1po", + "link": "attributes.html#fls_jyb5s2r8w1po", + "number": "13.2.1.4:57" }, { - "checksum": "2db64ea95f20d553dde55920a82cd6514d6071a691fc62a0a2db860ef6134582", - "id": "fls_dpzi4ygox7jw", - "link": "inline-assembly.html#fls_dpzi4ygox7jw", - "number": "22.1:44" - }, + "checksum": "6743b7e3fdc6f0ff88d22af6ed0826b488e6d15d91849cf2f44fe00b8824ed1c", + "id": "fls_xx51fjkbgg5g", + "link": "attributes.html#fls_xx51fjkbgg5g", + "number": "13.2.1.4:58" + } + ], + "title": "Attribute target_feature" + }, + { + "id": "fls_6qj249hphj1s", + "informational": false, + "link": "attributes.html#attribute-track-caller", + "number": "13.2.1.5", + "paragraphs": [ { - "checksum": "8967b0adae59edc96e1b580347bfc98961cd1a02ef5b17848e4d07b31d0bd2e0", - "id": "fls_yr5ztipvgezk", - "link": "inline-assembly.html#fls_yr5ztipvgezk", - "number": "22.1:45" + "checksum": "e23f26c1bdaac35fb187af2c3437a156c142109266b6922552c87a35c4fd7652", + "id": "fls_h8yepgchjxv9", + "link": "attributes.html#fls_h8yepgchjxv9", + "number": "13.2.1.5:1" }, { - "checksum": "8acdb975ac984cda5246944ae4bb019bf2a339227c9774d52bc7a0676f46fc87", - "id": "fls_n8ccafjut1yd", - "link": "inline-assembly.html#fls_n8ccafjut1yd", - "number": "22.1:46" + "checksum": "be22b61cc84fba1c898524be4f927f7ae8b8aec54323ad08f5b3a0d7ae1c6ff9", + "id": "fls_w1pxtzp7acty", + "link": "attributes.html#fls_w1pxtzp7acty", + "number": "13.2.1.5:2" }, { - "checksum": "d044514182338ccadc9d1a05981c817324f604a615020a9507188e023305649a", - "id": "fls_iv23mcgw6l3r", - "link": "inline-assembly.html#fls_iv23mcgw6l3r", - "number": "22.1:47" + "checksum": "778290a828a1c18436fe29f9d858726e4e341c74a14f532e55779389a4733012", + "id": "fls_zch43jpetmdu", + "link": "attributes.html#fls_zch43jpetmdu", + "number": "13.2.1.5:3" }, { - "checksum": "2aab1e6272ff7b563a82b6b37c18bca96b4c87c262d8f2e8b2c9cc0e81c7ec0e", - "id": "fls_yr7bac5k3uk7", - "link": "inline-assembly.html#fls_yr7bac5k3uk7", - "number": "22.1:48" + "checksum": "cf4ab277047420d2b26238c76cfc2a3cc9ba2c6e60092568995ceba4e201831f", + "id": "fls_y1e258p4rby5", + "link": "attributes.html#fls_y1e258p4rby5", + "number": "13.2.1.5:4" }, { - "checksum": "f7bcce04d256c567958ac3c7b038e42b735b6997898d7ff47aac48170c896eee", - "id": "fls_gvb2zcrseqci", - "link": "inline-assembly.html#fls_gvb2zcrseqci", - "number": "22.1:49" + "checksum": "ef4fb5367f8b8eec661ad431d7645eeb45c5a5691ee03447d7b267f66d6b2bc7", + "id": "fls_vkz8t751gfhk", + "link": "attributes.html#fls_vkz8t751gfhk", + "number": "13.2.1.5:5" }, { - "checksum": "3b2f9835bae0171b78f5e494a3aa32451cd8c1785291f2d6d8393e0839e5bf45", - "id": "fls_z1b9nf49nbjh", - "link": "inline-assembly.html#fls_z1b9nf49nbjh", - "number": "22.1:50" - }, + "checksum": "5b17e06e43d9f47282dbe761e0e463c0d2b276faa4045afc0ab58815012d19db", + "id": "fls_ddg0u5lej74x", + "link": "attributes.html#fls_ddg0u5lej74x", + "number": "13.2.1.5:6" + } + ], + "title": "Attribute track_caller" + }, + { + "id": "fls_sd6rumpeb355", + "informational": false, + "link": "attributes.html#attribute-naked", + "number": "13.2.1.6", + "paragraphs": [ { - "checksum": "cb975dffbdba381c89169413a7000e7dce7ec25ef91c34b0c74b850f3647a2f4", - "id": "fls_etfkcesnrlwt", - "link": "inline-assembly.html#fls_etfkcesnrlwt", - "number": "22.1:51" + "checksum": "137f3fc9833b471ee26f83a1a24deacc325fcc2b16843c9957d541c9c57c71b8", + "id": "fls_8eg0iN2diBZt", + "link": "attributes.html#fls_8eg0iN2diBZt", + "number": "13.2.1.6:1" }, { - "checksum": "1f33ae168aae6dde1001c1413bdefa412cf8161d3e84988f35963972258881d8", - "id": "fls_8sqzva4ibf0i", - "link": "inline-assembly.html#fls_8sqzva4ibf0i", - "number": "22.1:52" + "checksum": "9c57c083a646e3281524ef2281d711ff34fe80ce7958c597aec3a475b5811989", + "id": "fls_WnqgR5OyecIs", + "link": "attributes.html#fls_WnqgR5OyecIs", + "number": "13.2.1.6:2" }, { - "checksum": "c6aa9d96351da3b4fe364c7887ea1ba84b5643d1d7a3838ea2efb41907b794de", - "id": "fls_8bo9p6mybuzi", - "link": "inline-assembly.html#fls_8bo9p6mybuzi", - "number": "22.1:53" + "checksum": "9d654d742fc65068a0c5b2afad757634e4b17bf84fefa468a0a19536918e7442", + "id": "fls_NCHa4Dc1M6oM", + "link": "attributes.html#fls_NCHa4Dc1M6oM", + "number": "13.2.1.6:3" }, { - "checksum": "649109caddec8bfbd71e14d93cd7e1c50c27a223b51ebacfd28ace0d02e1526b", - "id": "fls_dk4z9dracvps", - "link": "inline-assembly.html#fls_dk4z9dracvps", - "number": "22.1:54" - }, - { - "checksum": "d7a0475661c593f7c0566fc7018842861e7876cde7b82b1866534efc7c2e0443", - "id": "fls_7c7lgvw8gbkb", - "link": "inline-assembly.html#fls_7c7lgvw8gbkb", - "number": "22.1:55" + "checksum": "11a2b6bc669fc7c1e8d10ee30bf62091e5e74cfc7c6e9e66157f2984fc1e1c77", + "id": "fls_H7ccwNk2HGAi", + "link": "attributes.html#fls_H7ccwNk2HGAi", + "number": "13.2.1.6:4" }, { - "checksum": "03796b443d5b6bee6e75a9e91f79f885c5c7a9c39ba82beb8257fc4d9bb65116", - "id": "fls_sb0ffx31gul9", - "link": "inline-assembly.html#fls_sb0ffx31gul9", - "number": "22.1:56" + "checksum": "fc359929be3f5c5328c068832c7788e882355d02aa26a75d7c175abbc22efa2f", + "id": "fls_ocOk47SGiRVb", + "link": "attributes.html#fls_ocOk47SGiRVb", + "number": "13.2.1.6:5" }, { - "checksum": "d3cdc1dbdbdaf3bf51d1c1d3e15c1fa5aef040e28178566c5cb0d654ebcab926", - "id": "fls_f9psgjl6ervm", - "link": "inline-assembly.html#fls_f9psgjl6ervm", - "number": "22.1:57" + "checksum": "9f2adfd5bcfe3283d7338f6cf77b417fddca8cb2689140e031da6ed4b4dc4693", + "id": "fls_VUL2qySBvRqy", + "link": "attributes.html#fls_VUL2qySBvRqy", + "number": "13.2.1.6:6" }, { - "checksum": "c72b9ed4c401c89d36486c8d501951d76d0d37f6c2e0e8b06a1d4e20c2431428", - "id": "fls_3y3b7znxznzu", - "link": "inline-assembly.html#fls_3y3b7znxznzu", - "number": "22.1:58" + "checksum": "bec2e0ffe9307dbf67461ed0d828670c1fe5c056cea089725f327e54b44c0c3a", + "id": "fls_0tYMAQY1V55p", + "link": "attributes.html#fls_0tYMAQY1V55p", + "number": "13.2.1.6:7" }, { - "checksum": "81ecd8fe1fe33b56420762ca519c95224c123ed4439893049875d345aba5c512", - "id": "fls_7y201ev5b0mq", - "link": "inline-assembly.html#fls_7y201ev5b0mq", - "number": "22.1:59" - }, + "checksum": "e1ffbaafd9d77623ddc68ff25c0694ad436de3c2ef9015f1a51f47e71fce913c", + "id": "fls_wGbJrz4OpKKb", + "link": "attributes.html#fls_wGbJrz4OpKKb", + "number": "13.2.1.6:8" + } + ], + "title": "Attribute naked" + }, + { + "id": "fls_cdx9zb1yxcc8", + "informational": false, + "link": "attributes.html#conditional-compilation-attributes", + "number": "13.2.2", + "paragraphs": [], + "title": "Conditional Compilation Attributes" + }, + { + "id": "fls_fymvsy6ig99a", + "informational": false, + "link": "attributes.html#attribute-cfg", + "number": "13.2.2.1", + "paragraphs": [ { - "checksum": "a44bc0de8a6ee0c018ae873e38b231a58ebb326951a3fd5fe21546388c740593", - "id": "fls_r2aoetnwhjkf", - "link": "inline-assembly.html#fls_r2aoetnwhjkf", - "number": "22.1:60" + "checksum": "8ce58553238726c00673ba1eb2613f145c385fd18764cfb47b1674bfa60a342b", + "id": "fls_xrjp7xw9jutz", + "link": "attributes.html#fls_xrjp7xw9jutz", + "number": "13.2.2.1:1" }, { - "checksum": "bc842bb6d2622c3a029ef4614723d21fd262ddf085253092dd84e1a420f77ca7", - "id": "fls_54n4ie3frc39", - "link": "inline-assembly.html#fls_54n4ie3frc39", - "number": "22.1:61" + "checksum": "58fc80fa15a48bf125434201acfd61890abb0876c32f33ee234150c5a2db53fa", + "id": "fls_l96kyix5xsof", + "link": "attributes.html#fls_l96kyix5xsof", + "number": "13.2.2.1:2" }, { - "checksum": "7a946b3318f99a6748a231b8c03c16781c4292e0fcbbc4ac706a962861f317b5", - "id": "fls_iok8gc906tc8", - "link": "inline-assembly.html#fls_iok8gc906tc8", - "number": "22.1:62" + "checksum": "20bbdb81e00aa2ce6e15572b261930193367b58565a11d3b7df5fe4d2fb92058", + "id": "fls_y1MUhnKCxK6T", + "link": "attributes.html#fls_y1MUhnKCxK6T", + "number": "13.2.2.1:3" }, { - "checksum": "770cd7d9c31bbb765ad40be40ea824f38ba9e412b3e507365f9c6a754c17551e", - "id": "fls_2mgjkyngbmbr", - "link": "inline-assembly.html#fls_2mgjkyngbmbr", - "number": "22.1:63" + "checksum": "e3e52c127b3a45a28120d4a22599dc479401160fd2ab551667790d586a58ed83", + "id": "fls_tncxxsyutppf", + "link": "attributes.html#fls_tncxxsyutppf", + "number": "13.2.2.1:4" }, { - "checksum": "5c59322286016f2c772ca739ba5233cd7e28720d91ce9189183e1852db7b095a", - "id": "fls_6tlnpguf23y3", - "link": "inline-assembly.html#fls_6tlnpguf23y3", - "number": "22.1:64" + "checksum": "0a8af2d1ed68a8e58507a513800cba27fe171d005323ccf9b1dea50098d45eda", + "id": "fls_Rp73YEE3aFdI", + "link": "attributes.html#fls_Rp73YEE3aFdI", + "number": "13.2.2.1:5" }, { - "checksum": "c7750120307aedb80af8ae7313dd6345535fd86e2c63679b6d61344356d21904", - "id": "fls_bz5xyv89i5m7", - "link": "inline-assembly.html#fls_bz5xyv89i5m7", - "number": "22.1:65" + "checksum": "b1317fa9240a0683317601af9dbd0d64b3bdfb9f8516093a3aa3febe38ef4940", + "id": "fls_m0zxktz168e0", + "link": "attributes.html#fls_m0zxktz168e0", + "number": "13.2.2.1:6" }, { - "checksum": "18febf551f1317bbf8aa6f68c01133df1bacd637fbb0500335b6725a840eb292", - "id": "fls_soitzjqze3rf", - "link": "inline-assembly.html#fls_soitzjqze3rf", - "number": "22.1:66" + "checksum": "c678d8736b809e8b047bb8dec6b729baecd22ff0dc7cfca0cea327a1174294dc", + "id": "fls_XsxeOd32JI8x", + "link": "attributes.html#fls_XsxeOd32JI8x", + "number": "13.2.2.1:7" }, { - "checksum": "5b49f664f474f3d6edcb8801c7593e791e1d1ad1349e8d219df81fa95929c9a4", - "id": "fls_b2c02r5y6zm9", - "link": "inline-assembly.html#fls_b2c02r5y6zm9", - "number": "22.1:67" + "checksum": "7b842e2904a1bd499fdf9166a26058c88d4357ca86b54fce379427f3db4f6c87", + "id": "fls_tvsadfy9uibu", + "link": "attributes.html#fls_tvsadfy9uibu", + "number": "13.2.2.1:8" }, { - "checksum": "641caa94a7b88594686c64a9e6c37cd6597bc61fb18259f047eb9c3a31ae1428", - "id": "fls_2ufm8y5ttcxf", - "link": "inline-assembly.html#fls_2ufm8y5ttcxf", - "number": "22.1:68" + "checksum": "ef3975edc33ff52d4a52d8454218b527f15a9cc09e83f4f9c87dc2283412ca35", + "id": "fls_JWx8vQwl19Fu", + "link": "attributes.html#fls_JWx8vQwl19Fu", + "number": "13.2.2.1:9" }, { - "checksum": "d1c20d9b16467ccff234ee51ec9f7445d1242be7328a3b7fc74105e82aa6848f", - "id": "fls_ub321ic94bbl", - "link": "inline-assembly.html#fls_ub321ic94bbl", - "number": "22.1:69" + "checksum": "c1b07345fa9901b40f4158770a884da21b22244131f2624ce79defce035cb4e0", + "id": "fls_SziFAQsio0ab", + "link": "attributes.html#fls_SziFAQsio0ab", + "number": "13.2.2.1:10" }, { - "checksum": "8e992777ce18f719a5a9f6087fe030f71520760bfe4215255991b93ae2a6775a", - "id": "fls_gtwmwsmyrdxe", - "link": "inline-assembly.html#fls_gtwmwsmyrdxe", - "number": "22.1:70" - }, + "checksum": "fbaba2bbfc24b35f86a4f7dd04dbe8282ddf984c2d109a9ee52a777dc8b3360f", + "id": "fls_jbl9xyynjo0g", + "link": "attributes.html#fls_jbl9xyynjo0g", + "number": "13.2.2.1:11" + } + ], + "title": "Attribute cfg" + }, + { + "id": "fls_dd9xh3wdjudo", + "informational": false, + "link": "attributes.html#attribute-cfg-attr", + "number": "13.2.2.2", + "paragraphs": [ { - "checksum": "22204cedd5304e36d4991c6ec1da6276140e3d0d2decbed86fe6c623c9a4cd8b", - "id": "fls_vy8alu9yuza9", - "link": "inline-assembly.html#fls_vy8alu9yuza9", - "number": "22.1:71" + "checksum": "d4c02f4ddc98da8bafd4fc43fb7cb00dc77d84e6b42cf83332e1d8ab9e29250f", + "id": "fls_r66jhict6rlq", + "link": "attributes.html#fls_r66jhict6rlq", + "number": "13.2.2.2:1" }, { - "checksum": "d8eeb48a603b537546fcc36fa3e17ccba43d166b30fd0d2792edc9e29f75d4ba", - "id": "fls_zEtLZ5KjQcHS", - "link": "inline-assembly.html#fls_zEtLZ5KjQcHS", - "number": "22.1:72" + "checksum": "58b7d08514f893172d59bfa69761494f4bb62b45412529d75cd3eb80fe2a6e9d", + "id": "fls_rzw12sagm585", + "link": "attributes.html#fls_rzw12sagm585", + "number": "13.2.2.2:2" } ], - "title": "Registers" + "title": "Attribute cfg_attr" }, { - "id": "fls_pz2ioqchjtym", + "id": "fls_wednba84zi3y", "informational": false, - "link": "inline-assembly.html#register-classes", - "number": "22.2", + "link": "attributes.html#derivation-attributes", + "number": "13.2.3", + "paragraphs": [], + "title": "Derivation Attributes" + }, + { + "id": "fls_bqw87nz4qbrb", + "informational": false, + "link": "attributes.html#attribute-automatically-derived", + "number": "13.2.3.1", "paragraphs": [ { - "checksum": "bca76cef1fe93380b4b2764a1780560ecfc7d399d0133d974aa8b4272185398c", - "id": "fls_7gxb7ztpuofj", - "link": "inline-assembly.html#fls_7gxb7ztpuofj", - "number": "22.2:1" - }, - { - "checksum": "072e7d6791f5c1d9aca09df3602ec2e87a96ea771b13d9baee44cc7da4745443", - "id": "fls_on0i2cpk254y", - "link": "inline-assembly.html#fls_on0i2cpk254y", - "number": "22.2:2" - }, + "checksum": "921207c7548372ce92fdca679bc3476d41831899b57c4e34b13268b01997e0f8", + "id": "fls_5u1e0lkt0ab1", + "link": "attributes.html#fls_5u1e0lkt0ab1", + "number": "13.2.3.1:1" + } + ], + "title": "Attribute automatically_derived" + }, + { + "id": "fls_r6gj1p4gajnq", + "informational": false, + "link": "attributes.html#attribute-derive", + "number": "13.2.3.2", + "paragraphs": [ { - "checksum": "f507ddb8a4d78d5f4516c08061e3fd00ce3680761f88d8533d74cebf1747d8e5", - "id": "fls_40ksem5g5xx9", - "link": "inline-assembly.html#fls_40ksem5g5xx9", - "number": "22.2:3" + "checksum": "a1c77a2b2980c21e6e863dd1b876f7c14683644e96cf49df977a847b4e9e5e1a", + "id": "fls_4btm6zwf445a", + "link": "attributes.html#fls_4btm6zwf445a", + "number": "13.2.3.2:1" }, { - "checksum": "4f9cf8829042709ab419bcfdac8f32f55808e17f6f72d0a868bc08b92f89d719", - "id": "fls_fqvjk6caipq", - "link": "inline-assembly.html#fls_fqvjk6caipq", - "number": "22.2:4" + "checksum": "0a92a3c710cbd1738e619ab4ed564919e5913e3252de308e5f6f347d1833cea7", + "id": "fls_pjmbmj2b35y8", + "link": "attributes.html#fls_pjmbmj2b35y8", + "number": "13.2.3.2:2" }, { - "checksum": "a745f47cfa17415d3efa516e8a511b98dc10024c933e4b234e51839ab8b2c4bf", - "id": "fls_u0ie66ep3glg", - "link": "inline-assembly.html#fls_u0ie66ep3glg", - "number": "22.2:5" - }, + "checksum": "01d955a36e6ea356f588c104a043bbb024bd45ea952c638344667bf6abc1fc81", + "id": "fls_xtty2ino4vwc", + "link": "attributes.html#fls_xtty2ino4vwc", + "number": "13.2.3.2:3" + } + ], + "title": "Attribute derive" + }, + { + "id": "fls_t2590yyvclgb", + "informational": true, + "link": "attributes.html#diagnostics-attributes", + "number": "13.2.4", + "paragraphs": [ { - "checksum": "834825290b52066ecedfd94cfeaaa564b134590cadb34cc3d93faffcfa68982a", - "id": "fls_wcvcansd88je", - "link": "inline-assembly.html#fls_wcvcansd88je", - "number": "22.2:6" - }, + "checksum": "3211f6c48f79ae56d33f1f9bacd442f463c800f82da1ca3bc76e3afbd75c27ea", + "id": "fls_ghumzt9ybtit", + "link": "attributes.html#fls_ghumzt9ybtit", + "number": "13.2.4:1" + } + ], + "title": "Diagnostics Attributes" + }, + { + "id": "fls_8wcliky2svcs", + "informational": false, + "link": "attributes.html#documentation-attributes", + "number": "13.2.5", + "paragraphs": [], + "title": "Documentation Attributes" + }, + { + "id": "fls_63v1fqedzwfd", + "informational": false, + "link": "attributes.html#attribute-doc", + "number": "13.2.5.1", + "paragraphs": [ { - "checksum": "19a597e10c09d8cd5266148bbaa227ca26d47e590e8cbe4a3a0b4a3b7b65ebf9", - "id": "fls_sbllpky4d7ka", - "link": "inline-assembly.html#fls_sbllpky4d7ka", - "number": "22.2:7" - }, + "checksum": "b5b9bec9d76d46cbea5f3747e5f7b0132aac36d1c007bca045c1a1e350d88cde", + "id": "fls_1ee9qjcgbwme", + "link": "attributes.html#fls_1ee9qjcgbwme", + "number": "13.2.5.1:1" + } + ], + "title": "Attribute doc" + }, + { + "id": "fls_pgp7ezcc9lh8", + "informational": false, + "link": "attributes.html#foreign-function-interface-attributes", + "number": "13.2.6", + "paragraphs": [], + "title": "Foreign Function Interface Attributes" + }, + { + "id": "fls_sun645voqex6", + "informational": false, + "link": "attributes.html#attribute-crate-name", + "number": "13.2.6.1", + "paragraphs": [ { - "checksum": "22f409acc30151195193e1ac9bc8011511fad86bb8a83517b941ceba06117903", - "id": "fls_nmx5xs829ms", - "link": "inline-assembly.html#fls_nmx5xs829ms", - "number": "22.2:8" + "checksum": "61437013bccd601dda4663fa8275e8def835508d51f858d4584acb2816792d24", + "id": "fls_tsdk8jyajcg", + "link": "attributes.html#fls_tsdk8jyajcg", + "number": "13.2.6.1:1" }, { - "checksum": "e7b421b9d33bd170c0b1cb09c6bf1d3514538ae75bad8431dc915f4629b428fe", - "id": "fls_nars4y8tv2w6", - "link": "inline-assembly.html#fls_nars4y8tv2w6", - "number": "22.2:9" - }, + "checksum": "31e103c274c243fd0657f6c9fb22d4587184086c9c66befda6643f82b2435479", + "id": "fls_6riphqysh0gd", + "link": "attributes.html#fls_6riphqysh0gd", + "number": "13.2.6.1:2" + } + ], + "title": "Attribute crate_name" + }, + { + "id": "fls_ujig607lmwbm", + "informational": false, + "link": "attributes.html#attribute-crate-type", + "number": "13.2.6.2", + "paragraphs": [ { - "checksum": "6b05bd0ba53bef61a1eb498458f8f387e92b3a84409fe2949e603bde1d9a915d", - "id": "fls_b5juxguclqjs", - "link": "inline-assembly.html#fls_b5juxguclqjs", - "number": "22.2:10" + "checksum": "54779685564cfbdb9ff97ae91287f885632bf1e75e9e44af0e3115fe73cdb3e6", + "id": "fls_2i2g55nqqpc1", + "link": "attributes.html#fls_2i2g55nqqpc1", + "number": "13.2.6.2:1" }, { - "checksum": "aea699b2678adf58e4f4bf56b5b412b83f1ded902ad91eb6d363bfc48e11ecb6", - "id": "fls_vmigixoxm5uf", - "link": "inline-assembly.html#fls_vmigixoxm5uf", - "number": "22.2:11" - }, + "checksum": "05939963a9001f26e930c1bf9bad738b146dad4bd95c5cbee3e0ef2503896e7b", + "id": "fls_1zziddjuzjeq", + "link": "attributes.html#fls_1zziddjuzjeq", + "number": "13.2.6.2:2" + } + ], + "title": "Attribute crate_type" + }, + { + "id": "fls_olzilmy8n0nl", + "informational": false, + "link": "attributes.html#attribute-export-name", + "number": "13.2.6.3", + "paragraphs": [ { - "checksum": "6481ad4cc845d4e7ffc277d6a32513834168222c01697bb90ede7f041f4b3ab3", - "id": "fls_vdqtbc4t69v2", - "link": "inline-assembly.html#fls_vdqtbc4t69v2", - "number": "22.2:12" + "checksum": "fa8489539e2aa3b8aa9a16e4531a9c0f7e082581000b26430071b5fe8bddd791", + "id": "fls_r3fwpuuaoeie", + "link": "attributes.html#fls_r3fwpuuaoeie", + "number": "13.2.6.3:1" }, { - "checksum": "a2eef70002f2666dc49f2def9707cdfa752018ddd635ddf2f70e7086f1782b72", - "id": "fls_t2d77dazjyjo", - "link": "inline-assembly.html#fls_t2d77dazjyjo", - "number": "22.2:13" - }, + "checksum": "35ac008e660d6499f218d0c11b1c822a4fda466a0469f3424a0c9aa028f72ea6", + "id": "fls_spwr6gf7kpds", + "link": "attributes.html#fls_spwr6gf7kpds", + "number": "13.2.6.3:2" + } + ], + "title": "Attribute export_name" + }, + { + "id": "fls_o0f9ae22ug1x", + "informational": false, + "link": "attributes.html#attribute-link", + "number": "13.2.6.4", + "paragraphs": [ { - "checksum": "ac9efee8bc991667f505b75c53a5d2409171184521200c1315775ac64f7bfc7c", - "id": "fls_jh02uk3ypett", - "link": "inline-assembly.html#fls_jh02uk3ypett", - "number": "22.2:14" + "checksum": "8f57b8472dff4cd91f355578e5c3bfa9096c22950b9766df341e266371a1a946", + "id": "fls_yslpkdngo8hj", + "link": "attributes.html#fls_yslpkdngo8hj", + "number": "13.2.6.4:1" }, { - "checksum": "0eaa7e064279eea86a9d365b6c251eb9d9b53356bcf44f642cefe63992aa99b6", - "id": "fls_rjk5laiyqagy", - "link": "inline-assembly.html#fls_rjk5laiyqagy", - "number": "22.2:15" + "checksum": "6eb5a07a373ecc60dd23324335a8b659caf2269885af5c3585f4df1c13ca727c", + "id": "fls_6rohnk4swj6c", + "link": "attributes.html#fls_6rohnk4swj6c", + "number": "13.2.6.4:2" }, { - "checksum": "ae959551a354c13f23e569b07e21962fd29e91edad42a1637c61c47ffbba3178", - "id": "fls_26bq6wbwznx", - "link": "inline-assembly.html#fls_26bq6wbwznx", - "number": "22.2:16" + "checksum": "d9db9f413583821ddb835f34a03fd02f2a1d38f6eab189bb99008475e2400f3f", + "id": "fls_o83pf3bcrzma", + "link": "attributes.html#fls_o83pf3bcrzma", + "number": "13.2.6.4:3" }, { - "checksum": "0e6100d8c3a6f0652c904a5a5da3ac3fdbc07c732bcb94f9ced925a3a67ea4df", - "id": "fls_6d25i0lkzd7u", - "link": "inline-assembly.html#fls_6d25i0lkzd7u", - "number": "22.2:17" + "checksum": "0353cd50793fff24e61d961587929ddeeb5a7f46853756fa4e2d70319d7500a6", + "id": "fls_5541q1qoxdpf", + "link": "attributes.html#fls_5541q1qoxdpf", + "number": "13.2.6.4:4" }, { - "checksum": "c763cce07ab00dfd3323500717b1eed3e55412bb79178870af3708ab08e16908", - "id": "fls_y52suhleyid2", - "link": "inline-assembly.html#fls_y52suhleyid2", - "number": "22.2:18" + "checksum": "eef115065e6c96a05b7ae0eb410b3b05418ce07352bb23ed9baaf23d7f5d5450", + "id": "fls_wpqawdpevkj6", + "link": "attributes.html#fls_wpqawdpevkj6", + "number": "13.2.6.4:5" }, { - "checksum": "96cd932df092222c77d3bcb7d31fad0c2be26093f163662b926fb5cc7acf7f83", - "id": "fls_7b7c8xtm8fr7", - "link": "inline-assembly.html#fls_7b7c8xtm8fr7", - "number": "22.2:19" + "checksum": "60ae8311dc7a096a8d78ddf2ae480d5cd77d966a20facb0ce94dcea8d1eab84e", + "id": "fls_HPl6poXGiAqV", + "link": "attributes.html#fls_HPl6poXGiAqV", + "number": "13.2.6.4:6" }, { - "checksum": "e0ac82fc15ff715d9cddb3bdc897151a9a4cf2819d813ac6da7a07d48fd07ad0", - "id": "fls_ue0se3dcop6w", - "link": "inline-assembly.html#fls_ue0se3dcop6w", - "number": "22.2:20" + "checksum": "b0db32db9b79b0e6ad02182b8befad2a9aa177097a1fa014a59781f5686d36a2", + "id": "fls_h9dfs6kzmobp", + "link": "attributes.html#fls_h9dfs6kzmobp", + "number": "13.2.6.4:7" }, { - "checksum": "9a47e3602b2201c92e0dce3aa02e6d20feae619aec5f1e0369434cb2af047e9e", - "id": "fls_2m49cyfqffvo", - "link": "inline-assembly.html#fls_2m49cyfqffvo", - "number": "22.2:21" + "checksum": "ae7e1e29c04ecbee336f1381e8308022f3ad0ccb61bd5c5c7c353b7eea560997", + "id": "fls_3a3r4jf7hzqr", + "link": "attributes.html#fls_3a3r4jf7hzqr", + "number": "13.2.6.4:8" }, { - "checksum": "fba730a9b194b9c95c6372bcd456c87e55aaa5d71933a5ffbf3afbc6ce1bdaf8", - "id": "fls_mj1t0f9lp6v8", - "link": "inline-assembly.html#fls_mj1t0f9lp6v8", - "number": "22.2:22" + "checksum": "4ea15c08520b2c2fb958beab6ad0fc324e6aa088b62d5fb1e6931d8d6507bc5d", + "id": "fls_1tdheukgm6ai", + "link": "attributes.html#fls_1tdheukgm6ai", + "number": "13.2.6.4:9" }, { - "checksum": "de2789ffcd9d02a9bbc9c39b7b94cd196bfd6f0167acfd400d37b10d6188efc3", - "id": "fls_tpkkubhjt7lk", - "link": "inline-assembly.html#fls_tpkkubhjt7lk", - "number": "22.2:23" + "checksum": "7fb19964f38021c6916eb50ab245d90c1f006905de59ccc42d0d0f3d1235641f", + "id": "fls_3i9ijypnh8nx", + "link": "attributes.html#fls_3i9ijypnh8nx", + "number": "13.2.6.4:10" }, { - "checksum": "2eabf1ca886e5459326f2967d018156022a1148d68a4f0cad387ba9857bdbd77", - "id": "fls_ivq874v4lmga", - "link": "inline-assembly.html#fls_ivq874v4lmga", - "number": "22.2:24" + "checksum": "e9be1b611b17fb0d613033d31855e2094fd26b9bbc43a9b80f8182da2e833906", + "id": "fls_iDjcQczFQknm", + "link": "attributes.html#fls_iDjcQczFQknm", + "number": "13.2.6.4:11" }, { - "checksum": "5f20a84f69b2f4df1e103ed21420855eb0edd88111d65c915c9323f4e86af1d4", - "id": "fls_2wdcrocczwyi", - "link": "inline-assembly.html#fls_2wdcrocczwyi", - "number": "22.2:25" - }, + "checksum": "fdf51bc6996e603cffe22c0b534103b20e91952afdf7739053ad88ccbdc56f61", + "id": "fls_rcERq1PfKPJI", + "link": "attributes.html#fls_rcERq1PfKPJI", + "number": "13.2.6.4:12" + } + ], + "title": "Attribute link" + }, + { + "id": "fls_p44fky7fifc", + "informational": false, + "link": "attributes.html#attribute-link-name", + "number": "13.2.6.5", + "paragraphs": [ { - "checksum": "f98c5961068bd12ec95c8e6cc0e0981321c82dd46936f9674f1da53e1f4da5d9", - "id": "fls_v04te7p28dth", - "link": "inline-assembly.html#fls_v04te7p28dth", - "number": "22.2:26" + "checksum": "6321fe23e29ea0ad41b3c85bedafbf9ef00aee8ccef4be33847faf98c00dcc83", + "id": "fls_g09jhukl0ez2", + "link": "attributes.html#fls_g09jhukl0ez2", + "number": "13.2.6.5:1" }, { - "checksum": "68b2c581d553c7dc02a6bcb848acc9c08aaa51f9dfd03525f402337590b1c00f", - "id": "fls_uypct69j2h6a", - "link": "inline-assembly.html#fls_uypct69j2h6a", - "number": "22.2:27" + "checksum": "089ca2eda0df3b0c76c2d4a1e66e7b57615201c37cb7791d3217d9bf7b581da9", + "id": "fls_d00wni4edi8f", + "link": "attributes.html#fls_d00wni4edi8f", + "number": "13.2.6.5:2" }, { - "checksum": "80f90c8b08776edc6b8d2896a8d06cd39c50126b7b0a36a79b50b336f5e3b3cd", - "id": "fls_gyet9huf6nr", - "link": "inline-assembly.html#fls_gyet9huf6nr", - "number": "22.2:28" - }, - { - "checksum": "fd2227497b01180261f81adb03ea6a85c78ad4ed2fef19428a5722d64a04012c", - "id": "fls_5ekbq9hacho9", - "link": "inline-assembly.html#fls_5ekbq9hacho9", - "number": "22.2:29" - }, + "checksum": "0600f1f844b645dd649dcfdebecbdc3af837dcba7089013ae17dfc7ba6295fec", + "id": "fls_0Athv8KFA5FO", + "link": "attributes.html#fls_0Athv8KFA5FO", + "number": "13.2.6.5:3" + } + ], + "title": "Attribute link_name" + }, + { + "id": "fls_hffpo88r61rh", + "informational": false, + "link": "attributes.html#attribute-link-section", + "number": "13.2.6.6", + "paragraphs": [ { - "checksum": "22aada8b4f1f9e603a61146c28eb2887803435066842ef0118a7f1917ebeb814", - "id": "fls_furnyxmwqn09", - "link": "inline-assembly.html#fls_furnyxmwqn09", - "number": "22.2:30" + "checksum": "10a4631db6c550ee4410b3605bb85beb0c3c0dc21dfefb51ce2f0d1b3e42343b", + "id": "fls_5loqzajiz34m", + "link": "attributes.html#fls_5loqzajiz34m", + "number": "13.2.6.6:1" }, { - "checksum": "38657e66bcc21e14e87aa5238007bcac36dce73156c9c5af823e904c25658fc4", - "id": "fls_1c4ts991vkpq", - "link": "inline-assembly.html#fls_1c4ts991vkpq", - "number": "22.2:31" - }, + "checksum": "fdb84e96608c3986056b7d198e88ed046825e6c72eff37d9b7ad5d107635d528", + "id": "fls_cyxk12wuicml", + "link": "attributes.html#fls_cyxk12wuicml", + "number": "13.2.6.6:2" + } + ], + "title": "Attribute link_section" + }, + { + "id": "fls_obik2w9gvhln", + "informational": false, + "link": "attributes.html#attribute-link-ordinal", + "number": "13.2.6.7", + "paragraphs": [ { - "checksum": "f12f6d554c25c5f85af01ebeffe90a992373ef8a69195825942db06fb5c04ea8", - "id": "fls_iwnb72jb9iwj", - "link": "inline-assembly.html#fls_iwnb72jb9iwj", - "number": "22.2:32" + "checksum": "b9f69058d6cf44ac40747cbd7dd38c8689c1de672c433b6fd76c37a59413c0a8", + "id": "fls_fuv29BIqcawW", + "link": "attributes.html#fls_fuv29BIqcawW", + "number": "13.2.6.7:1" }, { - "checksum": "15d41d994ebc27fa82da90d54388cdf07ae3e0f9ede2cfeb23369a24746bdeb7", - "id": "fls_mw3axoixjgnq", - "link": "inline-assembly.html#fls_mw3axoixjgnq", - "number": "22.2:33" + "checksum": "bae500fd492b5390f081b17a3c19a753b385115b83d2678de450e7fc08802073", + "id": "fls_qh5sXG4znAXa", + "link": "attributes.html#fls_qh5sXG4znAXa", + "number": "13.2.6.7:2" }, { - "checksum": "fe29bb239760aa31dc2989d7ef1b1e0c39eb0b86dba45b014c7a1944e999b5e4", - "id": "fls_mnzt6bxhycv9", - "link": "inline-assembly.html#fls_mnzt6bxhycv9", - "number": "22.2:34" - }, + "checksum": "1c20d78c53465204598d099ba8c270f12e905456c6b799086d88a295046e6b7f", + "id": "fls_ASnJJiXWGlKI", + "link": "attributes.html#fls_ASnJJiXWGlKI", + "number": "13.2.6.7:3" + } + ], + "title": "Attribute link_ordinal" + }, + { + "id": "fls_ch9nkxkloozv", + "informational": false, + "link": "attributes.html#attribute-no-link", + "number": "13.2.6.8", + "paragraphs": [ { - "checksum": "8ef8f937a7b7b7adb9ce03d888fcf5148a91620cc4af1b10b59ac502f648b5c7", - "id": "fls_drg7v8hxb5ca", - "link": "inline-assembly.html#fls_drg7v8hxb5ca", - "number": "22.2:35" + "checksum": "b264d167b04d1f62c99717023640f39c72ae2c165f8c8802648308cf461c78db", + "id": "fls_ayhn6g6sgt3h", + "link": "attributes.html#fls_ayhn6g6sgt3h", + "number": "13.2.6.8:1" }, { - "checksum": "a04359b7328e6f0222e6c4296470962b896234b8db289d9a7c215eba9ef0a7db", - "id": "fls_78gb8z1fyluc", - "link": "inline-assembly.html#fls_78gb8z1fyluc", - "number": "22.2:36" - }, + "checksum": "aad978ad1b9b2134fb756fc3db820f6b58d2638aa0c07cf0f6910e996e04b5df", + "id": "fls_76ox8n3eef5", + "link": "attributes.html#fls_76ox8n3eef5", + "number": "13.2.6.8:2" + } + ], + "title": "Attribute no_link" + }, + { + "id": "fls_fh27ljezn3qz", + "informational": false, + "link": "attributes.html#attribute-no-main", + "number": "13.2.6.9", + "paragraphs": [ { - "checksum": "2c9e81813ba55fc43a7bcbb08d580d2da6ced81202c68a5c37a3be0227a000a7", - "id": "fls_7dii7lee457t", - "link": "inline-assembly.html#fls_7dii7lee457t", - "number": "22.2:37" + "checksum": "515473b5a7edc71b0872a2f78ed4aac163ef1b1fd6716ca8b8a8a87717e85105", + "id": "fls_84a9k0fzmnfk", + "link": "attributes.html#fls_84a9k0fzmnfk", + "number": "13.2.6.9:1" }, { - "checksum": "240ab19a4ecb8be24c7140737462ed6c98ed2adeaae044fc076f6af615f3fa39", - "id": "fls_ujhjocg1361b", - "link": "inline-assembly.html#fls_ujhjocg1361b", - "number": "22.2:38" + "checksum": "861df3866ad296473e5cbbc9995aa29386bc3d4af7b1a33a57de0862d30fd94f", + "id": "fls_6qig3s3qpj0i", + "link": "attributes.html#fls_6qig3s3qpj0i", + "number": "13.2.6.9:2" } ], - "title": "Register Classes" + "title": "Attribute no_main" }, { - "id": "fls_hejgghwzblf", + "id": "fls_mvd7nz8k3wcy", "informational": false, - "link": "inline-assembly.html#register-arguments", - "number": "22.3", + "link": "attributes.html#attribute-no-mangle", + "number": "13.2.6.10", "paragraphs": [ { - "checksum": "51ff09ea4d125dfa2dddd25429197d68e0ab90254402dd2d93ea531c65a622a0", - "id": "fls_455dmnp4cxqv", - "link": "inline-assembly.html#fls_455dmnp4cxqv", - "number": "22.3:1" + "checksum": "d11ddb1c212025856ed6074fd11d79b465db9c57c2818f14597a4a2b51ef0028", + "id": "fls_q5swm5meafmx", + "link": "attributes.html#fls_q5swm5meafmx", + "number": "13.2.6.10:1" }, { - "checksum": "fa26d928624b04e379be24a2fc2001a49d414a019eebebf261e5a6c5951a3532", - "id": "fls_6bv3s8be5xif", - "link": "inline-assembly.html#fls_6bv3s8be5xif", - "number": "22.3:2" + "checksum": "68ed1d8594d8c5b565c9c3ba1dad3e246f93c1b2b85a0620b07350282ecfb68d", + "id": "fls_esaew4fqk8mm", + "link": "attributes.html#fls_esaew4fqk8mm", + "number": "13.2.6.10:2" }, { - "checksum": "c48eae85f32cfba112d97ce5307d7272d0911f9df4e67ba250f055efc0a0be0b", - "id": "fls_uddjvkz4g899", - "link": "inline-assembly.html#fls_uddjvkz4g899", - "number": "22.3:3" + "checksum": "9865aff343470dc6f503617f53b6b0518ff23e429de527be62858ede954a50ad", + "id": "fls_lvnclpxbye9u", + "link": "attributes.html#fls_lvnclpxbye9u", + "number": "13.2.6.10:3" }, { - "checksum": "de9b4bea7caed53ee7b0afc9fdfb7ce959dbe46af01b3c32dcdaca669dd8a0c0", - "id": "fls_sqs5to20p0te", - "link": "inline-assembly.html#fls_sqs5to20p0te", - "number": "22.3:4" + "checksum": "41b882e78b95a2fd3d2d109426bb31a61f34606a58d041b2592a662c22c5305b", + "id": "fls_VKuSiswPMll7", + "link": "attributes.html#fls_VKuSiswPMll7", + "number": "13.2.6.10:4" }, { - "checksum": "40c4d335089982458288126ed9615e8a2616285afc565532e391dd1ff35326f2", - "id": "fls_dzlycyk24euk", - "link": "inline-assembly.html#fls_dzlycyk24euk", - "number": "22.3:5" - }, + "checksum": "7a8578b15d438d33bfe5ea2e9b8441da5cd13f514c196d3711fda2f876d4e701", + "id": "fls_I029Rvr5BX5P", + "link": "attributes.html#fls_I029Rvr5BX5P", + "number": "13.2.6.10:5" + } + ], + "title": "Attribute no_mangle" + }, + { + "id": "fls_aibb2quva4mn", + "informational": false, + "link": "attributes.html#attribute-repr", + "number": "13.2.6.11", + "paragraphs": [ { - "checksum": "5154cd8c69374c45e9d741be110ba567134bb0b39eabcb8c458adbc972fdd2df", - "id": "fls_ics6gdzww1p", - "link": "inline-assembly.html#fls_ics6gdzww1p", - "number": "22.3:6" + "checksum": "10eda6760b60f8b3456de8229a6557a58fb131d2fbbdb50e40f210219814c453", + "id": "fls_vetjq9sw84qc", + "link": "attributes.html#fls_vetjq9sw84qc", + "number": "13.2.6.11:1" }, { - "checksum": "1fec5e0696f6525d77986c3fb4fe9afdfc0cb637119ad785681ba1c292856bae", - "id": "fls_mmc1w8jjr55r", - "link": "inline-assembly.html#fls_mmc1w8jjr55r", - "number": "22.3:7" - }, + "checksum": "0a18a601c320e54cccbb64a16e0ff23f67f5a8c9ee5e3fced2951001ef7cfd88", + "id": "fls_is2esjz1sy36", + "link": "attributes.html#fls_is2esjz1sy36", + "number": "13.2.6.11:2" + } + ], + "title": "Attribute repr" + }, + { + "id": "fls_19lnti3wabfd", + "informational": false, + "link": "attributes.html#attribute-unsafe", + "number": "13.2.6.12", + "paragraphs": [ { - "checksum": "1a6421d6b7caaf88bbb46cf868ffb92933d8f68b11221f3cc918d093f8888e72", - "id": "fls_9hhtcey2d4t6", - "link": "inline-assembly.html#fls_9hhtcey2d4t6", - "number": "22.3:8" + "checksum": "8342623b9650ffe1d4caaa6ba0d2c04837aecf96d6d8fa91600844a511c2ffe5", + "id": "fls_z5DTDKM9mU3o", + "link": "attributes.html#fls_z5DTDKM9mU3o", + "number": "13.2.6.12:1" }, { - "checksum": "b2936dd6ad92f8cfa53239fd025460f7d3e1b679ede4b445d3115ece8999ed98", - "id": "fls_8aynifgq02gt", - "link": "inline-assembly.html#fls_8aynifgq02gt", - "number": "22.3:9" + "checksum": "7bb763e6b566d916330764f843b5092bda30af6db45395cc44ef7efbe9fecf63", + "id": "fls_5pjo3nGOxbVw", + "link": "attributes.html#fls_5pjo3nGOxbVw", + "number": "13.2.6.12:2" }, { - "checksum": "0f946393a931cc5b78f9934819f7aa4ba6b514ee373b349ecce52f591ebcf435", - "id": "fls_5a3vfresnv5z", - "link": "inline-assembly.html#fls_5a3vfresnv5z", - "number": "22.3:10" + "checksum": "a927fa1655a602138331609181e3a8ce24c846c5dab93681a15c212346e2f64b", + "id": "fls_gpxlWbQUNsj8", + "link": "attributes.html#fls_gpxlWbQUNsj8", + "number": "13.2.6.12:3" }, { - "checksum": "4c9f00fb04a33159473b68a31e8ee74f70f23444dfe8e434256b0773119cc988", - "id": "fls_fta1gb5tzi3a", - "link": "inline-assembly.html#fls_fta1gb5tzi3a", - "number": "22.3:11" + "checksum": "287b25e9a268f4b0147d1c69c4e4d637601a42133ab7056edc4e33917f3a07d6", + "id": "fls_PhrfxSBvXTPV", + "link": "attributes.html#fls_PhrfxSBvXTPV", + "number": "13.2.6.12:4" }, { - "checksum": "ad39aebc78473e0d03c855868bf7740446e20ac900ed24bdf0f239dc211dfb67", - "id": "fls_sopiivuae0x7", - "link": "inline-assembly.html#fls_sopiivuae0x7", - "number": "22.3:12" + "checksum": "a267d2d8dc5df8de8cecf7df5fe42a741b7e0fff7a559232e3c33dfc5398c3e6", + "id": "fls_pCSew95bKJJ5", + "link": "attributes.html#fls_pCSew95bKJJ5", + "number": "13.2.6.12:5" }, { - "checksum": "2d30b2a811ba061cae0405a18f7f1f991bd3a4c2505bccd7e7be3afa49baf79b", - "id": "fls_81Ju1TEqJ48K", - "link": "inline-assembly.html#fls_81Ju1TEqJ48K", - "number": "22.3:13" + "checksum": "38376757927eaf5909477f2dca05aa7fb506242593d5e386b266d6ee5137b289", + "id": "fls_NRLgmOXxuljQ", + "link": "attributes.html#fls_NRLgmOXxuljQ", + "number": "13.2.6.12:6" }, { - "checksum": "503348cabcbc12fa0d9d17abf0e2019e91f2b649f8a985f9ccda7e84e2ac7ba4", - "id": "fls_kkrcyk96w8x1", - "link": "inline-assembly.html#fls_kkrcyk96w8x1", - "number": "22.3:14" + "checksum": "e65b61832ae1e972d420b56ea27d68dea076d20d6d2dc5f66b662790de5d45b5", + "id": "fls_ykpxNByUDyHG", + "link": "attributes.html#fls_ykpxNByUDyHG", + "number": "13.2.6.12:7" }, { - "checksum": "a383ba34b050c0c54cc8abca1eb3edacee0c31437bceca025437faa04647913b", - "id": "fls_aniw4ehsn2kb", - "link": "inline-assembly.html#fls_aniw4ehsn2kb", - "number": "22.3:15" - }, + "checksum": "bbacacadea0e15e11df2b03e93522e4dc19b9d42d1ebe55281c843437893bfca", + "id": "fls_2oP2nbDPtUg7", + "link": "attributes.html#fls_2oP2nbDPtUg7", + "number": "13.2.6.12:8" + } + ], + "title": "Attribute unsafe" + }, + { + "id": "fls_7skf24auayqy", + "informational": false, + "link": "attributes.html#attribute-used", + "number": "13.2.6.13", + "paragraphs": [ { - "checksum": "86940ba8544cf93b499195ba8a42affc88d320b1f29834d89a351cfbdd019105", - "id": "fls_j9XOoXDmN5Dq", - "link": "inline-assembly.html#fls_j9XOoXDmN5Dq", - "number": "22.3:16" + "checksum": "15b90a46b8d8e960e0c5c083b1de7c180ee05c88e6844587b34798d179ad1ee6", + "id": "fls_s4ii078wgpk", + "link": "attributes.html#fls_s4ii078wgpk", + "number": "13.2.6.13:1" }, { - "checksum": "3fd38b4ea292044ef82d8766b16d3c418062ba805b387bedc2880ed0d110fec4", - "id": "fls_jU8zg4k8dFsY", - "link": "inline-assembly.html#fls_jU8zg4k8dFsY", - "number": "22.3:17" - }, + "checksum": "34849743163e54f2380ebe33f0c44562d6903c45fbcb2d17d9b42cde2c5d27f4", + "id": "fls_k293nzcffks4", + "link": "attributes.html#fls_k293nzcffks4", + "number": "13.2.6.13:2" + } + ], + "title": "Attribute used" + }, + { + "id": "fls_cjq792yj6vft", + "informational": false, + "link": "attributes.html#limits-attributes", + "number": "13.2.7", + "paragraphs": [], + "title": "Limits Attributes" + }, + { + "id": "fls_u2hzlzpzh7yy", + "informational": false, + "link": "attributes.html#attribute-recursion-limit", + "number": "13.2.7.1", + "paragraphs": [ { - "checksum": "454698f13e45b8c93ac6b98fdde5ffd3c98fc9a27d568f69fb7431ffb7eee9bf", - "id": "fls_y2wCBvXDtQK2", - "link": "inline-assembly.html#fls_y2wCBvXDtQK2", - "number": "22.3:18" + "checksum": "e029467a0536bae96ae25a73cd801fbfc9c8874c35c9cef6110fda5cb0ae9916", + "id": "fls_o55cxc67sya7", + "link": "attributes.html#fls_o55cxc67sya7", + "number": "13.2.7.1:1" }, { - "checksum": "29c83fda93c055d8e8e1b5413124cd7b9321a5fcd2661ff0c6b2ad53c3a0cb10", - "id": "fls_66owmltvhnu4", - "link": "inline-assembly.html#fls_66owmltvhnu4", - "number": "22.3:19" - }, + "checksum": "181e8dbd1bc511f8b6a2fd59595070b2e85422b0c0b2fa446b31cf0408650ba2", + "id": "fls_o9p8fa8zhe15", + "link": "attributes.html#fls_o9p8fa8zhe15", + "number": "13.2.7.1:2" + } + ], + "title": "Attribute recursion_limit" + }, + { + "id": "fls_tdjjuwbr7mkg", + "informational": false, + "link": "attributes.html#attribute-type-length-limit", + "number": "13.2.7.2", + "paragraphs": [ { - "checksum": "8f3aef39cde848c0aff944c3688510c0623f11c4956e906b2c149933ca1bfc31", - "id": "fls_72p8e4bo6pns", - "link": "inline-assembly.html#fls_72p8e4bo6pns", - "number": "22.3:20" + "checksum": "4a50bf4d79c29ee4da73fa4b82e49dbde7e5bb057e6709e84b888a7458ea5356", + "id": "fls_dfnkzj8ob3uq", + "link": "attributes.html#fls_dfnkzj8ob3uq", + "number": "13.2.7.2:1" }, { - "checksum": "1d170272d80212e9ab38968ac21b6adb73b67fea20a811908cf097679cd04403", - "id": "fls_z0dbmmp5yblf", - "link": "inline-assembly.html#fls_z0dbmmp5yblf", - "number": "22.3:21" - }, + "checksum": "c1cc259e5bd84ef7201ecf3eea15ae58c6db27ab49f9b20cca614c7397dc0cd0", + "id": "fls_61vt1r8g51nh", + "link": "attributes.html#fls_61vt1r8g51nh", + "number": "13.2.7.2:2" + } + ], + "title": "Attribute type_length_limit" + }, + { + "id": "fls_2084b06dr0wz", + "informational": false, + "link": "attributes.html#macros-attributes", + "number": "13.2.8", + "paragraphs": [], + "title": "Macros Attributes" + }, + { + "id": "fls_qyudjghzfyjh", + "informational": false, + "link": "attributes.html#attribute-collapse-debuginfo", + "number": "13.2.8.1", + "paragraphs": [ { - "checksum": "bcda305ab21eb743bfbaf94192a7f091188f70e80d49d4ecc69cd944d560dbc0", - "id": "fls_4jdnt8uap95i", - "link": "inline-assembly.html#fls_4jdnt8uap95i", - "number": "22.3:22" + "checksum": "c412ca736f3a1f514ecfa77bc8072cda92f7f2b321b96f0715c267adb8ca8d34", + "id": "fls_EzKHtWHmXMAZ", + "link": "attributes.html#fls_EzKHtWHmXMAZ", + "number": "13.2.8.1:1" }, { - "checksum": "0937925a39e22d804ee545814336d49c733f31ea7756377335fefff6db62282f", - "id": "fls_wd2hzsbzdg2y", - "link": "inline-assembly.html#fls_wd2hzsbzdg2y", - "number": "22.3:23" - }, + "checksum": "1d0371f26b1d25705d10c9bb99d25dbcf8528ee5d114056d6623a5e2f0292f33", + "id": "fls_BCvJpfQMYEcD", + "link": "attributes.html#fls_BCvJpfQMYEcD", + "number": "13.2.8.1:2" + } + ], + "title": "Attribute collapse_debuginfo" + }, + { + "id": "fls_e0a96eb6ux3y", + "informational": false, + "link": "attributes.html#attribute-macro-export", + "number": "13.2.8.2", + "paragraphs": [ { - "checksum": "f7df1b813dc778c45c902762fd85f901ebede6cf627c93ddffa2731614f81305", - "id": "fls_sqy00lg5j7c6", - "link": "inline-assembly.html#fls_sqy00lg5j7c6", - "number": "22.3:24" + "checksum": "b7f82a1f9a690d7f22754bcc39c0c5a7aa96121a1dd60e82b27d66a66723bc28", + "id": "fls_3ma7zkk6john", + "link": "attributes.html#fls_3ma7zkk6john", + "number": "13.2.8.2:1" }, { - "checksum": "7dbe8e81fc6ab3a89a0c7db915942f414747d3e1426a7f0a9e0eb76bbcf6f54a", - "id": "fls_vxba1ttvz6hh", - "link": "inline-assembly.html#fls_vxba1ttvz6hh", - "number": "22.3:25" - }, + "checksum": "482470d45ab2936cb24974dba4d6dd68a83506c1c8d812b93b1125611ef28893", + "id": "fls_h26iw5wh4lla", + "link": "attributes.html#fls_h26iw5wh4lla", + "number": "13.2.8.2:2" + } + ], + "title": "Attribute macro_export" + }, + { + "id": "fls_qxjy0f758x5s", + "informational": false, + "link": "attributes.html#attribute-macro-use", + "number": "13.2.8.3", + "paragraphs": [ { - "checksum": "485c4204bb3fc6bde480eae99cb508323f08d53843fe840b571c140e2d09efaf", - "id": "fls_xkbnla2avrn0", - "link": "inline-assembly.html#fls_xkbnla2avrn0", - "number": "22.3:26" + "checksum": "cb9be301d970ad119fddcd5b1aed8838b378d008a99aee3b37680273ef7c2735", + "id": "fls_uua0nthq9id", + "link": "attributes.html#fls_uua0nthq9id", + "number": "13.2.8.3:1" }, { - "checksum": "2eae510aa3c0984999efed6c99d780375dd3245fbf78f98b65affdd980165179", - "id": "fls_w6jhcv616l9o", - "link": "inline-assembly.html#fls_w6jhcv616l9o", - "number": "22.3:27" + "checksum": "c57f6b3b535491797fe56ebb3d700049dd61c9daed4faeabf4b81ff930713d72", + "id": "fls_oq4kyo5z5tj5", + "link": "attributes.html#fls_oq4kyo5z5tj5", + "number": "13.2.8.3:2" }, { - "checksum": "bb5fda81378ad922bd59db0c0afb2d1243c2d1e2d61865303fb327146cc086cc", - "id": "fls_xweobiwapog1", - "link": "inline-assembly.html#fls_xweobiwapog1", - "number": "22.3:28" + "checksum": "0f7a33e20540faa9a9f1f692378ac792ba6bdad3b1cae65e1aa6f5f1d46f15ad", + "id": "fls_skexvtpbjknn", + "link": "attributes.html#fls_skexvtpbjknn", + "number": "13.2.8.3:3" }, { - "checksum": "8148c38c96dbc7562e0a236a6f7c2bf76b1224b0b409a58dbe086b5bd1d63c38", - "id": "fls_4matyejw6cls", - "link": "inline-assembly.html#fls_4matyejw6cls", - "number": "22.3:29" + "checksum": "1f21807e410d91d0bdf2a148125d253f2e11d2ef02a10360f295161deb2db79d", + "id": "fls_v03924dr0u0z", + "link": "attributes.html#fls_v03924dr0u0z", + "number": "13.2.8.3:4" }, { - "checksum": "fc4d057ac8b2c2a9823f6f4514305fd0ecc50af30f6f7ac3ed854e7f8a7e14c0", - "id": "fls_hklqabav1jju", - "link": "inline-assembly.html#fls_hklqabav1jju", - "number": "22.3:30" + "checksum": "d3ba7cfda60161cb47300e194e3d076342330aae22d93b71577c8de8eaa19e68", + "id": "fls_eha2hoey857x", + "link": "attributes.html#fls_eha2hoey857x", + "number": "13.2.8.3:5" }, { - "checksum": "a8f57c97461cbdfb32ce6148c10dcf2b2deb5070adcb2fe1bb4850b3c211ba8d", - "id": "fls_nq22h8gragil", - "link": "inline-assembly.html#fls_nq22h8gragil", - "number": "22.3:31" - }, + "checksum": "15633d7af605c9b73e99840c7e31720cc2a83e5731086fef2f9861de505c03ce", + "id": "fls_p6jlgmn2sg7j", + "link": "attributes.html#fls_p6jlgmn2sg7j", + "number": "13.2.8.3:6" + } + ], + "title": "Attribute macro_use" + }, + { + "id": "fls_qkmkev85o5jf", + "informational": false, + "link": "attributes.html#attribute-proc-macro", + "number": "13.2.8.4", + "paragraphs": [ { - "checksum": "a2c973cb292004179620ef85189058fef5ca0a6338ee56b885dfe5087c24658f", - "id": "fls_uxgcrs57bznk", - "link": "inline-assembly.html#fls_uxgcrs57bznk", - "number": "22.3:32" + "checksum": "a7056881313a129fd3ec31f81d6d3bb01a2c9d5f56b3d256ff553f5d47df7eda", + "id": "fls_u48dtmh97g", + "link": "attributes.html#fls_u48dtmh97g", + "number": "13.2.8.4:1" }, { - "checksum": "a70c379b4de9989a193e6532e92d5156358a876a75e38c960fda76d0d60c3647", - "id": "fls_ym05938ejwng", - "link": "inline-assembly.html#fls_ym05938ejwng", - "number": "22.3:33" - }, - { - "checksum": "3aee30c9effa5a942a1ada18c2ffb67bead416de8b1aa12b44d92fba4836a4e4", - "id": "fls_5l77g8h8et2o", - "link": "inline-assembly.html#fls_5l77g8h8et2o", - "number": "22.3:34" - }, - { - "checksum": "71c84d73a44e48e642f979b9a86af1793a323f1925a436f71dd359c01bb520e4", - "id": "fls_xlcliuums5b0", - "link": "inline-assembly.html#fls_xlcliuums5b0", - "number": "22.3:35" - }, - { - "checksum": "2bdd537d58814885175ea0a364633afe6412e67633eb7dac5eeeebbfce4eac16", - "id": "fls_5p4hyl7mxgai", - "link": "inline-assembly.html#fls_5p4hyl7mxgai", - "number": "22.3:36" - }, - { - "checksum": "3e4ce3eefa93e708f4edf4590415a8f929092e0676f38915c2d3bb267538656b", - "id": "fls_ilepg263w5o7", - "link": "inline-assembly.html#fls_ilepg263w5o7", - "number": "22.3:37" - }, - { - "checksum": "bffcbb94249f82f25927f78213e778f1a7f985c4920412526cbb8efaef4b7845", - "id": "fls_tubmavru8wvn", - "link": "inline-assembly.html#fls_tubmavru8wvn", - "number": "22.3:38" - }, - { - "checksum": "541ae9ba48b53ec96b54e4a5a32502dfa1000cf8754fe5ecc7d2312b34d2ee37", - "id": "fls_b1xi3u9k4pdl", - "link": "inline-assembly.html#fls_b1xi3u9k4pdl", - "number": "22.3:39" - }, - { - "checksum": "cb91ec0812f0c384bf095f2797ac51734ba4121d8e8ab16363d157860a106878", - "id": "fls_i9ds6724tv20", - "link": "inline-assembly.html#fls_i9ds6724tv20", - "number": "22.3:40" - }, - { - "checksum": "e2937f6d2545e4b849a96cff1214ad6058a60ad6ba062f6d3f55147d0b5bbedf", - "id": "fls_trldyekxxlzx", - "link": "inline-assembly.html#fls_trldyekxxlzx", - "number": "22.3:41" - }, - { - "checksum": "0c794e05658bfc67f5509b086682137d1e225b3d3125bc988b0f7dade8a602dd", - "id": "fls_efmpbyi4qjmf", - "link": "inline-assembly.html#fls_efmpbyi4qjmf", - "number": "22.3:42" - }, - { - "checksum": "c1e90e11afbfaff93f5efda1e0676fe9bc08b30d4293f557d10af6cef6e2f5b5", - "id": "fls_4x3w50w7qm8w", - "link": "inline-assembly.html#fls_4x3w50w7qm8w", - "number": "22.3:43" - }, - { - "checksum": "cc1f051a457238506a16c5250f10a10b89a5a6c4be1b61afbf9fb31e8db6299a", - "id": "fls_6cne58tlquze", - "link": "inline-assembly.html#fls_6cne58tlquze", - "number": "22.3:44" - }, - { - "checksum": "07f663b7ccb32c2c12d99fcfcdaf84bba2f001085ffcbdc46af147dbd632e2bf", - "id": "fls_5w718fne9jsh", - "link": "inline-assembly.html#fls_5w718fne9jsh", - "number": "22.3:45" - }, - { - "checksum": "7f8e00a6debc5e2204cfaa0b86e6518ec9b97c56b7803af2fb2df7eada835828", - "id": "fls_tel7kogaqytg", - "link": "inline-assembly.html#fls_tel7kogaqytg", - "number": "22.3:46" - }, - { - "checksum": "080df59e1c6daaf9dcbaf8723b6d2151909c977c1ca58d4543b4648be9c8db54", - "id": "fls_aw61psz5drg8", - "link": "inline-assembly.html#fls_aw61psz5drg8", - "number": "22.3:47" - }, - { - "checksum": "f4784be0f3397b59835c7c80fc3fa280f40f0c5a558cfeb0dd74943416b87ae3", - "id": "fls_sv2x3x81b32j", - "link": "inline-assembly.html#fls_sv2x3x81b32j", - "number": "22.3:48" - }, - { - "checksum": "74f321168c8014a105d3fe61537f3cf35443beabfa8f6fc079c2b904d6de8b85", - "id": "fls_nebb0nhxf5ix", - "link": "inline-assembly.html#fls_nebb0nhxf5ix", - "number": "22.3:49" - }, - { - "checksum": "07b64182b91f96ec3d687a71dbf44e3ef75125b7a754d09ee0c8274a774ab7c9", - "id": "fls_j0pxc8g8kcxm", - "link": "inline-assembly.html#fls_j0pxc8g8kcxm", - "number": "22.3:50" - }, - { - "checksum": "f173b49637243cd66db248543679179b3894437aad904b62189d0106a6448977", - "id": "fls_wwh6xyclxwqj", - "link": "inline-assembly.html#fls_wwh6xyclxwqj", - "number": "22.3:51" - }, - { - "checksum": "9806b7c79391b43a6ff2c6de978f08dbf262be1f09d5994c4a3b5176b86d35da", - "id": "fls_qcb47z1ap9dz", - "link": "inline-assembly.html#fls_qcb47z1ap9dz", - "number": "22.3:52" - }, - { - "checksum": "d82b2f675143ea3e841f686833a7a7a875694a71bea0a335101d9f496b02b415", - "id": "fls_h01au4vk8mjd", - "link": "inline-assembly.html#fls_h01au4vk8mjd", - "number": "22.3:53" - }, - { - "checksum": "f531c0d0ea7572debc684b12dbc0edf4d3924d960344eb4cbe9fdd03c62ba2cd", - "id": "fls_92ijsf4p6yn", - "link": "inline-assembly.html#fls_92ijsf4p6yn", - "number": "22.3:54" - }, - { - "checksum": "f7e4dae4c4ddaa16ca26afebb3f35a670745389e64b88c65844e029c7a0cd97b", - "id": "fls_xkui7j3gnfg0", - "link": "inline-assembly.html#fls_xkui7j3gnfg0", - "number": "22.3:55" - }, - { - "checksum": "1e37a5e34d46afce6f7c07e6ac3a4bf3326a0f8b0b5f47b6398afc090f6bfe24", - "id": "fls_eahyqniqs2pn", - "link": "inline-assembly.html#fls_eahyqniqs2pn", - "number": "22.3:56" - }, - { - "checksum": "2a483ecef64dcb6a3f27eff5e1df5885c60546538f60c4dbfe1df9f84440e25b", - "id": "fls_5g7p2zo07gfe", - "link": "inline-assembly.html#fls_5g7p2zo07gfe", - "number": "22.3:57" - }, - { - "checksum": "412c8164a6ff9c0f787cf54c8a347a7a17608a58d8e21510f1752e83c5090438", - "id": "fls_dobbatnjs0yt", - "link": "inline-assembly.html#fls_dobbatnjs0yt", - "number": "22.3:58" - }, - { - "checksum": "3ace63d8c3c27df97ec81e12c7db56b091597dd8e85b82a39419826a4e0e9964", - "id": "fls_ax8t4uta34ym", - "link": "inline-assembly.html#fls_ax8t4uta34ym", - "number": "22.3:59" - }, - { - "checksum": "b4b8fd6cf4f2c93377215e7dc6ab80bfd0fb116d3f2aa401c9b213f57ec725fb", - "id": "fls_dvft4ha00wj3", - "link": "inline-assembly.html#fls_dvft4ha00wj3", - "number": "22.3:60" - }, - { - "checksum": "719c2d431cf4fa8f1d481db9a1a415bbdba221e1f78a967d89e4fccfc93fa6bb", - "id": "fls_2ekwpx2bwj1b", - "link": "inline-assembly.html#fls_2ekwpx2bwj1b", - "number": "22.3:61" - }, - { - "checksum": "9b44340bf99e2336f97d4544df886ba2d990b67388ac619ac2d18fb6b917a484", - "id": "fls_3s2n9dlrlhz9", - "link": "inline-assembly.html#fls_3s2n9dlrlhz9", - "number": "22.3:62" - }, - { - "checksum": "fa9005c5774af5498655cd144555d0013c40b6180373f8c7e8918e1ba12e03aa", - "id": "fls_nbkkz6krcngi", - "link": "inline-assembly.html#fls_nbkkz6krcngi", - "number": "22.3:63" - }, - { - "checksum": "435b90abb518be657a6bf0d3065224d00b9a224cd9f274e5cf1aeb3a1270b306", - "id": "fls_utrvenwrettz", - "link": "inline-assembly.html#fls_utrvenwrettz", - "number": "22.3:64" - }, - { - "checksum": "7b31b5b6ead309666eb975b9d0834ba47f9a3fa55195978c7d27e62afaadd0f0", - "id": "fls_n85sjh925x", - "link": "inline-assembly.html#fls_n85sjh925x", - "number": "22.3:65" - } - ], - "title": "Register Arguments" - }, - { - "id": "fls_e0896uk0mdyl", - "informational": false, - "link": "inline-assembly.html#assembly-instructions", - "number": "22.4", - "paragraphs": [ - { - "checksum": "e080d5e0ebf43bfbc4219d2d2e397aef7c54aa4e7a03b032120b3ba79a54a4d6", - "id": "fls_4jr7eg6e0g4w", - "link": "inline-assembly.html#fls_4jr7eg6e0g4w", - "number": "22.4:1" - }, - { - "checksum": "3ec0655943bc0361bb5ee51dee65ef55fc0cdbba100b9689c377aa00b58bbfcf", - "id": "fls_ihjhpy4osl53", - "link": "inline-assembly.html#fls_ihjhpy4osl53", - "number": "22.4:2" - }, - { - "checksum": "a9365d11f22e6d0282ff7a7829d262dd0237bb7df54f65c07d06cac38a8ef4e5", - "id": "fls_2d05gcixjrzt", - "link": "inline-assembly.html#fls_2d05gcixjrzt", - "number": "22.4:3" - }, - { - "checksum": "f0766d5049dcbb5d0c9cd71d282ee6fadf59ca4944f8fcfe3f0a3076cbee4359", - "id": "fls_z64f094aivp6", - "link": "inline-assembly.html#fls_z64f094aivp6", - "number": "22.4:4" - }, - { - "checksum": "818e1461216ec078c22ad83372847074fe214f98afcc5de45e628dae31f6c1f9", - "id": "fls_u8lifqig90gq", - "link": "inline-assembly.html#fls_u8lifqig90gq", - "number": "22.4:5" - }, - { - "checksum": "a8ae2c3d3244053dde269a59221580fbe43986dc1e0db3bb6a587f6818456d11", - "id": "fls_lfeun3er5sc9", - "link": "inline-assembly.html#fls_lfeun3er5sc9", - "number": "22.4:6" - }, - { - "checksum": "df6f74ba0870e9951d73e552be2119d9b8d9cde3bfe76879dbc0b4d621f4adff", - "id": "fls_mmdmymljq8a3", - "link": "inline-assembly.html#fls_mmdmymljq8a3", - "number": "22.4:7" - }, - { - "checksum": "d5f187840f02cfdf21c191f7e410d01ce205cd27e448a8ed69b3b623d6a75434", - "id": "fls_xugsn2ghh73c", - "link": "inline-assembly.html#fls_xugsn2ghh73c", - "number": "22.4:8" - }, - { - "checksum": "78dc81cd3902f11ea65d5e75e486faeb11ca34a926001792ce627e4c3f8fd23f", - "id": "fls_opnxq5kyw9jo", - "link": "inline-assembly.html#fls_opnxq5kyw9jo", - "number": "22.4:9" - }, - { - "checksum": "67eac47f7353bee4b01c2c0155a18d611ee18841d560d9d21ef05ad20fde5032", - "id": "fls_wydu9yft7a3r", - "link": "inline-assembly.html#fls_wydu9yft7a3r", - "number": "22.4:10" - } - ], - "title": "Assembly Instructions" - }, - { - "id": "fls_lv19xysy1f7e", - "informational": false, - "link": "inline-assembly.html#register-parameter-modifiers", - "number": "22.4.1", - "paragraphs": [ - { - "checksum": "df3daccc1134fbde7da5c81da7286e4a8aee9877bdc758f10041315d7afdbc82", - "id": "fls_2xilifichdqu", - "link": "inline-assembly.html#fls_2xilifichdqu", - "number": "22.4.1:1" - }, - { - "checksum": "8a38fde192b47c9883e5f9c64b28fc71e84f6027fdf2b6510307bd918adb2c36", - "id": "fls_o3fx9397ib74", - "link": "inline-assembly.html#fls_o3fx9397ib74", - "number": "22.4.1:2" - }, - { - "checksum": "c4b970759ed150c13e63c36cd2534ef92a591c902367bfc2ecccc00302882d45", - "id": "fls_3atrad53m22a", - "link": "inline-assembly.html#fls_3atrad53m22a", - "number": "22.4.1:3" - }, - { - "checksum": "2aa77d6c13b1986b1ca71de2a9193b60d5f2ad0e8326131351cbe4785d63394a", - "id": "fls_5zaqgz9jc8gy", - "link": "inline-assembly.html#fls_5zaqgz9jc8gy", - "number": "22.4.1:4" - }, - { - "checksum": "1dbdb7b1cb424770dccf1f1ca6a22190bfded4e3a864ebe0c70138dcb08aa376", - "id": "fls_erjczjotwqc3", - "link": "inline-assembly.html#fls_erjczjotwqc3", - "number": "22.4.1:5" - }, - { - "checksum": "fce3a5b86ad0c82c14627febfea99be0d1adbc87a19da2a21ac7918c982c2165", - "id": "fls_z8m45i9xqkct", - "link": "inline-assembly.html#fls_z8m45i9xqkct", - "number": "22.4.1:6" - }, - { - "checksum": "f2a6f18c824fdc7760e14c9983e4f55bb428d7bc2e926c5336d84aebcad96812", - "id": "fls_adaavz3olha3", - "link": "inline-assembly.html#fls_adaavz3olha3", - "number": "22.4.1:7" - }, - { - "checksum": "402be3937906f8d4fa926dd2d3368473a0c85d6086a0eaf2ced7795159268355", - "id": "fls_u5dzca5f2pfm", - "link": "inline-assembly.html#fls_u5dzca5f2pfm", - "number": "22.4.1:8" - }, - { - "checksum": "a46c8e5b6d61001549276e9ceda68d5082cde5d8c411b9fcd4322e415159f543", - "id": "fls_6542a2hi5yhc", - "link": "inline-assembly.html#fls_6542a2hi5yhc", - "number": "22.4.1:9" - }, - { - "checksum": "69d1797e8dae4213d66892c3b2df1eccc2ba619132a731ed8578a80930d8b125", - "id": "fls_94thqwabspw2", - "link": "inline-assembly.html#fls_94thqwabspw2", - "number": "22.4.1:10" - }, - { - "checksum": "a26371ca4aa01bab4d2e1d2db822d99d09f609cc2e368e34640291ed8b08987b", - "id": "fls_xhp8xu8xvvgd", - "link": "inline-assembly.html#fls_xhp8xu8xvvgd", - "number": "22.4.1:11" - }, - { - "checksum": "934b550a3ace7fdebb140b888b4c6b0bd6fc15ba51a11245c09777b143e31c74", - "id": "fls_g0p2ebuffnxb", - "link": "inline-assembly.html#fls_g0p2ebuffnxb", - "number": "22.4.1:12" - }, - { - "checksum": "91e48c407ab94676b42f914c8b5e3d221426fc65422e434aee70e89bc2a2575e", - "id": "fls_d0e2weni8q87", - "link": "inline-assembly.html#fls_d0e2weni8q87", - "number": "22.4.1:13" - }, - { - "checksum": "c267be0d4596f4e187e1217ff192f745f74d1ed2d2e60cffc633f57ab23a3d0f", - "id": "fls_bq3p4k42tzh8", - "link": "inline-assembly.html#fls_bq3p4k42tzh8", - "number": "22.4.1:14" - }, - { - "checksum": "fbd2694649731061bfc93b035df30a2ffd8057aa228d378e6a1bbbd4d0c3b01b", - "id": "fls_l5mkfdot97fz", - "link": "inline-assembly.html#fls_l5mkfdot97fz", - "number": "22.4.1:15" - }, - { - "checksum": "a80431476395f4d9b37789b614a02fd3ed653fbfdbe231393ee74f8ffa344d62", - "id": "fls_sarl1hoq0lco", - "link": "inline-assembly.html#fls_sarl1hoq0lco", - "number": "22.4.1:16" - }, - { - "checksum": "e8ec5785b962e9d67a7073c3f3ed029663329777ffe7ed04740bb5bae1a2ca3d", - "id": "fls_f4q9a0jrs4ek", - "link": "inline-assembly.html#fls_f4q9a0jrs4ek", - "number": "22.4.1:17" - }, - { - "checksum": "94c265f36be700f5ab1b92a26255bccb819f831e2c5eb49430450911142fd3fc", - "id": "fls_w0hn6vkpuvpc", - "link": "inline-assembly.html#fls_w0hn6vkpuvpc", - "number": "22.4.1:18" - }, - { - "checksum": "a9dbcb5f2bc6a82cb619b70700efb6c5fb95c9028afc2ed8d33d54d9b8cc4285", - "id": "fls_a7bvkyh5otx5", - "link": "inline-assembly.html#fls_a7bvkyh5otx5", - "number": "22.4.1:19" - }, - { - "checksum": "210fc5aba0385a72ab09974dd17ebe68a03a97d12721b43936fe872062b67039", - "id": "fls_klthi4cczkem", - "link": "inline-assembly.html#fls_klthi4cczkem", - "number": "22.4.1:20" - }, - { - "checksum": "99debc3917e79cbba4f44a5de1c26fe2a35a28b999da0b9e0896c74136c7f55a", - "id": "fls_pizgnxvcnj46", - "link": "inline-assembly.html#fls_pizgnxvcnj46", - "number": "22.4.1:21" - }, - { - "checksum": "4646e2e4cfd3b465d1c592a0f0fc10fc28890d013e8bc48635645162b72b1c25", - "id": "fls_pvzfiuxka6wg", - "link": "inline-assembly.html#fls_pvzfiuxka6wg", - "number": "22.4.1:22" - }, - { - "checksum": "13837d13ce9ed671b2a8b76b9831fa0d911cd1db7ef3fd3c4dc3393776c153e8", - "id": "fls_pf8yj3m81wk4", - "link": "inline-assembly.html#fls_pf8yj3m81wk4", - "number": "22.4.1:23" - }, - { - "checksum": "5f0c87c237f4329b033b02aa1a4e501b3ef81ac67ae21f801fc66e6f1860dbdc", - "id": "fls_2bf3ady2idq1", - "link": "inline-assembly.html#fls_2bf3ady2idq1", - "number": "22.4.1:24" - }, - { - "checksum": "eb8c9934a20fbabd92ca5744c146afd324847e0dfda556cf639c4dfb8f63fe77", - "id": "fls_x1se4r75v58o", - "link": "inline-assembly.html#fls_x1se4r75v58o", - "number": "22.4.1:25" - }, - { - "checksum": "a71927d83c22a012d774779a46a3cc373fd770ac798eaa5bccaa5727d5fc7d23", - "id": "fls_rgovn5r3caif", - "link": "inline-assembly.html#fls_rgovn5r3caif", - "number": "22.4.1:26" - }, - { - "checksum": "d6f5bc80d218fd2eb5f767c7df3d43a0fbb3a4f9d611ff09ec5e2db2d73b964b", - "id": "fls_faxm8xkhruvz", - "link": "inline-assembly.html#fls_faxm8xkhruvz", - "number": "22.4.1:27" - }, - { - "checksum": "6cff8dae509fe0e99e81732637131e38defb7dc8880572cd5eaab052c4f188ae", - "id": "fls_x1kkkvugpkyd", - "link": "inline-assembly.html#fls_x1kkkvugpkyd", - "number": "22.4.1:28" - }, - { - "checksum": "486e270c4089e2051cac9eaba641373777e921aa5c7fe474f0ca357161b85e9e", - "id": "fls_uonqmj16oqxe", - "link": "inline-assembly.html#fls_uonqmj16oqxe", - "number": "22.4.1:29" - }, - { - "checksum": "ab3d0ca0fad9f39d9ea1ee3f6e6bd6a62feaf78a7c4ad89e4da7e1c9e1cd94e9", - "id": "fls_ydhwlp56vmrz", - "link": "inline-assembly.html#fls_ydhwlp56vmrz", - "number": "22.4.1:30" - }, - { - "checksum": "88ba61d8f48ccf3c27d589bf4bce3d0792b2193ee35a423cf45e9b005f6270f3", - "id": "fls_yolqzfqbfjoh", - "link": "inline-assembly.html#fls_yolqzfqbfjoh", - "number": "22.4.1:31" - }, + "checksum": "c2045a082f6a84673475a58ccde2204fadd47f7c6e23407bfaed9319e1242fdd", + "id": "fls_t4ez0zg1m569", + "link": "attributes.html#fls_t4ez0zg1m569", + "number": "13.2.8.4:2" + } + ], + "title": "Attribute proc_macro" + }, + { + "id": "fls_ejhlylrcajo", + "informational": false, + "link": "attributes.html#attribute-proc-macro-attribute", + "number": "13.2.8.5", + "paragraphs": [ { - "checksum": "4cc9da7a653b181d53c273bb4262f0da36f474bba28cccad6515df9569c34691", - "id": "fls_ojzzhoed6t9l", - "link": "inline-assembly.html#fls_ojzzhoed6t9l", - "number": "22.4.1:32" + "checksum": "d914e82b19183ec81ba7ca878ce84320d00af280cfaeebaf06a1bce3ce91dc34", + "id": "fls_huznzmkuhdky", + "link": "attributes.html#fls_huznzmkuhdky", + "number": "13.2.8.5:1" }, { - "checksum": "7fbd7be9fcd499335adb0893a1a8a65bec287ca73feb1889dc5982a40247b238", - "id": "fls_gw56ok8llid3", - "link": "inline-assembly.html#fls_gw56ok8llid3", - "number": "22.4.1:33" - }, + "checksum": "51be5aebe1c07fea1dd288f810fd58b529feabbb2db4633c447eac8036526b22", + "id": "fls_gc3ly8fsodf1", + "link": "attributes.html#fls_gc3ly8fsodf1", + "number": "13.2.8.5:2" + } + ], + "title": "Attribute proc_macro_attribute" + }, + { + "id": "fls_q6qecp6e413", + "informational": false, + "link": "attributes.html#attribute-proc-macro-derive", + "number": "13.2.8.6", + "paragraphs": [ { - "checksum": "72a5a17c379fbb6bacbacf067b17b2ceea95cab6686fa06bc8a48211b01489fa", - "id": "fls_ry7qoosmjrev", - "link": "inline-assembly.html#fls_ry7qoosmjrev", - "number": "22.4.1:34" + "checksum": "ccd312dde256071afed24a8368cb4561a8238f97767345ad8286d082eb213a6e", + "id": "fls_l82yswg0k3px", + "link": "attributes.html#fls_l82yswg0k3px", + "number": "13.2.8.6:1" }, { - "checksum": "6790bb787a1e84d353705bdf02d3ecff05ea2e3e901235703945395484fba35d", - "id": "fls_i0ax45x2wskd", - "link": "inline-assembly.html#fls_i0ax45x2wskd", - "number": "22.4.1:35" + "checksum": "c57f5a4e85fdec7a28646b6dac958146ae5ffacab2268b24082814db828cc895", + "id": "fls_ir9i4i2x5gyx", + "link": "attributes.html#fls_ir9i4i2x5gyx", + "number": "13.2.8.6:2" }, { - "checksum": "173a7c080780f8a61d2439061e6ae4d2d179116636cebf6c3b387311b75d8cdc", - "id": "fls_op4dx1rqwhsf", - "link": "inline-assembly.html#fls_op4dx1rqwhsf", - "number": "22.4.1:36" + "checksum": "f06497bca4b72dd176f21ed2508cb69fa3833ef6cbd8a4b22af928fc995b81b8", + "id": "fls_NydVxyb43TH6", + "link": "attributes.html#fls_NydVxyb43TH6", + "number": "13.2.8.6:3" } ], - "title": "Register Parameter Modifiers" + "title": "Attribute proc_macro_derive" }, { - "id": "fls_6momhvgx4w21", + "id": "fls_7bb5ua4g06k8", "informational": false, - "link": "inline-assembly.html#directive-support", - "number": "22.4.2", + "link": "attributes.html#modules-attributes", + "number": "13.2.9", + "paragraphs": [], + "title": "Modules Attributes" + }, + { + "id": "fls_1zbaajz5prpn", + "informational": false, + "link": "attributes.html#attribute-path", + "number": "13.2.9.1", "paragraphs": [ { - "checksum": "273042489d4acdfdbbb4c1c21595ddaf45d9567bc4f222b33e969448bfe38b69", - "id": "fls_4tfod2vgz2m6", - "link": "inline-assembly.html#fls_4tfod2vgz2m6", - "number": "22.4.2:1" + "checksum": "82da18a25355abd0b9d6d12edfe37ae45678bc9c547052772f63a9a356614526", + "id": "fls_lht4pcezmbxw", + "link": "attributes.html#fls_lht4pcezmbxw", + "number": "13.2.9.1:1" }, { - "checksum": "c5968dc1b0f463f09aeb9cd4b2aa570644d895641c7b98e829068c6a3b7bcead", - "id": "fls_3b0ab1nlo641", - "link": "inline-assembly.html#fls_3b0ab1nlo641", - "number": "22.4.2:2" + "checksum": "1779cf2215b4d1593fa40611459d55cb2246586eee4503f9d2e823cc1871c703", + "id": "fls_qb6anohvc03k", + "link": "attributes.html#fls_qb6anohvc03k", + "number": "13.2.9.1:2" }, { - "checksum": "b3410bc20ea46ca83da705dd5c3d4bd7e5dbcbb0cb06be3739e98bde74815d2e", - "id": "fls_caqznttql5p8", - "link": "inline-assembly.html#fls_caqznttql5p8", - "number": "22.4.2:3" - }, + "checksum": "074e1f0751b8cd12b037a8b551dc3d753dae8a3d8c8238db1bd5f168eab83a8f", + "id": "fls_18tcecx4p2wp", + "link": "attributes.html#fls_18tcecx4p2wp", + "number": "13.2.9.1:3" + } + ], + "title": "Attribute path" + }, + { + "id": "fls_go457hpaf7ov", + "informational": false, + "link": "attributes.html#prelude-attributes", + "number": "13.2.10", + "paragraphs": [], + "title": "Prelude Attributes" + }, + { + "id": "fls_iikmhqsp1r5a", + "informational": false, + "link": "attributes.html#attribute-no-implicit-prelude", + "number": "13.2.10.1", + "paragraphs": [ { - "checksum": "327bbebd420db49f61b331ce68c29a03205db8e1034271f35724e3bff4967225", - "id": "fls_bcheqswo7a1", - "link": "inline-assembly.html#fls_bcheqswo7a1", - "number": "22.4.2:4" + "checksum": "78b6ef76a225f8a6314dbb484e5f6c33a4323ad9202816ead2f71474e4fcdc82", + "id": "fls_tki5k5uo74gw", + "link": "attributes.html#fls_tki5k5uo74gw", + "number": "13.2.10.1:1" }, { - "checksum": "322b37644fe5b701127925bfa2b7e952ba5a3875e5ea4488a332ddde869dc5fd", - "id": "fls_qxcl999rdwam", - "link": "inline-assembly.html#fls_qxcl999rdwam", - "number": "22.4.2:5" + "checksum": "81930ef591f96d6b1e90e5ad685dadeadd18244ffd6bf2b93e1e564d00df7002", + "id": "fls_cmrqxc5oax4r", + "link": "attributes.html#fls_cmrqxc5oax4r", + "number": "13.2.10.1:2" }, { - "checksum": "b253c450a0ba8f47f8d9771eac1d50f4af75e1f2c3a1287d83242bd3fe0dece5", - "id": "fls_2yi7kjnhkfme", - "link": "inline-assembly.html#fls_2yi7kjnhkfme", - "number": "22.4.2:6" - }, + "checksum": "63933d48ff890b9977fb1e7739ec4a0c3fce66da852cfc19921e9fef4c0c30c6", + "id": "fls_c7v2hbdb7g2d", + "link": "attributes.html#fls_c7v2hbdb7g2d", + "number": "13.2.10.1:3" + } + ], + "title": "Attribute no_implicit_prelude" + }, + { + "id": "fls_9xnaxd7qbakp", + "informational": false, + "link": "attributes.html#attribute-no-std", + "number": "13.2.10.2", + "paragraphs": [ { - "checksum": "38d62d0687777daed55c786318f571e04236edf24b0be2be66fb64bcc79a1ec0", - "id": "fls_q0jp60aj81nv", - "link": "inline-assembly.html#fls_q0jp60aj81nv", - "number": "22.4.2:7" + "checksum": "bf65af2170c2fce6c798624dc20ef98cdd4c56ba8a6d771db6d63c5b33b9fe50", + "id": "fls_qnxihxsvkyf6", + "link": "attributes.html#fls_qnxihxsvkyf6", + "number": "13.2.10.2:1" }, { - "checksum": "74b6e657cec0604048f4b316ae345d1d3607cb8345b45d3c19a0762e701bb114", - "id": "fls_akny3esj88yy", - "link": "inline-assembly.html#fls_akny3esj88yy", - "number": "22.4.2:8" + "checksum": "812969d17f6cf1c8f09c3aa7e496056b918a7936c5656724b5023957f1a6480d", + "id": "fls_kxav9vw59ts4", + "link": "attributes.html#fls_kxav9vw59ts4", + "number": "13.2.10.2:2" }, { - "checksum": "d74eb6d81576b2d4afe799e9b3c3af708fffc38c914243232b77c7da8763f198", - "id": "fls_9jajt7jn9cxk", - "link": "inline-assembly.html#fls_9jajt7jn9cxk", - "number": "22.4.2:9" + "checksum": "c0823a0a3da1888952864c8a1e51dcb076f7acb24133027a4c96805704a36b87", + "id": "fls_ve1shwjq09pl", + "link": "attributes.html#fls_ve1shwjq09pl", + "number": "13.2.10.2:3" }, { - "checksum": "bf756baa1fd54ce8e191d9722e682a9688658b4f09b5a2d2adc2be66ec71f671", - "id": "fls_i7dr87fyrei8", - "link": "inline-assembly.html#fls_i7dr87fyrei8", - "number": "22.4.2:10" + "checksum": "be41407d903f76e4c4f3bb28728743dc02d28c58b2abb01664514d25f6707420", + "id": "fls_wgwsn7laoju7", + "link": "attributes.html#fls_wgwsn7laoju7", + "number": "13.2.10.2:4" }, { - "checksum": "d0ce672b39bf64337b97f071c7ba60f06f19162e6e462862b7d3ba89bbf35177", - "id": "fls_rjzgpxt8z8x", - "link": "inline-assembly.html#fls_rjzgpxt8z8x", - "number": "22.4.2:11" - }, + "checksum": "55f1527bb22419e4a271f37b4ddc4109e3478e34cbcaf56381afe6e82f4ff76a", + "id": "fls_lxkd6hdboav4", + "link": "attributes.html#fls_lxkd6hdboav4", + "number": "13.2.10.2:5" + } + ], + "title": "Attribute no_std" + }, + { + "id": "fls_nbbvukrdngev", + "informational": false, + "link": "attributes.html#runtime-attributes", + "number": "13.2.11", + "paragraphs": [], + "title": "Runtime Attributes" + }, + { + "id": "fls_fs0lcfllamj", + "informational": false, + "link": "attributes.html#attribute-global-allocator", + "number": "13.2.11.1", + "paragraphs": [ { - "checksum": "8f07653c7f5ffded5888d86d7b90df75357f168c0520bdf55624bb2c836d2f9b", - "id": "fls_iqrjkvgae5k", - "link": "inline-assembly.html#fls_iqrjkvgae5k", - "number": "22.4.2:12" + "checksum": "b627ddaa5ef649b306598dd5f6297c67a76f75ec92d0b5746f31fa0163690273", + "id": "fls_5b8aewlgeon8", + "link": "attributes.html#fls_5b8aewlgeon8", + "number": "13.2.11.1:1" }, { - "checksum": "8f0cc6ef2c23c8cb4bee94667d1ec39ea29fbe2c83738e9a0a6911180130faee", - "id": "fls_tzb5diegx3d5", - "link": "inline-assembly.html#fls_tzb5diegx3d5", - "number": "22.4.2:13" - }, + "checksum": "da27332328b097f036a002143a0c62df334d3fafe943281b9e5ac3f53256ac87", + "id": "fls_homoidh8mu1r", + "link": "attributes.html#fls_homoidh8mu1r", + "number": "13.2.11.1:2" + } + ], + "title": "Attribute global_allocator" + }, + { + "id": "fls_ls5eryuoxlp9", + "informational": false, + "link": "attributes.html#attribute-panic-handler", + "number": "13.2.11.2", + "paragraphs": [ { - "checksum": "540a3815a037e2019aa295eebe637bce2924911055ffd22655e0f7105361d716", - "id": "fls_82nia9oagat", - "link": "inline-assembly.html#fls_82nia9oagat", - "number": "22.4.2:14" + "checksum": "811d25ac05c366571fee950dbae9130bfedbc07a6e6b74ecfd339fc993199f16", + "id": "fls_ryz8qy1wdnma", + "link": "attributes.html#fls_ryz8qy1wdnma", + "number": "13.2.11.2:1" }, { - "checksum": "4c30c61a09b304482477071c1e8940277819245caa2586bd24d5f47c8f0604ea", - "id": "fls_qg6wt4plwnw6", - "link": "inline-assembly.html#fls_qg6wt4plwnw6", - "number": "22.4.2:15" + "checksum": "19a78bf1c35131ec0625e919fd7f92ef2acc5b5986bdae84101d8449b0bf671a", + "id": "fls_ncyod5gegMqL", + "link": "attributes.html#fls_ncyod5gegMqL", + "number": "13.2.11.2:2" }, { - "checksum": "7b42846b370887a3f35c3a27a083518bb3dc29223630fbcb50980f0b92d4c3c5", - "id": "fls_8ci8ukk25nz3", - "link": "inline-assembly.html#fls_8ci8ukk25nz3", - "number": "22.4.2:16" + "checksum": "7be989fe417f083b8e5dd74846cda7e32d86a89db6194539d9733eb40ba837bc", + "id": "fls_jzIHzPkJqVm8", + "link": "attributes.html#fls_jzIHzPkJqVm8", + "number": "13.2.11.2:3" }, { - "checksum": "c180e76cf8759c8f1c126845c7c4691255b1fc7300dd6d998b1ec33d2ba5193a", - "id": "fls_ysbaz052rjg4", - "link": "inline-assembly.html#fls_ysbaz052rjg4", - "number": "22.4.2:17" + "checksum": "12c2336bf9e9b05b5fe0f3bab73c7b1cdb604d082afa1e7984eb3b79f71b3642", + "id": "fls_qXii07xWRUoT", + "link": "attributes.html#fls_qXii07xWRUoT", + "number": "13.2.11.2:4" }, { - "checksum": "2237d36080e7a184ba2787ef43ea4309766b3fe20fcd28e287f48d8467f79652", - "id": "fls_lbazk0g9r350", - "link": "inline-assembly.html#fls_lbazk0g9r350", - "number": "22.4.2:18" + "checksum": "a88dbeff7797209a81595c30a58d7f150b9104f217bcef43b2ee461555a5a0ce", + "id": "fls_ZEjvn4mmfSct", + "link": "attributes.html#fls_ZEjvn4mmfSct", + "number": "13.2.11.2:5" }, { - "checksum": "3a38d11b37270cb84ae0f2d6aeb76dd4852810ab70083e2f74a868de0b004dcb", - "id": "fls_2dui79hn30o7", - "link": "inline-assembly.html#fls_2dui79hn30o7", - "number": "22.4.2:19" + "checksum": "051e4b8ff27f299cff0e443ea677b2f935831974162b34dba8fd7b30feb610a7", + "id": "fls_iWPw0QFE5TrP", + "link": "attributes.html#fls_iWPw0QFE5TrP", + "number": "13.2.11.2:6" }, { - "checksum": "913c7a58bd440f285a8792fb9cb87bfe92b9d9e3e781b519576338a87006b568", - "id": "fls_qzwyjj6xxwc2", - "link": "inline-assembly.html#fls_qzwyjj6xxwc2", - "number": "22.4.2:20" + "checksum": "c82af1ca7021a4e17089b436f3d65889b6c5d9a6e7600fae295b40ff0f0f6ec6", + "id": "fls_WirXqd7UV4M0", + "link": "attributes.html#fls_WirXqd7UV4M0", + "number": "13.2.11.2:7" }, { - "checksum": "b999de4283114f3434ff6b2f3d644c97f9947cdb3682846f9e5cfe9198c41071", - "id": "fls_9flwdfh5crsk", - "link": "inline-assembly.html#fls_9flwdfh5crsk", - "number": "22.4.2:21" + "checksum": "8e4d1d0997dd94de884dbc95268544335e331949cd1d45db0be53a80200a5714", + "id": "fls_5CGTQTuT0I5v", + "link": "attributes.html#fls_5CGTQTuT0I5v", + "number": "13.2.11.2:8" }, { - "checksum": "e95b22baaaaccde95e5436616a10d509d3d62c095e320c2f3c8dd4e40946315a", - "id": "fls_u1c09ssrllil", - "link": "inline-assembly.html#fls_u1c09ssrllil", - "number": "22.4.2:22" + "checksum": "7b2ec9db6d8b1c8ace22368047c47d159462712924d519696cffb84e602fcbfc", + "id": "fls_8gqun8lma9wz", + "link": "attributes.html#fls_8gqun8lma9wz", + "number": "13.2.11.2:9" }, { - "checksum": "5cabacdc42396ecdc4239421e1c94751f7e18bae7137ce2e7a90b826dc77137d", - "id": "fls_tx58qbvh3jz3", - "link": "inline-assembly.html#fls_tx58qbvh3jz3", - "number": "22.4.2:23" - }, + "checksum": "f8d09837569e3e2a9dace1f81b8514e10951252bd8ca780e1a7de6210fc4abe5", + "id": "fls_ka66jcu8gir7", + "link": "attributes.html#fls_ka66jcu8gir7", + "number": "13.2.11.2:10" + } + ], + "title": "Attribute panic_handler" + }, + { + "id": "fls_1l4mnlfk5rr2", + "informational": false, + "link": "attributes.html#attribute-windows-subsystem", + "number": "13.2.11.3", + "paragraphs": [ { - "checksum": "1b9a50adc4326cc8e3aa3e0191f596d700af6e1ee878fcd463eddc88796901c9", - "id": "fls_anwe21ypcjws", - "link": "inline-assembly.html#fls_anwe21ypcjws", - "number": "22.4.2:24" + "checksum": "7102958597ca2f19205e0e9f7c950d9b597adf5a8c1e6fe96e7661413a02f860", + "id": "fls_7mzjahvdzpy5", + "link": "attributes.html#fls_7mzjahvdzpy5", + "number": "13.2.11.3:1" }, { - "checksum": "553eef4baa71f1f46de56c071450919bf2eca3dd6cf7bd9f4fde72bb42083db8", - "id": "fls_43rchr5ffxsv", - "link": "inline-assembly.html#fls_43rchr5ffxsv", - "number": "22.4.2:25" + "checksum": "b8684eebecaefb67c2e17229476d5544ed6ff0e7e8b51d4b8cab06a169d1541c", + "id": "fls_t3c0t3lcnebk", + "link": "attributes.html#fls_t3c0t3lcnebk", + "number": "13.2.11.3:2" }, { - "checksum": "f79e3715f39c38e8e39df76757e19cba8ddf802007d1b76f19778e8c66455608", - "id": "fls_rscmbo3kbrsm", - "link": "inline-assembly.html#fls_rscmbo3kbrsm", - "number": "22.4.2:26" - }, + "checksum": "d52e19f42f5927bd34a651b16184a11e9fce5ae8de504bde89af1da5a51e0c59", + "id": "fls_go7pfkgpjk2t", + "link": "attributes.html#fls_go7pfkgpjk2t", + "number": "13.2.11.3:3" + } + ], + "title": "Attribute windows_subsystem" + }, + { + "id": "fls_riyi0gy48fxw", + "informational": false, + "link": "attributes.html#testing-attributes", + "number": "13.2.12", + "paragraphs": [], + "title": "Testing Attributes" + }, + { + "id": "fls_x849a4u7h82j", + "informational": false, + "link": "attributes.html#attribute-ignore", + "number": "13.2.12.1", + "paragraphs": [ { - "checksum": "24edea8823258cf1aec3574d325ce870b217dc84940d5cbaef8363f52c84ce84", - "id": "fls_hnol9houwn1f", - "link": "inline-assembly.html#fls_hnol9houwn1f", - "number": "22.4.2:27" + "checksum": "233bac50acbcf5ce70191e350e3e9d720fd83236b2a754a5846e557024e5fb50", + "id": "fls_qmdylxse9yhu", + "link": "attributes.html#fls_qmdylxse9yhu", + "number": "13.2.12.1:1" }, { - "checksum": "ef6d7d774ab1ca21f1aef251798dada30ebf5aa565f69e642e4ce2ef193ab470", - "id": "fls_es1lo6siw702", - "link": "inline-assembly.html#fls_es1lo6siw702", - "number": "22.4.2:28" + "checksum": "001e5f299105a44ca3acba687e810549c184a7062f15f48c4c2907feedcd00db", + "id": "fls_9m8e59fc1tyh", + "link": "attributes.html#fls_9m8e59fc1tyh", + "number": "13.2.12.1:2" + } + ], + "title": "Attribute ignore" + }, + { + "id": "fls_aes2d94g12b9", + "informational": false, + "link": "attributes.html#attribute-should-panic", + "number": "13.2.12.2", + "paragraphs": [ + { + "checksum": "bc29bb034a6a845f15d32f52bc63929f460f59dfafc941ba3d3ac7f591786021", + "id": "fls_w7dq8gnzel36", + "link": "attributes.html#fls_w7dq8gnzel36", + "number": "13.2.12.2:1" }, { - "checksum": "05610e4ed44e10cfa011be9ec110980564ae67ea3f1a345629bc1726e0fbd447", - "id": "fls_xlk7kd26j2rm", - "link": "inline-assembly.html#fls_xlk7kd26j2rm", - "number": "22.4.2:29" + "checksum": "0f5bcffb6ab2fd79ab1a1578fd84c818378597bd4bf7bc940dba19ff7cc9de02", + "id": "fls_bm5x846zfnb8", + "link": "attributes.html#fls_bm5x846zfnb8", + "number": "13.2.12.2:2" }, { - "checksum": "c2122b8e33a87c74ed1103c4a38fb30ea601a8445ed9d99df70a31536b77d278", - "id": "fls_x9kaplz9g1z9", - "link": "inline-assembly.html#fls_x9kaplz9g1z9", - "number": "22.4.2:30" + "checksum": "4b053fefee6cf33683101e489e70e2569e4deb8d7aa26b06a840e03085ddb586", + "id": "fls_bcoq5aus8nkr", + "link": "attributes.html#fls_bcoq5aus8nkr", + "number": "13.2.12.2:3" + } + ], + "title": "Attribute should_panic" + }, + { + "id": "fls_dv2j1fvvnk1t", + "informational": false, + "link": "attributes.html#attribute-test", + "number": "13.2.12.3", + "paragraphs": [ + { + "checksum": "41f8276015dc58f76e82db668219043600611837b52a93fd9dbb18436201041a", + "id": "fls_o2elhg5w1rj9", + "link": "attributes.html#fls_o2elhg5w1rj9", + "number": "13.2.12.3:1" }, { - "checksum": "3066b6559d893177f8936aea6fc3b373621f7ad26e0b80588ac312291a2c572a", - "id": "fls_928ermlgde11", - "link": "inline-assembly.html#fls_928ermlgde11", - "number": "22.4.2:31" + "checksum": "de64cdcc854596c8cf8a9e3bb1b6312bf4faac5928890297f69918e148a51d43", + "id": "fls_ert22u8rvkxt", + "link": "attributes.html#fls_ert22u8rvkxt", + "number": "13.2.12.3:2" }, { - "checksum": "db73f3b0c8c275ef4f5be8518eac860264aba6acc6f606edcf905e67a01f825a", - "id": "fls_49bkqmxwl0d2", - "link": "inline-assembly.html#fls_49bkqmxwl0d2", - "number": "22.4.2:32" + "checksum": "580975987fb8829aa62f5481e0c98f2f9a739c3d0a5cc0719d34be6815601aa3", + "id": "fls_c9ckjrq6emdj", + "link": "attributes.html#fls_c9ckjrq6emdj", + "number": "13.2.12.3:3" }, { - "checksum": "dea0ddd23f309d02462555b58271dfaff6e5958548830d4a0c6891eafc50b025", - "id": "fls_xlvkpe975b58", - "link": "inline-assembly.html#fls_xlvkpe975b58", - "number": "22.4.2:33" + "checksum": "e7ee08c2d3407ba055bfeec7a59f1c8c56257bf55ff14412206210e1dbbe08a2", + "id": "fls_4uykzqpq6svl", + "link": "attributes.html#fls_4uykzqpq6svl", + "number": "13.2.12.3:4" }, { - "checksum": "7c4f40ae43ddedb5dc71bae0dff059a430abcc5ef5dc36e38ce185ef2631ff54", - "id": "fls_k3sy1ph0kvy", - "link": "inline-assembly.html#fls_k3sy1ph0kvy", - "number": "22.4.2:34" + "checksum": "2c4a77cf5f2b9daf7f0535ffb85ee39298f774999cb40b3f78672fb5a2bd7a24", + "id": "fls_aqzd30s267pt", + "link": "attributes.html#fls_aqzd30s267pt", + "number": "13.2.12.3:5" }, { - "checksum": "d14404ffbac7fa6b204f853222509a2a09e59f7bbd912577a5a7ff4aeff0d298", - "id": "fls_ku6noqc0poxq", - "link": "inline-assembly.html#fls_ku6noqc0poxq", - "number": "22.4.2:35" + "checksum": "c19b0501aa117ddef6f66c5dc6c307c9c23cdfb62ecb953be67f2cf7448cda23", + "id": "fls_ze6cs75y9aft", + "link": "attributes.html#fls_ze6cs75y9aft", + "number": "13.2.12.3:6" }, { - "checksum": "251021164436aa4b642498f6a3d30aa2af316742c592de939c3153c932ff6d94", - "id": "fls_hny0patop479", - "link": "inline-assembly.html#fls_hny0patop479", - "number": "22.4.2:36" + "checksum": "6bfc6057de86432e2367e8c9682ff52750a502d7bd68f37c400345417f514d4b", + "id": "fls_pcs0prrh23y3", + "link": "attributes.html#fls_pcs0prrh23y3", + "number": "13.2.12.3:7" }, { - "checksum": "e493e8ca70b894b223615529525c358bb19c570024d3de0d8bd36a9ffa32c238", - "id": "fls_jm61m237cww", - "link": "inline-assembly.html#fls_jm61m237cww", - "number": "22.4.2:37" + "checksum": "c55f9c613d6d247902e066735179a60ae013f13645180b2f0558fee98a1bcf07", + "id": "fls_niky8lbkvej9", + "link": "attributes.html#fls_niky8lbkvej9", + "number": "13.2.12.3:8" }, { - "checksum": "0830b44abd4b4c395984463fae9b127ebdbe221c966d5585ce9c6279c5e1253a", - "id": "fls_wvje5eua16xm", - "link": "inline-assembly.html#fls_wvje5eua16xm", - "number": "22.4.2:38" - }, + "checksum": "2799842d7aed69f223fca391a95a67b96cb9d904f240678550cd095843a9163c", + "id": "fls_qfuntdm2g184", + "link": "attributes.html#fls_qfuntdm2g184", + "number": "13.2.12.3:9" + } + ], + "title": "Attribute test" + }, + { + "id": "fls_r3zwgf9sg1xp", + "informational": false, + "link": "attributes.html#type-attributes", + "number": "13.2.13", + "paragraphs": [], + "title": "Type Attributes" + }, + { + "id": "fls_9tmvuqrmk3ug", + "informational": false, + "link": "attributes.html#attribute-non-exhaustive", + "number": "13.2.13.1", + "paragraphs": [ { - "checksum": "43d9d2ab6988505415d82f7db889a536048c143771389684c62cc7e58d4650fd", - "id": "fls_frxn2f6v584d", - "link": "inline-assembly.html#fls_frxn2f6v584d", - "number": "22.4.2:39" + "checksum": "db123f9e8d90ea8a65b70081bc86bdbeb74728f7fbb3abf94a19106591eacfd5", + "id": "fls_szvrd79cgzsg", + "link": "attributes.html#fls_szvrd79cgzsg", + "number": "13.2.13.1:1" }, { - "checksum": "abea834cf3c23b93b75de1a6678a732d14decb29a408e444d3a290397ea29134", - "id": "fls_x6azw3td92b3", - "link": "inline-assembly.html#fls_x6azw3td92b3", - "number": "22.4.2:40" + "checksum": "d921bf1556555621ac4497f95036f2b36a900b3ca72209b3c8257b411f3434aa", + "id": "fls_1of56vl2ewq0", + "link": "attributes.html#fls_1of56vl2ewq0", + "number": "13.2.13.1:2" }, { - "checksum": "b5e53d8a4f2d5d92ff52782f5c95785d1d304ad5347ceb94c72d18c10a07aad1", - "id": "fls_twerrggztho5", - "link": "inline-assembly.html#fls_twerrggztho5", - "number": "22.4.2:41" + "checksum": "2b72505af01da754c8938fcc663e58c83b4bbcaabd1dc98ae2ab5e8da65fd8a2", + "id": "fls_hkyzdmmdyoin", + "link": "attributes.html#fls_hkyzdmmdyoin", + "number": "13.2.13.1:3" }, { - "checksum": "538abae4915a252a44ef56d9fd5d1e9b54e0a5256c3493cfd1ed292abb455257", - "id": "fls_s4cbxrc4ijyp", - "link": "inline-assembly.html#fls_s4cbxrc4ijyp", - "number": "22.4.2:42" + "checksum": "a408a04e473714cf160f9d5888fb173e91412fe640b793002c6cc41c7426ec78", + "id": "fls_7b0fvwrmz0mh", + "link": "attributes.html#fls_7b0fvwrmz0mh", + "number": "13.2.13.1:4" }, { - "checksum": "e12629bff77cc49dd0fd9697926e543b3db783726e728995ab6dcf3eb21b6637", - "id": "fls_2hdsgqko25l5", - "link": "inline-assembly.html#fls_2hdsgqko25l5", - "number": "22.4.2:43" + "checksum": "6cec52bab3d21f307cca338424ee3bb3802f2d59a4e3b224c7f00f72640ffd19", + "id": "fls_oqfrg9tqgaj8", + "link": "attributes.html#fls_oqfrg9tqgaj8", + "number": "13.2.13.1:5" }, { - "checksum": "3906f6daca8c9ecacb9ceb21d3511d2db168376916cb202a32f59cbac41bd889", - "id": "fls_5tzwwove8mgq", - "link": "inline-assembly.html#fls_5tzwwove8mgq", - "number": "22.4.2:44" + "checksum": "51678d511ec9b523bd052b51dce30e863238b3e09f7cf8640da14f382d4df5be", + "id": "fls_aql3c89840ix", + "link": "attributes.html#fls_aql3c89840ix", + "number": "13.2.13.1:6" }, { - "checksum": "0706a9b6e7d90d7b3533057e27539a37cf1b148a53e539ae61ad9dafdd8286f0", - "id": "fls_e6nvq9xygvh", - "link": "inline-assembly.html#fls_e6nvq9xygvh", - "number": "22.4.2:45" + "checksum": "91faee15159779bdfb246c3082526e3f01c0b8b71644bffad0bc701a7a23e5a0", + "id": "fls_cez7yxfc376c", + "link": "attributes.html#fls_cez7yxfc376c", + "number": "13.2.13.1:7" } ], - "title": "Directive Support" + "title": "Attribute non_exhaustive" + } + ], + "title": "Attributes" + }, + { + "informational": true, + "link": "dev-guide.html", + "sections": [], + "title": "Developer Guide" + }, + { + "informational": false, + "link": "lexical-elements.html", + "sections": [ + { + "id": "fls_411up5z0b6n6", + "informational": true, + "link": "lexical-elements.html", + "number": "2", + "paragraphs": [ + { + "checksum": "c63cd9576e1595dfb4d7d38e0c9732d20c4044d18947914c36952a45f6592248", + "id": "fls_pqwpf87b84tr", + "link": "lexical-elements.html#fls_pqwpf87b84tr", + "number": "2:1" + } + ], + "title": "Lexical Elements" }, { - "id": "fls_a3joqzqp1v9d", + "id": "fls_2i089jvv8j5g", "informational": false, - "link": "inline-assembly.html#abi-clobbers", - "number": "22.5", + "link": "lexical-elements.html#character-set", + "number": "2.1", "paragraphs": [ { - "checksum": "059896cc2c79a9ed0b43866fe3d923d7239e3a67a128c7e89e6869411edccc1c", - "id": "fls_xa11ggykg0sh", - "link": "inline-assembly.html#fls_xa11ggykg0sh", - "number": "22.5:1" + "checksum": "40ca6b69e8a1f39dacde761ccaecbaca619375767abb05e925bec6c504198244", + "id": "fls_itcth8292ud6", + "link": "lexical-elements.html#fls_itcth8292ud6", + "number": "2.1:1" }, { - "checksum": "25daebb16bac887b6a619f1c15055cf292b836403ab183382752976a02a4257c", - "id": "fls_e43sj9inlsym", - "link": "inline-assembly.html#fls_e43sj9inlsym", - "number": "22.5:2" + "checksum": "e82f4769c3930c55731cfdff64cba2bf3f3fe6f20f8bbb741ec95c4bce18f4d2", + "id": "fls_vfx8byq5zo8t", + "link": "lexical-elements.html#fls_vfx8byq5zo8t", + "number": "2.1:2" }, { - "checksum": "3151135e30ed0eaa0046c0a2c933b3df4524b6ebd38636be511d97edf1093142", - "id": "fls_gq2khxl1hixg", - "link": "inline-assembly.html#fls_gq2khxl1hixg", - "number": "22.5:3" + "checksum": "a2710a596348e56b2b389ff7a77765369a97fcd53c60fc5c39a42050c3aa2b74", + "id": "fls_pvslhm3chtlb", + "link": "lexical-elements.html#fls_pvslhm3chtlb", + "number": "2.1:3" }, { - "checksum": "9d980c1d9a3bd3147ff52fa56ba95e975ddb9eec707b8dcc601b03993a6bfbc5", - "id": "fls_o2qn842y0vvc", - "link": "inline-assembly.html#fls_o2qn842y0vvc", - "number": "22.5:4" + "checksum": "2a0b32b27fe371cd7e93f22f0a2244eba0ae620a5b844fe722d308d5cba30392", + "id": "fls_a5ec9cpn4sc8", + "link": "lexical-elements.html#fls_a5ec9cpn4sc8", + "number": "2.1:4" }, { - "checksum": "c9961878c937f4ee5de1788b5a9a58b77076276170d875a58a526fa90be99665", - "id": "fls_msysjt5m2941", - "link": "inline-assembly.html#fls_msysjt5m2941", - "number": "22.5:5" + "checksum": "66a1720f2e9d2efd1b01c5a8e3d89b8b70f8f707c0d5c446e6b1326f2e44e146", + "id": "fls_dgyrj49y3c7c", + "link": "lexical-elements.html#fls_dgyrj49y3c7c", + "number": "2.1:5" }, { - "checksum": "e99e85bd28b33b69146abf6665f55cd679b81c2d26e54ed077ca48c986466401", - "id": "fls_vyhl5po6pl4x", - "link": "inline-assembly.html#fls_vyhl5po6pl4x", - "number": "22.5:6" + "checksum": "2d61867ea0852e491d286632860fa0111941830a7af9e3240f26cbce126f5a67", + "id": "fls_5ocmngyur7by", + "link": "lexical-elements.html#fls_5ocmngyur7by", + "number": "2.1:6" }, { - "checksum": "4a320730472da6e94d6e7eb0e60110ce54e50eaf2bd689ad2d9e77d9174281bb", - "id": "fls_d1be48ik4a8", - "link": "inline-assembly.html#fls_d1be48ik4a8", - "number": "22.5:7" + "checksum": "7f6017bf13927f894ed1460d26d072453606445d1dbff591da54b2f1eb9fe834", + "id": "fls_1aj0rgi9kpib", + "link": "lexical-elements.html#fls_1aj0rgi9kpib", + "number": "2.1:7" }, { - "checksum": "c7a214a478ecba87863b838e3cc1dc1e0211c41d45b466650cf9e1ee4af781b1", - "id": "fls_49pus6qqmf72", - "link": "inline-assembly.html#fls_49pus6qqmf72", - "number": "22.5:8" + "checksum": "ffc345f23396d082ca468df1855e3f5aca7a1585cff5388705d9c1a3f70e74bd", + "id": "fls_bfzdxsbq2c2q", + "link": "lexical-elements.html#fls_bfzdxsbq2c2q", + "number": "2.1:8" }, { - "checksum": "90b9e3c561e86b008b46a75c32a8381e977ed63097178f6649d726503e1e5aa6", - "id": "fls_tc727ietnawz", - "link": "inline-assembly.html#fls_tc727ietnawz", - "number": "22.5:9" + "checksum": "02086e7f1249fcbd972c9e0518ee1939b64715df2d4770f72ce70c688b6338ad", + "id": "fls_vw0kq2y1o63m", + "link": "lexical-elements.html#fls_vw0kq2y1o63m", + "number": "2.1:9" }, { - "checksum": "02ff55c63d8be9b67db3379370d75d0fc64e42c2a63af002328807ef6040df7d", - "id": "fls_6jgsmfvww667", - "link": "inline-assembly.html#fls_6jgsmfvww667", - "number": "22.5:10" + "checksum": "82e2f578c850a061a6aaaad593ca77086467b3ecb1e21068c97a4c8d66c0aee0", + "id": "fls_ao296bmamwzh", + "link": "lexical-elements.html#fls_ao296bmamwzh", + "number": "2.1:10" }, { - "checksum": "544d1d040690c1ef7a2b666dc2a809d33f42b0b1114f00dc05d36f203757cfd5", - "id": "fls_gvzoq5mqwjx", - "link": "inline-assembly.html#fls_gvzoq5mqwjx", - "number": "22.5:11" + "checksum": "8815f83db374f9a454841fc1f6b6390c6ecd954fabc83881cea39dcc3e76bfc7", + "id": "fls_6kymhq7embdh", + "link": "lexical-elements.html#fls_6kymhq7embdh", + "number": "2.1:11" }, { - "checksum": "61e5e0531c0e617d3d424dfdfc8416bbf5aeb5313b8b85f8073e6fac4da59243", - "id": "fls_bnwzzpcmiero", - "link": "inline-assembly.html#fls_bnwzzpcmiero", - "number": "22.5:12" + "checksum": "e0d4111521e0ef7716e900e64befc27972b65b3002d9d3421d0df6e070601ecd", + "id": "fls_8mxmrxvhn3by", + "link": "lexical-elements.html#fls_8mxmrxvhn3by", + "number": "2.1:12" + }, + { + "checksum": "8d7234572699415553e79da17fd2524d1fcc2a7efe1e5033a320ab62a52024e6", + "id": "fls_bc6D1ATvmJJr", + "link": "lexical-elements.html#fls_bc6D1ATvmJJr", + "number": "2.1:13" + }, + { + "checksum": "e3b6a12dce515b8175a3e8b860650047b16ef9d3ca779fa9d7d654e44137ddc7", + "id": "fls_zfs15iel08y0", + "link": "lexical-elements.html#fls_zfs15iel08y0", + "number": "2.1:14" + }, + { + "checksum": "0019b4f55d1b71c339a645baf16461aa169dbed3b2d823b8af749a41fe5c41ac", + "id": "fls_7eifv4ksunu1", + "link": "lexical-elements.html#fls_7eifv4ksunu1", + "number": "2.1:15" + }, + { + "checksum": "8a24c7ea76335af8905267c0228e0a1fb01b2367fb5def7030a6d69e27d6a447", + "id": "fls_PIDKEm8GiLNL", + "link": "lexical-elements.html#fls_PIDKEm8GiLNL", + "number": "2.1:16" + }, + { + "checksum": "d5ae1cce239b620e197250f6a2726fc8f495aee8a7115926a963718c9033130f", + "id": "fls_2brw13n9ldgy", + "link": "lexical-elements.html#fls_2brw13n9ldgy", + "number": "2.1:17" } ], - "title": "ABI Clobbers" + "title": "Character Set" }, { - "id": "fls_ylli0ortyegk", + "id": "fls_fgnllgz5k3e6", "informational": false, - "link": "inline-assembly.html#assembly-options", - "number": "22.6", + "link": "lexical-elements.html#lexical-elements-separators-and-punctuation", + "number": "2.2", "paragraphs": [ { - "checksum": "8a3a017f2afb0587c55dfb386c7e5d180d3157b04f01968b1eb489ce80ca469f", - "id": "fls_i21l6t3vn95t", - "link": "inline-assembly.html#fls_i21l6t3vn95t", - "number": "22.6:1" + "checksum": "653136e8fac3c1c53040add411c925009e38f8501d503a48b3c73829f59e7bbc", + "id": "fls_d4nvxsvxj537", + "link": "lexical-elements.html#fls_d4nvxsvxj537", + "number": "2.2:1" }, { - "checksum": "bab672735ba4bd11338fe18ef154010c328d60c6904b856a7e71670295861eb9", - "id": "fls_g09kmp2a04g9", - "link": "inline-assembly.html#fls_g09kmp2a04g9", - "number": "22.6:2" + "checksum": "73d17b1543408a0260ebf6781483f768a98a08e65e7b3efd7a67056c0769a116", + "id": "fls_a1zylpqha73x", + "link": "lexical-elements.html#fls_a1zylpqha73x", + "number": "2.2:2" }, { - "checksum": "387233953cd06bc333811fa124272c964f24902926b1502b30fd6a1be40fc044", - "id": "fls_quer8ltdwnf2", - "link": "inline-assembly.html#fls_quer8ltdwnf2", - "number": "22.6:3" + "checksum": "11d71ea399510ce28be4e570347824ac5796e92cb7aeec1b5a5917ddac5f41dc", + "id": "fls_jy6wifn5r2bu", + "link": "lexical-elements.html#fls_jy6wifn5r2bu", + "number": "2.2:3" }, { - "checksum": "36d8b0665a541f55861ac8eec58788b1300271d197914bcef79a5366d764a54e", - "id": "fls_5wpgqpcm1v40", - "link": "inline-assembly.html#fls_5wpgqpcm1v40", - "number": "22.6:4" + "checksum": "f7f8a0f54827276c6d4560722f7e9788d530dd5d345822294140f92d6b9bca15", + "id": "fls_efdfq9nhpmp5", + "link": "lexical-elements.html#fls_efdfq9nhpmp5", + "number": "2.2:4" }, { - "checksum": "81f05b034ad37fdb0de0e9a2242a963b2cfd4886a5fa12d422e3944d67f850a4", - "id": "fls_ejuap3kkvs57", - "link": "inline-assembly.html#fls_ejuap3kkvs57", - "number": "22.6:5" + "checksum": "c9af1ffb3e58642391c41d16d4207e4f717098bcbe022bdf29bec6ce24663dc4", + "id": "fls_go25sisi5fdp", + "link": "lexical-elements.html#fls_go25sisi5fdp", + "number": "2.2:5" }, { - "checksum": "e2e32a90c459f6211a5915bd66ddf72c97e86792880aa7a5af68a1028b4f1c27", - "id": "fls_1nopbk5bkeqm", - "link": "inline-assembly.html#fls_1nopbk5bkeqm", - "number": "22.6:6" + "checksum": "761bcfdf8467cee695b29cd8d454ab4a558df08126df0edde891a74925371be6", + "id": "fls_a6t53o8h1vdk", + "link": "lexical-elements.html#fls_a6t53o8h1vdk", + "number": "2.2:6" }, { - "checksum": "7ef8c5e5436b9b3ccf1d5ddd8f024651de630ff3a775b15cfb2dbec38833c4dc", - "id": "fls_e5b1mp3byll2", - "link": "inline-assembly.html#fls_e5b1mp3byll2", - "number": "22.6:7" + "checksum": "eb78e3a8a8e977064b2d7149e4306c21f9fcdfb8f18ceeb51eb2b40b9ac4a9c7", + "id": "fls_8fv63w6f4udl", + "link": "lexical-elements.html#fls_8fv63w6f4udl", + "number": "2.2:7" }, { - "checksum": "6c6abff15d559e67cfbee302183c6b6cd9e2d0b27ef17f3fc6c2d2b1b8dc247d", - "id": "fls_2gf4wemrzaae", - "link": "inline-assembly.html#fls_2gf4wemrzaae", - "number": "22.6:8" + "checksum": "2db8aaaa096a0e58bd2ffc1db8997f6950ac40fd421ec7ba8b451a13f9a86de5", + "id": "fls_es0tz1q9cmoo", + "link": "lexical-elements.html#fls_es0tz1q9cmoo", + "number": "2.2:8" }, { - "checksum": "9a59466198ee2c628bf21cf3451d438f7b681c05bff38179d054b394ca87b055", - "id": "fls_5ebifab8dhy", - "link": "inline-assembly.html#fls_5ebifab8dhy", - "number": "22.6:9" + "checksum": "f795577e13f973533719549c1acfa613203c8ccb340008307f46401e67c453d7", + "id": "fls_vm86olkeecer", + "link": "lexical-elements.html#fls_vm86olkeecer", + "number": "2.2:9" }, { - "checksum": "3812da6e1f78fd4f04190a7f0b03c53c355e7fd9497bdbb2cc363720db6d7f05", - "id": "fls_ae2x4ho3i0zr", - "link": "inline-assembly.html#fls_ae2x4ho3i0zr", - "number": "22.6:10" + "checksum": "a00d2db187c054f568dbc3e78a1605de3149d3c6abc4a34aa93f2710ba6ca2fd", + "id": "fls_5zxdgxy8tjrq", + "link": "lexical-elements.html#fls_5zxdgxy8tjrq", + "number": "2.2:10" }, { - "checksum": "9227d85264bddc464ffe9527094d6daa871bb3d8c05d00cb821528e1c241cb04", - "id": "fls_188ib65a1z36", - "link": "inline-assembly.html#fls_188ib65a1z36", - "number": "22.6:11" + "checksum": "115718cd9f0c771924efeceb67f8b777b85cec578ef27ca2e037c38243f8f350", + "id": "fls_x89vkq9rwlyt", + "link": "lexical-elements.html#fls_x89vkq9rwlyt", + "number": "2.2:11" }, { - "checksum": "9947ef35f98d51e42642de4b89c7ff24c303708a6fc048d6baf420acd0c2e144", - "id": "fls_ia3cg424d601", - "link": "inline-assembly.html#fls_ia3cg424d601", - "number": "22.6:12" + "checksum": "5f4e89c3e94a0b323a33d11a320cc4dcc9f49ad2aadb50b2bafe1222bc8ddf57", + "id": "fls_bo3xh8r60ji1", + "link": "lexical-elements.html#fls_bo3xh8r60ji1", + "number": "2.2:12" }, { - "checksum": "1f08df8b8d9ead71e6d2a2b97ce398fa198247177be827becf8b36abac2eec70", - "id": "fls_j09bo53i5n69", - "link": "inline-assembly.html#fls_j09bo53i5n69", - "number": "22.6:13" + "checksum": "6f99b7bbcf1e55392ad60df6c0625665b9dc85560643ad80f415189d8c7ea74d", + "id": "fls_sslkjuxjnteu", + "link": "lexical-elements.html#fls_sslkjuxjnteu", + "number": "2.2:13" }, { - "checksum": "6156a4e0aabe75163b3c333a7cc831ca44acd71d75015372430ca8c0d1ca7495", - "id": "fls_eka6chp3hapa", - "link": "inline-assembly.html#fls_eka6chp3hapa", - "number": "22.6:14" + "checksum": "deb239c182541b9351e7079d839b668ddf59a12393d1595c39ea0a9e9ddb774e", + "id": "fls_9g1godm0jp0z", + "link": "lexical-elements.html#fls_9g1godm0jp0z", + "number": "2.2:14" }, { - "checksum": "6e26d5ced9abcc14efda425d56724492c38c2a89dfaaf7cc53fdcff38153f695", - "id": "fls_nszx1gllufi2", - "link": "inline-assembly.html#fls_nszx1gllufi2", - "number": "22.6:15" + "checksum": "0aa275a9c135534577ced486481a428aeb61c42968ca8a6ceafaada95c15ce69", + "id": "fls_6oith9q0soot", + "link": "lexical-elements.html#fls_6oith9q0soot", + "number": "2.2:15" }, { - "checksum": "2d7523471d7b595463ca7ca9c55972764ee91e9b48fd28620896baccbfe97aa7", - "id": "fls_d169ppna563c", - "link": "inline-assembly.html#fls_d169ppna563c", - "number": "22.6:16" + "checksum": "da7de79db9b949ccd8272fe75b48914ed735298d02e452128b6f46462c2d48a6", + "id": "fls_1dledwdc8fa6", + "link": "lexical-elements.html#fls_1dledwdc8fa6", + "number": "2.2:16" }, { - "checksum": "44e6f48245cc97766452666dcc62fa1d73508669f7a630f942edc2e6047d67ff", - "id": "fls_h8549stij7pj", - "link": "inline-assembly.html#fls_h8549stij7pj", - "number": "22.6:17" + "checksum": "47d2b64fbe4f3b6669f313e326a56bc80c9e29d2609798c8fbd66944b711bc45", + "id": "fls_lunw7ucj5ius", + "link": "lexical-elements.html#fls_lunw7ucj5ius", + "number": "2.2:17" }, { - "checksum": "b7cabc7b0d3991a23cd2b6064e873e856d12f722b48c78eb4f1e5c68cb9e9fb9", - "id": "fls_2drikpht6md9", - "link": "inline-assembly.html#fls_2drikpht6md9", - "number": "22.6:18" + "checksum": "c9313afb793a167c53fe2e63b2f4af9b33b23f6209fbf7e0b074de80d123e380", + "id": "fls_a4oiuhz95uiv", + "link": "lexical-elements.html#fls_a4oiuhz95uiv", + "number": "2.2:18" }, { - "checksum": "87fde9a65891debe011e4dabc743129a995adf7c4605b39913aa4478ab811aab", - "id": "fls_x66j1cn6zi6p", - "link": "inline-assembly.html#fls_x66j1cn6zi6p", - "number": "22.6:19" + "checksum": "6e723820ff0d3af2b33911208421ec93bfa3d9388535b353d60fe2a74ccc1c8e", + "id": "fls_137x9s6guj6h", + "link": "lexical-elements.html#fls_137x9s6guj6h", + "number": "2.2:19" }, { - "checksum": "e09645a3d913b01ba7b2da0538ca2a8cf2971062f274bbb7b07eb2fcbdf50eb6", - "id": "fls_ikwbu1ho33is", - "link": "inline-assembly.html#fls_ikwbu1ho33is", - "number": "22.6:20" + "checksum": "d871501237f68094e868228be1c9ed208944a082241b0cf7a5e34375ee6310ba", + "id": "fls_y0wdb09cpp1w", + "link": "lexical-elements.html#fls_y0wdb09cpp1w", + "number": "2.2:20" }, { - "checksum": "2e64f7a7609904f62a789b1086409483bd975bfc91a04b82c0b5263044395404", - "id": "fls_nf0h9crdzhfg", - "link": "inline-assembly.html#fls_nf0h9crdzhfg", - "number": "22.6:21" + "checksum": "27d189b098b53140fcdc85a3f039a286f0345dd4aa05f64de22d79e472edc815", + "id": "fls_48b7mepiuupz", + "link": "lexical-elements.html#fls_48b7mepiuupz", + "number": "2.2:21" }, { - "checksum": "ec4f1cc8bf22f706c2134897ccf8d68dff4c3a033f4ca1578e692769fe6fa36f", - "id": "fls_wh0wasawjj5s", - "link": "inline-assembly.html#fls_wh0wasawjj5s", - "number": "22.6:22" + "checksum": "cead86db2db6918d8b043df1bb6d9a515f70eedb704cb0533f19bf1a702786d5", + "id": "fls_g9h9bsvrsmk1", + "link": "lexical-elements.html#fls_g9h9bsvrsmk1", + "number": "2.2:22" }, { - "checksum": "3a414e98a1f7d173475399458067467182305c436b62978e990ae3071bb8dd27", - "id": "fls_s0ivlbjefh1u", - "link": "inline-assembly.html#fls_s0ivlbjefh1u", - "number": "22.6:23" - } - ], - "title": "Assembly Options" - }, - { - "id": "fls_qezwyridmjob", - "informational": false, - "link": "inline-assembly.html#macros-asm-global-asm-and-naked-asm", - "number": "22.7", - "paragraphs": [ - { - "checksum": "8a46f8db9ec5bee19e7fe7d419aade6653be1d595cc0b0e03910ea2632034b58", - "id": "fls_ecteot716j8j", - "link": "inline-assembly.html#fls_ecteot716j8j", - "number": "22.7:1" + "checksum": "9919d5618da261c20fa0dcbe41e0e33cdadc04c63ce6b754c24cc21f2cea2143", + "id": "fls_fxne2xd0zzzo", + "link": "lexical-elements.html#fls_fxne2xd0zzzo", + "number": "2.2:23" }, { - "checksum": "413125c0a5130dec6c099b6fbdc521c30f5edd1eed9330ba3453c48606ab9e60", - "id": "fls_tKj18krZ1pSt", - "link": "inline-assembly.html#fls_tKj18krZ1pSt", - "number": "22.7:2" + "checksum": "1db05fadca01dde48b4ba9f4c5941275523165e10756ef03f43cfa640cb2164e", + "id": "fls_il7zv5x3aw0q", + "link": "lexical-elements.html#fls_il7zv5x3aw0q", + "number": "2.2:24" }, { - "checksum": "5d11d62e1e8b0838d243aad0ca1b6d984c7ecdd5697fb0fda10f24de8da95537", - "id": "fls_nLBhw2w6uznH", - "link": "inline-assembly.html#fls_nLBhw2w6uznH", - "number": "22.7:3" + "checksum": "0f736355e9492cd634311cdd73a04550f157f18342e924ed85e3662ff008085f", + "id": "fls_ovcs1qm86ss9", + "link": "lexical-elements.html#fls_ovcs1qm86ss9", + "number": "2.2:25" }, { - "checksum": "29eb3c8d783230dd65c04e22cfcd393f3f1112155a70f3cb220df3d03a0ce6cf", - "id": "fls_cTEiqjf6haEg", - "link": "inline-assembly.html#fls_cTEiqjf6haEg", - "number": "22.7:4" + "checksum": "90ab0fbf40492678aadc1c1460d74be57bd1740b09977b1ac1304747e519aba3", + "id": "fls_wmhlvjm0b0j9", + "link": "lexical-elements.html#fls_wmhlvjm0b0j9", + "number": "2.2:26" }, { - "checksum": "95dbbdf815af777d799412933cfb1c6139d1a5a0618aca24c1ce9095e8a3bf75", - "id": "fls_1ikzov7cxic1", - "link": "inline-assembly.html#fls_1ikzov7cxic1", - "number": "22.7:5" + "checksum": "d68ed94fe4e38b43ce1f83e10bd9d63c0a70f2869947d5e7a3b05323020be8e0", + "id": "fls_gg42klb2gn9v", + "link": "lexical-elements.html#fls_gg42klb2gn9v", + "number": "2.2:27" }, { - "checksum": "8b4f18328e73d75d6d74d43fa45304ecad3929469d9cfbf55a766168dac286fe", - "id": "fls_4lb6yh12w1cv", - "link": "inline-assembly.html#fls_4lb6yh12w1cv", - "number": "22.7:6" + "checksum": "2565056b10c31e82a75554c2f93e8566040b57a534228e4eb172894ee351df1e", + "id": "fls_icahptg5enj4", + "link": "lexical-elements.html#fls_icahptg5enj4", + "number": "2.2:28" }, { - "checksum": "f1285396b9e871a470e6c99fcee2ba4fee44f7c1c1494d2d7504854d586374ff", - "id": "fls_tgzga1lanfuo", - "link": "inline-assembly.html#fls_tgzga1lanfuo", - "number": "22.7:7" + "checksum": "72b90114176314f65b298489501e1763317985e1f90ddc834c7ba00bceacc339", + "id": "fls_baawlxoi7yd4", + "link": "lexical-elements.html#fls_baawlxoi7yd4", + "number": "2.2:29" }, { - "checksum": "cef34131dd6ae60d425b87a2ac6d953390e2dec00e679df1e058873e45adaf2a", - "id": "fls_nfkbvs86d6kz", - "link": "inline-assembly.html#fls_nfkbvs86d6kz", - "number": "22.7:8" + "checksum": "7e8d7cb2d1db1915eefb51e0bb97b3578e2168dc029dbb4c6ce360f326d9f760", + "id": "fls_m7gt3wfbtm81", + "link": "lexical-elements.html#fls_m7gt3wfbtm81", + "number": "2.2:30" }, { - "checksum": "04fe361f7c1056d577d0cd246f23b6ed603ea7f7dc710b06089a1dc2c44649eb", - "id": "fls_PEoOGTBjuEQc", - "link": "inline-assembly.html#fls_PEoOGTBjuEQc", - "number": "22.7:9" + "checksum": "d1faa62ca541cacacd198a34628995933bad01292a21dd568ddcf474e4dbc4b2", + "id": "fls_6ewl7gn3sjm2", + "link": "lexical-elements.html#fls_6ewl7gn3sjm2", + "number": "2.2:31" }, { - "checksum": "9c4853b2d65ce161746b9b6388b8f200ee318204f2714bf9e78e14123dc45f62", - "id": "fls_vcB5xwgD6Ign", - "link": "inline-assembly.html#fls_vcB5xwgD6Ign", - "number": "22.7:10" + "checksum": "721175acceef5a74b065e1b446344787ec7a10cbffbef36ce67f086022ce23ba", + "id": "fls_nb8q6oq8txv3", + "link": "lexical-elements.html#fls_nb8q6oq8txv3", + "number": "2.2:32" }, { - "checksum": "76c9be8a2c13f41d46c1674de81af3faa54bf6a070033c81d62a9c8d437ac6ed", - "id": "fls_98vyqh9bzigx", - "link": "inline-assembly.html#fls_98vyqh9bzigx", - "number": "22.7:11" + "checksum": "d3f16e94c1af504357ac2df39037b3897e443574d68f41a97f3a3751fb96fd49", + "id": "fls_4nnky9ansr9j", + "link": "lexical-elements.html#fls_4nnky9ansr9j", + "number": "2.2:33" }, { - "checksum": "29bb7394c69509f75799537c989b895449d8782786410554e4b948bd0a38ada0", - "id": "fls_ppnj8bcncdp9", - "link": "inline-assembly.html#fls_ppnj8bcncdp9", - "number": "22.7:12" + "checksum": "f33b2927d4c93c042777b5efd14b10f479d4567213b07eeb39af2f226b208a84", + "id": "fls_h1gvudehmnn9", + "link": "lexical-elements.html#fls_h1gvudehmnn9", + "number": "2.2:34" }, { - "checksum": "fb17b760d5c76e48d93d08f1722c8da1fd927224135f46c206a4d3fe54d4a55d", - "id": "fls_wmay1vd8u0da", - "link": "inline-assembly.html#fls_wmay1vd8u0da", - "number": "22.7:13" + "checksum": "3b19d33ec536baf30efd81e160cddae06ecb9f87449e9a2f0eb4b7b41f45a9e9", + "id": "fls_6yj1c3lh691s", + "link": "lexical-elements.html#fls_6yj1c3lh691s", + "number": "2.2:35" }, { - "checksum": "f1479feedda5b57e1e5936f05df2da01ee75eb433febd48f24327a86fb66916e", - "id": "fls_e613hpr50t9", - "link": "inline-assembly.html#fls_e613hpr50t9", - "number": "22.7:14" + "checksum": "1849bba6049bdbc82b21b091dec81c60ec2b201d17fb46c10b00dd7008a75383", + "id": "fls_2d3oo9nou9vv", + "link": "lexical-elements.html#fls_2d3oo9nou9vv", + "number": "2.2:36" }, { - "checksum": "ce7ddc33151dcfba8a36c91bc23c8438759f7fce66fff26d097a1d2838d4028e", - "id": "fls_bic6iyd1nvfm", - "link": "inline-assembly.html#fls_bic6iyd1nvfm", - "number": "22.7:15" - } - ], - "title": "Macros: asm, global_asm, and naked_asm" - }, - { - "id": "fls_mw7mth5ooeq1", - "informational": false, - "link": "inline-assembly.html#label-block", - "number": "22.8", - "paragraphs": [ + "checksum": "1e646156faf2fee7e2b56b8ba713276080504a642c80857cae228114284669d0", + "id": "fls_st2vhcy14ud9", + "link": "lexical-elements.html#fls_st2vhcy14ud9", + "number": "2.2:37" + }, { - "checksum": "db39466ade2055d7d33326432bffe9094fa1a564ec801823984b2b3a1812277f", - "id": "fls_ZKMMqhBRe5lJ", - "link": "inline-assembly.html#fls_ZKMMqhBRe5lJ", - "number": "22.8:1" + "checksum": "296ef99a61a7ee9d96f8b232724e9426eaf744eb4fee6e5a8d5f382bab039071", + "id": "fls_9gdyw71dl25", + "link": "lexical-elements.html#fls_9gdyw71dl25", + "number": "2.2:38" }, { - "checksum": "4d06b8a41f8bf3e343ea5d02e9d220c061982d3bb9faefce760dcca123f89624", - "id": "fls_vJT3iAG1ZLh3", - "link": "inline-assembly.html#fls_vJT3iAG1ZLh3", - "number": "22.8:2" + "checksum": "a6eb8fc79796023bbf80ae6ebebbe9aa8ca02eaea9e91dd0101a92d6502c436a", + "id": "fls_sp8ufz28l9w3", + "link": "lexical-elements.html#fls_sp8ufz28l9w3", + "number": "2.2:39" }, { - "checksum": "b41b66d98778917ff4bf18fd633771bd2b6b3e3a71d1d2a3717983508fc3cbe7", - "id": "fls_ER0GVQE9N7oH", - "link": "inline-assembly.html#fls_ER0GVQE9N7oH", - "number": "22.8:3" - } - ], - "title": "Label block" - } - ], - "title": "Inline Assembly" - }, - { - "informational": true, - "link": "licenses.html", - "sections": [ - { - "id": "fls_kd7fcvfrwks0", - "informational": false, - "link": "licenses.html", - "number": "A", - "paragraphs": [], - "title": "Licenses" - }, - { - "id": "fls_mwoe9jy6l7er", - "informational": false, - "link": "licenses.html#ada-reference-manual-copyright-notice", - "number": "A.1", - "paragraphs": [ + "checksum": "1206752913d765884541bbdca0403f9d865f619f0a66850120cd7d31fdf9dba4", + "id": "fls_7kdr8biodxvz", + "link": "lexical-elements.html#fls_7kdr8biodxvz", + "number": "2.2:40" + }, { - "checksum": "6af57950721e83dae393e90ca8d980bdcca5c9b81a188fe360ec5adfda00b6d5", - "id": "fls_9rpvtm5tjrpp", - "link": "licenses.html#fls_9rpvtm5tjrpp", - "number": "A.1:1" + "checksum": "313032bcb9b35d0790c79d41a0ca4b969af9522ce30869193930e68bc013f5ab", + "id": "fls_pf92l9bkte2u", + "link": "lexical-elements.html#fls_pf92l9bkte2u", + "number": "2.2:41" }, { - "checksum": "ef760ed2bc838098d7e8efb78966c4cd75d281f71907582b05283c54343ba0ce", - "id": "fls_7uhb9t9x8r0c", - "link": "licenses.html#fls_7uhb9t9x8r0c", - "number": "A.1:2" + "checksum": "33ac534f7ecfa88d267e49b475398c2c99a9dc708d9947f300849180c325d542", + "id": "fls_ui40thspgyav", + "link": "lexical-elements.html#fls_ui40thspgyav", + "number": "2.2:42" }, { - "checksum": "a5f9956bcdfc5ce615410d2af601a90ce92f066360a5467a27d39718da318796", - "id": "fls_ej4g7lz5eqm8", - "link": "licenses.html#fls_ej4g7lz5eqm8", - "number": "A.1:3" + "checksum": "7d1d9f51a7a61689fd5c36e8bb1692e0155f452ed6ee445950644a177628e200", + "id": "fls_h33qzachmimc", + "link": "lexical-elements.html#fls_h33qzachmimc", + "number": "2.2:43" }, { - "checksum": "20b03a1ea2320f89250e61fc875c4f01597a27856b189891293511d1c3c55df5", - "id": "fls_l9uu4q48v1co", - "link": "licenses.html#fls_l9uu4q48v1co", - "number": "A.1:4" + "checksum": "2f5232d2dd945c47f4332b95ce35d9d402898e71f4f7c411c663e301e2429cfa", + "id": "fls_13ud1clgdnyv", + "link": "lexical-elements.html#fls_13ud1clgdnyv", + "number": "2.2:44" }, { - "checksum": "38194322b850acd0ebe5ec95d660959c07d88f87ef2fe6c7912a773201dc4836", - "id": "fls_3j7jx4qsqucs", - "link": "licenses.html#fls_3j7jx4qsqucs", - "number": "A.1:5" - } - ], - "title": "Ada Reference Manual Copyright Notice" - }, - { - "id": "fls_w6b35kn6la40", - "informational": false, - "link": "licenses.html#rust-reference-apache-license", - "number": "A.2", - "paragraphs": [ + "checksum": "70b0b1197e263b4916e16125090cd6e9a9899d0554b9d920f0a3ea7fa28419bb", + "id": "fls_7fosi8l2ktz2", + "link": "lexical-elements.html#fls_7fosi8l2ktz2", + "number": "2.2:45" + }, { - "checksum": "7659351efeafb9bcab1f7bded9384347863a49e48384d8b8f59f4aadc9c047d5", - "id": "fls_wn4kcs3skqra", - "link": "licenses.html#fls_wn4kcs3skqra", - "number": "A.2:1" + "checksum": "5683fc048d59af88dbe3eab83e4bd8d23b94856307f288a6f6377f4037f4595d", + "id": "fls_9qitp6r75ia6", + "link": "lexical-elements.html#fls_9qitp6r75ia6", + "number": "2.2:46" }, { - "checksum": "6b9064c87e131c2318a938f837e161eb0c65d3f5fb3d76f34c6a07ac89c08f73", - "id": "fls_mtcsb64efiw1", - "link": "licenses.html#fls_mtcsb64efiw1", - "number": "A.2:2" + "checksum": "910783ea8b3d0346e1c6f25a4103c369012b37ccd28169fb7513ec357673c4d2", + "id": "fls_g0umao9roi2l", + "link": "lexical-elements.html#fls_g0umao9roi2l", + "number": "2.2:47" }, { - "checksum": "511b49aedd83a0711011cf0a3d3ffbc8faa1c1443f13b71376c4ce8d0b1c6f6d", - "id": "fls_9tssu3wp22cz", - "link": "licenses.html#fls_9tssu3wp22cz", - "number": "A.2:3" + "checksum": "425e9c50e0a6e99ab3bd2a32d34417baaa88ef286249a7ea4c40aff88803a88d", + "id": "fls_lamrpdpko48", + "link": "lexical-elements.html#fls_lamrpdpko48", + "number": "2.2:48" }, { - "checksum": "a423d8767937df97db94bc21dea4f19e65f5d2a464521f2deca8482ccd4eb2e1", - "id": "fls_4pclfuqukw5o", - "link": "licenses.html#fls_4pclfuqukw5o", - "number": "A.2:4" + "checksum": "44095be1f456e5e12848444c3cccdaf9cb0595d285c3b9af792f68c3c6d12e7a", + "id": "fls_s4lte9onbmqb", + "link": "lexical-elements.html#fls_s4lte9onbmqb", + "number": "2.2:49" }, { - "checksum": "bcc06c2a274e4878336dd5b4c71758d3b854dcf325e9f4ca54c1c6501dccbcee", - "id": "fls_rgn86bsd9tl5", - "link": "licenses.html#fls_rgn86bsd9tl5", - "number": "A.2:5" + "checksum": "ef74969af8ed15f1495ae8e706dfd8210ff6adfe2b49651ba869290be7687de0", + "id": "fls_ywc297y8s0dt", + "link": "lexical-elements.html#fls_ywc297y8s0dt", + "number": "2.2:50" }, { - "checksum": "0c8baad676d0cb1c29fe117e5864ef351160f102c13d047d354e16c2145fc69a", - "id": "fls_n6ash4u0e838", - "link": "licenses.html#fls_n6ash4u0e838", - "number": "A.2:6" + "checksum": "5646edff72acfcef1812fe04617d99f20bd1792169e20de2737a09248d9dd74f", + "id": "fls_ijb0fws4gshu", + "link": "lexical-elements.html#fls_ijb0fws4gshu", + "number": "2.2:51" }, { - "checksum": "619babacc89fcc3a48bee87f41d8567998cf7926a806e7bcb31bef0bc1bf70fd", - "id": "fls_4z50m2plw8lg", - "link": "licenses.html#fls_4z50m2plw8lg", - "number": "A.2:7" + "checksum": "20e185246054cf07309c6a13c1abd811e9488b5a85b5b076da651edbcdb233bd", + "id": "fls_c25ur4xwbpk0", + "link": "lexical-elements.html#fls_c25ur4xwbpk0", + "number": "2.2:52" }, { - "checksum": "d902ee93c8c40420453bff915be7d882e3390fbfb7c3d38905a094dde4369625", - "id": "fls_bmx87il86j77", - "link": "licenses.html#fls_bmx87il86j77", - "number": "A.2:8" + "checksum": "c9171c2be2e92bbdf83f7a6944a2ba47aed3227600b99f5fb6a50b1ee7cfffdd", + "id": "fls_9dd9479zzq30", + "link": "lexical-elements.html#fls_9dd9479zzq30", + "number": "2.2:53" }, { - "checksum": "a5fd12a0acbcca3ac4cfca07cb3a66976c749dcabc7c73c69ed9abfad2067282", - "id": "fls_467b9ucekcv3", - "link": "licenses.html#fls_467b9ucekcv3", - "number": "A.2:9" + "checksum": "6392d2a1b3e699903a3891e189ec2627cd4cdadb40b1901227056869728ed558", + "id": "fls_kwsu9d3ppv3f", + "link": "lexical-elements.html#fls_kwsu9d3ppv3f", + "number": "2.2:54" }, { - "checksum": "aa73e595c7b2ef9149acd45a1b91fed2704f511303a5b2ae619eb01a1ffb9410", - "id": "fls_9ws2rqie234r", - "link": "licenses.html#fls_9ws2rqie234r", - "number": "A.2:10" + "checksum": "9cd08193b2cec929c6aa172a5dc0c456e7f5faf0dddc91f4a5010af10ff3cffb", + "id": "fls_oh62j9unw4mg", + "link": "lexical-elements.html#fls_oh62j9unw4mg", + "number": "2.2:55" }, { - "checksum": "c9b7d6603bdf686dc20a9604928e83ad51a6e432ce273673e0619de6904ad3a9", - "id": "fls_j075fx75s7wg", - "link": "licenses.html#fls_j075fx75s7wg", - "number": "A.2:11" + "checksum": "6031f088738902177f0a06367e797bbbed6f87b4be23c0317f10da0e7ca27224", + "id": "fls_g0tltt8qmbum", + "link": "lexical-elements.html#fls_g0tltt8qmbum", + "number": "2.2:56" }, { - "checksum": "67a79516239a86e9f3ac268c33cea7354013e2efe8dae4acf776dbee55bdf3cb", - "id": "fls_akjks1czkd7n", - "link": "licenses.html#fls_akjks1czkd7n", - "number": "A.2:12" + "checksum": "c66c5da22713843633a8a9e39b2b20bc790ff265b578a9814659071b19adb6d4", + "id": "fls_ounkw8b8tk4f", + "link": "lexical-elements.html#fls_ounkw8b8tk4f", + "number": "2.2:57" }, { - "checksum": "03e71fa54f500a9dba9a17354fc29921b6a6421fbdde23db702448cfe7f3d731", - "id": "fls_6n4t2e7lxq3", - "link": "licenses.html#fls_6n4t2e7lxq3", - "number": "A.2:13" + "checksum": "146ed363f52681258de59c4c591f3e964f91cf5b1f49ab1ba0410c4b99fb63a6", + "id": "fls_8ywv8gftsfr1", + "link": "lexical-elements.html#fls_8ywv8gftsfr1", + "number": "2.2:58" }, { - "checksum": "d890372e8c497682bf9b4b51d8b75a7211ca6e6b4ee30436e8c59dbbb0149bf3", - "id": "fls_g0gyduo0wc55", - "link": "licenses.html#fls_g0gyduo0wc55", - "number": "A.2:14" + "checksum": "db921aee3f65b1447572f308b1536051374edd08a46c64e1900f1f18f7d003e1", + "id": "fls_hsn6zc29ifyx", + "link": "lexical-elements.html#fls_hsn6zc29ifyx", + "number": "2.2:59" }, { - "checksum": "85d7c24f5cba0d54181181872cf27f0cad45c28f1d281733ef26114ef08e1bea", - "id": "fls_w8npkgeox7u5", - "link": "licenses.html#fls_w8npkgeox7u5", - "number": "A.2:15" + "checksum": "5b84c1b6b34b6a35f73d808399557de0dda5643c5dd061eab6fe2f70f841047a", + "id": "fls_o3amqe3ca82d", + "link": "lexical-elements.html#fls_o3amqe3ca82d", + "number": "2.2:60" }, { - "checksum": "f0a11688e77729f1b2fa1e4efbc104a39c8fd916d1f58f90eefccd38b209c0d6", - "id": "fls_iygftblnws72", - "link": "licenses.html#fls_iygftblnws72", - "number": "A.2:16" + "checksum": "503fcedbff8acca4704b08fc4a18dd34eacb130b874da3393499d1b4b2767f79", + "id": "fls_lkevfpj7sqd3", + "link": "lexical-elements.html#fls_lkevfpj7sqd3", + "number": "2.2:61" }, { - "checksum": "4a50d529d3ee650e897db5b0f8d99db215c718a75f11d527d5c560abc287de09", - "id": "fls_s32i8ovj5nqu", - "link": "licenses.html#fls_s32i8ovj5nqu", - "number": "A.2:17" + "checksum": "d11919954e7ca0bf885a5dcecbf1173c4e7d47a9a0c9680cdfe30689bdc09607", + "id": "fls_ff05ge2189z", + "link": "lexical-elements.html#fls_ff05ge2189z", + "number": "2.2:62" }, { - "checksum": "74231e6684fc04c3f1ed8c36da5619eb35f84411c9f9fbbc9354ff69182ec20e", - "id": "fls_c673i0mbecb9", - "link": "licenses.html#fls_c673i0mbecb9", - "number": "A.2:18" + "checksum": "f5fea3393e5672ccf2f182c92ada0ce8e51b908b4c56d6e5dff341b04aadbdda", + "id": "fls_nplkudde6oxf", + "link": "lexical-elements.html#fls_nplkudde6oxf", + "number": "2.2:63" }, { - "checksum": "b5a621567e61cacbad30b9320aa5fa59abe37e46a15e890c42ad445536c69bd0", - "id": "fls_pwpt8rg76oj3", - "link": "licenses.html#fls_pwpt8rg76oj3", - "number": "A.2:19" + "checksum": "0f4d81ea4a932a0b0c4ca39ea2ab7cf36fe9262611f07950869b94f715652cb3", + "id": "fls_qwnrklmbz0b", + "link": "lexical-elements.html#fls_qwnrklmbz0b", + "number": "2.2:64" + } + ], + "title": "Lexical Elements, Separators, and Punctuation" + }, + { + "id": "fls_21vnag69kbwe", + "informational": false, + "link": "lexical-elements.html#identifiers", + "number": "2.3", + "paragraphs": [ + { + "checksum": "3987fd5d747ef2d4785094f84e754d8cae140017fa5f9c28362feeef107d4dac", + "id": "fls_ls7ymvgd5kfa", + "link": "lexical-elements.html#fls_ls7ymvgd5kfa", + "number": "2.3:1" }, { - "checksum": "aa50ce2c3adbcfcae86514a554462ac599522a77d99072af00fd41aee7896afc", - "id": "fls_yu3hgzo57bvm", - "link": "licenses.html#fls_yu3hgzo57bvm", - "number": "A.2:20" + "checksum": "711b7898931cab56cb1058ef3932abe5b767cb86bbe9998c0d33d0dec68b7fc5", + "id": "fls_aqj9aguczgqs", + "link": "lexical-elements.html#fls_aqj9aguczgqs", + "number": "2.3:2" }, { - "checksum": "ee98c5a9da1ccf20da27dc04bdfd4c6b0ee7b4dd5180bb435515d300e6aa7c1f", - "id": "fls_37aibo9w67e8", - "link": "licenses.html#fls_37aibo9w67e8", - "number": "A.2:21" + "checksum": "17d71ec4c1973c054222c7033a9c64d1fd8dca1fdf82d323472445e8c7511c81", + "id": "fls_xsdmun5uqy4c", + "link": "lexical-elements.html#fls_xsdmun5uqy4c", + "number": "2.3:3" }, { - "checksum": "870cf6f77ad5d8ef0cfde85af78b7c2a507c6e6c8db5bbe62b60548c02229cfc", - "id": "fls_py9om9jvbulw", - "link": "licenses.html#fls_py9om9jvbulw", - "number": "A.2:22" + "checksum": "3a5077067bbd7d98abb0e8ef62f3c68e25a82638fe896907b96848701a6b9ed0", + "id": "fls_ktnf6zkrdy45", + "link": "lexical-elements.html#fls_ktnf6zkrdy45", + "number": "2.3:4" }, { - "checksum": "e4c5f14a1bc396e634f99d3e9c008a29af8f84b61dc6da057710d23ebb6a1855", - "id": "fls_wkf26wyy0ndr", - "link": "licenses.html#fls_wkf26wyy0ndr", - "number": "A.2:23" + "checksum": "7c2cc455bbdbc244dae982edf85b1ec3c9109a0bcba535a244af7823fff6829a", + "id": "fls_jpecw46eh061", + "link": "lexical-elements.html#fls_jpecw46eh061", + "number": "2.3:5" }, { - "checksum": "d3162aaf802254c584ffd111d733498e754b056c61bdb59144aed5024b8bc566", - "id": "fls_qsvzdiicam4f", - "link": "licenses.html#fls_qsvzdiicam4f", - "number": "A.2:24" + "checksum": "198814e4e6f61528781c58b11e16a0038ae24778bba6d20a1a9a8dd79185cb3e", + "id": "fls_lwcflgezgs5z", + "link": "lexical-elements.html#fls_lwcflgezgs5z", + "number": "2.3:6" }, { - "checksum": "c502f762cf1f733336f2b49fdabec625a400d1b9137ba436de9acfb180ea3681", - "id": "fls_n4zpph83hza1", - "link": "licenses.html#fls_n4zpph83hza1", - "number": "A.2:25" + "checksum": "a2af446e5664b56514cde36964346072a3c31267e8e81fd5417885aa9f091b32", + "id": "fls_uts0hywaw1rq", + "link": "lexical-elements.html#fls_uts0hywaw1rq", + "number": "2.3:7" }, { - "checksum": "c6ab38d458c9605260610ec31abaaf83a03d54cc4109c75ce57d5330b7e0ae43", - "id": "fls_6gvjqsyyk3sg", - "link": "licenses.html#fls_6gvjqsyyk3sg", - "number": "A.2:26" + "checksum": "3d09604f290b1e849a5430295209e2f6556f6c9f2024c7160c92b0ac7d91a602", + "id": "fls_lju1avcn0pfd", + "link": "lexical-elements.html#fls_lju1avcn0pfd", + "number": "2.3:8" }, { - "checksum": "dbb66a9641f131e6288803d7b31289cc46c0c00d4bf70ecaade3c44dfb5acfaa", - "id": "fls_evwltjyhvhz", - "link": "licenses.html#fls_evwltjyhvhz", - "number": "A.2:27" + "checksum": "4e8345c2b229363a30e4c701ca308a343dc98f3fc1025d6321b4a101aa16e444", + "id": "fls_cs6cbw625np1", + "link": "lexical-elements.html#fls_cs6cbw625np1", + "number": "2.3:9" }, { - "checksum": "f6437e117dc2304d057d9a5394bea1e927d97d91a0451366fb3bdbaa35556c6c", - "id": "fls_xj0b5mrjgdbt", - "link": "licenses.html#fls_xj0b5mrjgdbt", - "number": "A.2:28" + "checksum": "54da339a3e1b9269cf812fda1ad283d6cf0918c3a3807f8c61fab188ef3d2658", + "id": "fls_irwcldiotei2", + "link": "lexical-elements.html#fls_irwcldiotei2", + "number": "2.3:10" }, { - "checksum": "5cb92d044eeac194b5d3a2e2fed02e650bdca56fe9e6386c4729f39717879270", - "id": "fls_ecitsmzb8l28", - "link": "licenses.html#fls_ecitsmzb8l28", - "number": "A.2:29" + "checksum": "9ae1eb42d553b7e7443d02b5b2d1e3a6d78bb9bcb4eedc72060780aa692d65bf", + "id": "fls_g72rxs2z5960", + "link": "lexical-elements.html#fls_g72rxs2z5960", + "number": "2.3:11" }, { - "checksum": "a69bf85bf1838570613037b4423858a9a047187494aea6e78ae1c91c9d58f711", - "id": "fls_wx0hv6f68i4f", - "link": "licenses.html#fls_wx0hv6f68i4f", - "number": "A.2:30" + "checksum": "c7c123027fb95da5b47bbb45a0d262f1ccc8a67be78b8c5e6995d4eabf0dcb75", + "id": "fls_w473jevurlt1", + "link": "lexical-elements.html#fls_w473jevurlt1", + "number": "2.3:12" }, { - "checksum": "df088a3e94ccbd2e98926c4f5ca34fd02f3cd8dfc3924b7ba3749ef0eb08b68d", - "id": "fls_fgjjzdwbw1t8", - "link": "licenses.html#fls_fgjjzdwbw1t8", - "number": "A.2:31" + "checksum": "14cfd5ffca7331e76e77c2fc7141bce89335774b3850f3cc90a06066c4660451", + "id": "fls_mt1u4m3simhc", + "link": "lexical-elements.html#fls_mt1u4m3simhc", + "number": "2.3:13" }, { - "checksum": "1a6497e3eeb694644723d0a451104bff4dd133960c27e3d4e9b6c5282cdcdf2a", - "id": "fls_hnxekf2ahnif", - "link": "licenses.html#fls_hnxekf2ahnif", - "number": "A.2:32" + "checksum": "b8d023cf7da2ee836679c97c7dccb223c0cfe0d172d661327da271ea51059628", + "id": "fls_e2v58o233lvd", + "link": "lexical-elements.html#fls_e2v58o233lvd", + "number": "2.3:14" }, { - "checksum": "839ca0d83587df0f49d5e7af7a86cb4ed2526d3c7a4fb98ca1e7031d41e9c3bb", - "id": "fls_e0ytxo6b451", - "link": "licenses.html#fls_e0ytxo6b451", - "number": "A.2:33" + "checksum": "5d492f34dedfe4c347ae8fc5cc979751886b374d0f597aec7270a8850f233696", + "id": "fls_op0lp1i065di", + "link": "lexical-elements.html#fls_op0lp1i065di", + "number": "2.3:15" }, { - "checksum": "1c4d9cbfb808d36160fd3001c512e6887268a758682958c644bcbe07f04a32af", - "id": "fls_145e22m6bw47", - "link": "licenses.html#fls_145e22m6bw47", - "number": "A.2:34" + "checksum": "d8a9ddf03bd6e5fe69b23403d83fb785383eaa9e3e1b12e4d7a2d26f930c5843", + "id": "fls_vde7gev5rz4q", + "link": "lexical-elements.html#fls_vde7gev5rz4q", + "number": "2.3:16" }, { - "checksum": "9c3bd1dd9f6780741a46dfb5af593752082db35d8ce9d3eb8ae7d663e403c665", - "id": "fls_9o0wr812ggf2", - "link": "licenses.html#fls_9o0wr812ggf2", - "number": "A.2:35" + "checksum": "cb6600fc3669e86c2955c97b83b7f6396c4187ddd330aa46e989b9cd1c9f0ace", + "id": "fls_j9yh8j8jgdeu", + "link": "lexical-elements.html#fls_j9yh8j8jgdeu", + "number": "2.3:17" }, { - "checksum": "a3d8a030eaf80e2f2d7b0b8c819a8b4a03d2468cad162b5778d7535713e6eb54", - "id": "fls_cqaian5c7mwr", - "link": "licenses.html#fls_cqaian5c7mwr", - "number": "A.2:36" + "checksum": "8123eef62794e72515c380c13f09fe4a1a1b30ee0516beacac7e93a43c943a7b", + "id": "fls_jejt5z8m1yew", + "link": "lexical-elements.html#fls_jejt5z8m1yew", + "number": "2.3:18" + } + ], + "title": "Identifiers" + }, + { + "id": "fls_nrkd5wpi64oo", + "informational": false, + "link": "lexical-elements.html#literals", + "number": "2.4", + "paragraphs": [ + { + "checksum": "4303183568664aedd54d9840d1fb3ca733ea37f08af4e8a629102afce83dc9bc", + "id": "fls_s76un78zyd0j", + "link": "lexical-elements.html#fls_s76un78zyd0j", + "number": "2.4:1" + } + ], + "title": "Literals" + }, + { + "id": "fls_2ifjqwnw03ms", + "informational": false, + "link": "lexical-elements.html#byte-literals", + "number": "2.4.1", + "paragraphs": [ + { + "checksum": "c40401452c29d8db5b9b193597726e18789ccd7a194187365e54cf4fb4b57067", + "id": "fls_3hpzf12h60u4", + "link": "lexical-elements.html#fls_3hpzf12h60u4", + "number": "2.4.1:1" }, { - "checksum": "2caf6f88f365a1f0b8673022f566d0e34e5f487002cbb975f7a3810984aa2b33", - "id": "fls_eil4b7ffojqa", - "link": "licenses.html#fls_eil4b7ffojqa", - "number": "A.2:37" + "checksum": "59045ce2e31e65cd7b1195587644af1891984b02d9dc612c1f7a61850d140332", + "id": "fls_q0qwr83frszx", + "link": "lexical-elements.html#fls_q0qwr83frszx", + "number": "2.4.1:2" + }, + { + "checksum": "34306366aa23941d86cf2fc0d347e2b007703424de36480b5357c7c4583e3671", + "id": "fls_fggytrv5jvw0", + "link": "lexical-elements.html#fls_fggytrv5jvw0", + "number": "2.4.1:3" } ], - "title": "Rust Reference Apache License" + "title": "Byte Literals" }, { - "id": "fls_un9oqipiretc", + "id": "fls_fqaffyrjob7v", "informational": false, - "link": "licenses.html#rust-reference-mit-license", - "number": "A.3", + "link": "lexical-elements.html#byte-string-literals", + "number": "2.4.2", "paragraphs": [ { - "checksum": "43b7d2588a00010b6644920dff56e6f4652ec966cf564d52f3e5e87748e58bbf", - "id": "fls_bkxw1o1hanmk", - "link": "licenses.html#fls_bkxw1o1hanmk", - "number": "A.3:1" + "checksum": "28f4df70edac5734cc64ff6089f04d400be4ce9f2af1664070e1b0326cb24d26", + "id": "fls_t63zfv5JdUhj", + "link": "lexical-elements.html#fls_t63zfv5JdUhj", + "number": "2.4.2:1" }, { - "checksum": "efda707a511b8c7e80727098049e5458105f2960dc74c0d7faa11a43f7e7d997", - "id": "fls_yc3wyw3plm1s", - "link": "licenses.html#fls_yc3wyw3plm1s", - "number": "A.3:2" + "checksum": "b97d7cd6b67ccbb2930123cc93a7e4857e32dbbf7a22764e35120ca15f7dca95", + "id": "fls_Xd6LnfzMb7t7", + "link": "lexical-elements.html#fls_Xd6LnfzMb7t7", + "number": "2.4.2:2" + } + ], + "title": "Byte String Literals" + }, + { + "id": "fls_msbaxfc09vkk", + "informational": false, + "link": "lexical-elements.html#simple-byte-string-literals", + "number": "2.4.2.1", + "paragraphs": [ + { + "checksum": "62ac30b5cfa63f4632a34f11e4698ea67c7ae55a34b3d28b393ca9d5fa086c06", + "id": "fls_3dcqhuosqb84", + "link": "lexical-elements.html#fls_3dcqhuosqb84", + "number": "2.4.2.1:1" }, { - "checksum": "dc6e47d05ec79c0cbaa15dd61b6a60ab03fd6f9d7e2f51df6828fc53f4916265", - "id": "fls_ypomxdgiswq8", - "link": "licenses.html#fls_ypomxdgiswq8", - "number": "A.3:3" + "checksum": "5e13841395ee1fa0c45128b43036136b6cb4e4916df10822bac39fdbd3793ff4", + "id": "fls_moe3zfx39ox2", + "link": "lexical-elements.html#fls_moe3zfx39ox2", + "number": "2.4.2.1:2" }, { - "checksum": "f227391839011fc1299d7eef9855e682ceab676478b839480bb5cdea3359f70b", - "id": "fls_q6rhvbuhxa21", - "link": "licenses.html#fls_q6rhvbuhxa21", - "number": "A.3:4" + "checksum": "8f22427e1ba53391df74a01e8e153344365f804365f3a68124e8fb15dc383bb6", + "id": "fls_vffxb6arj9jf", + "link": "lexical-elements.html#fls_vffxb6arj9jf", + "number": "2.4.2.1:3" } ], - "title": "Rust Reference MIT License" - } - ], - "title": "Licenses" - }, - { - "informational": false, - "link": "items.html", - "sections": [ + "title": "Simple Byte String Literals" + }, { - "id": "fls_wb86edg02t6a", + "id": "fls_jps9102q0qfi", "informational": false, - "link": "items.html", - "number": "3", + "link": "lexical-elements.html#raw-byte-string-literals", + "number": "2.4.2.2", "paragraphs": [ { - "checksum": "7828a434b91eb775a25b624f9c2c6e10d5b02ed8e26529f7471e4a787e24e10f", - "id": "fls_s3b1cba9lfj5", - "link": "items.html#fls_s3b1cba9lfj5", - "number": "3:1" + "checksum": "990fc45d4abab0b67d0ef88df0be04d33d65c7522287bb90176a8a85a131faf8", + "id": "fls_yyw7nv651580", + "link": "lexical-elements.html#fls_yyw7nv651580", + "number": "2.4.2.2:1" }, { - "checksum": "c820b8889c9e57bcae1988c78c8217a0fcd495f92409b93715942f79e683ae67", - "id": "fls_hil5f7y4xdhe", - "link": "items.html#fls_hil5f7y4xdhe", - "number": "3:2" + "checksum": "f646d235451afad8b859ba4be5f8fb7a8bfd7a81daf561627ab2473cdba57128", + "id": "fls_5ybq0euwya42", + "link": "lexical-elements.html#fls_5ybq0euwya42", + "number": "2.4.2.2:2" } ], - "title": "Items" - } - ], - "title": "Items" - }, - { - "informational": false, - "link": "attributes.html", - "sections": [ + "title": "Raw Byte String Literals" + }, { - "id": "fls_gvwd0kf72jt", + "id": "fls_u1ghcy16emve", "informational": false, - "link": "attributes.html", - "number": "13", + "link": "lexical-elements.html#c-string-literals", + "number": "2.4.3", "paragraphs": [ { - "checksum": "eff2f3d8085ca2ad43e10aa74acd5fb4de1a71393ec1e3bb69b4da6eca0bac43", - "id": "fls_rnzxj1t0hehl", - "link": "attributes.html#fls_rnzxj1t0hehl", - "number": "13:1" - }, - { - "checksum": "dd933aff5339727903dca2ff3f8484b0c000fdda5ab4ef729319db3dd76c9f10", - "id": "fls_yd0ehw5csaur", - "link": "attributes.html#fls_yd0ehw5csaur", - "number": "13:2" - }, - { - "checksum": "08694050309ada68c0f9d36f4cf7bf427a740fb2dd1175bd0acf555bedcf4729", - "id": "fls_8o6vmzbw1b1j", - "link": "attributes.html#fls_8o6vmzbw1b1j", - "number": "13:3" + "checksum": "7e16d894447f8ef686c945d36f1faf6f80c8b136f4741bca6b0179cef2491e4f", + "id": "fls_VKCW830CzhhN", + "link": "lexical-elements.html#fls_VKCW830CzhhN", + "number": "2.4.3:1" }, { - "checksum": "80ab4794df7b97c498adf667d822f03e67802cfa46ac84a9c38967818009cfd1", - "id": "fls_9TMRVlQwAdTB", - "link": "attributes.html#fls_9TMRVlQwAdTB", - "number": "13:4" + "checksum": "6bb6d55a686c54d2952946d0c2402066f7f569ce6a973b70b764b9dd3b68ddc5", + "id": "fls_XJprzaEn82Xs", + "link": "lexical-elements.html#fls_XJprzaEn82Xs", + "number": "2.4.3:2" } ], - "title": "Attributes" + "title": "C String Literals" }, { - "id": "fls_i52cujixq9qs", + "id": "fls_p090c5otnelw", "informational": false, - "link": "attributes.html#attribute-properties", - "number": "13.1", + "link": "lexical-elements.html#simple-c-string-literals", + "number": "2.4.3.1", "paragraphs": [ { - "checksum": "37140f4cac4f331316da767c72dc2e92fb5dfe383af30c5eb742a269163ccd01", - "id": "fls_p4potvq7x532", - "link": "attributes.html#fls_p4potvq7x532", - "number": "13.1:1" - }, - { - "checksum": "e3cc14af5a12637d401f768630f9f0596ea7cd5c05458d5e49c6242fc7e178d8", - "id": "fls_xk7lb2g02sy7", - "link": "attributes.html#fls_xk7lb2g02sy7", - "number": "13.1:2" - }, - { - "checksum": "cebd4af12f069c0f4ce742eabdd258723662de0e0f1065a6bcfe8772e5e7194b", - "id": "fls_q8wl7pidx2za", - "link": "attributes.html#fls_q8wl7pidx2za", - "number": "13.1:3" + "checksum": "4da63974cf01cd18e7aa9a85771f1aa5c6cd0c1ac07ff0d7c0161a5acd50e54b", + "id": "fls_fnwQHo7twAom", + "link": "lexical-elements.html#fls_fnwQHo7twAom", + "number": "2.4.3.1:1" }, { - "checksum": "3c8df296f170a3b399899cfa57fc5da2cb24c804bc939039d3e9458c342b6334", - "id": "fls_jottio69o9e7", - "link": "attributes.html#fls_jottio69o9e7", - "number": "13.1:4" + "checksum": "250f1bc827e567b0bf3ebf794f811002cb851aad7c0cb2a5eebc0af9a3447f9f", + "id": "fls_nPI7j0siGP8G", + "link": "lexical-elements.html#fls_nPI7j0siGP8G", + "number": "2.4.3.1:2" }, { - "checksum": "9ea853c8e48bec469214a2d9b52929d6510733dbfd7da35ad72428a27b205528", - "id": "fls_gzyx9lfi5pvd", - "link": "attributes.html#fls_gzyx9lfi5pvd", - "number": "13.1:5" - }, + "checksum": "d44d85ac2c9004357bdef5501479b3401a2c2f41eef632c27dfe32aa01bc76ac", + "id": "fls_Ae7LM4Wg0NA7", + "link": "lexical-elements.html#fls_Ae7LM4Wg0NA7", + "number": "2.4.3.1:3" + } + ], + "title": "Simple C String Literals" + }, + { + "id": "fls_g4ldypf3rl6i", + "informational": false, + "link": "lexical-elements.html#raw-c-string-literals", + "number": "2.4.3.2", + "paragraphs": [ { - "checksum": "b4754f2c355f2072357c0a755bb404f6751667b43571d332db5c965114f6b906", - "id": "fls_elsfqsiqor1y", - "link": "attributes.html#fls_elsfqsiqor1y", - "number": "13.1:6" + "checksum": "97abe5a9182e0c5bba193506ce6ed8d0d709c421862ea27f46cbfd5dd8006652", + "id": "fls_gLrei65i8Uzq", + "link": "lexical-elements.html#fls_gLrei65i8Uzq", + "number": "2.4.3.2:1" }, { - "checksum": "ddc3357e11fde52528c561fe21918b1245bd5e8f4b51c556ad29d044506d4dd5", - "id": "fls_4xu1rwecd9au", - "link": "attributes.html#fls_4xu1rwecd9au", - "number": "13.1:7" - }, + "checksum": "9e1becddf98e3da940cd15c78147593aca7294145116b7a88ef4ee29b333791c", + "id": "fls_9nJHsg9dCi66", + "link": "lexical-elements.html#fls_9nJHsg9dCi66", + "number": "2.4.3.2:2" + } + ], + "title": "Raw C String Literals" + }, + { + "id": "fls_hv9jtycp0o1y", + "informational": false, + "link": "lexical-elements.html#numeric-literals", + "number": "2.4.4", + "paragraphs": [ { - "checksum": "f304103b55d7d87cca0e19cc4a96ed5406fa81e32277ba35a91b20c6ac85614f", - "id": "fls_n3737i320qum", - "link": "attributes.html#fls_n3737i320qum", - "number": "13.1:8" + "checksum": "d35bc8c4c792333754935689e43e0ed039b2a0b85a1730fd03f18091f05a6c19", + "id": "fls_fqpqnku27v99", + "link": "lexical-elements.html#fls_fqpqnku27v99", + "number": "2.4.4:1" } ], - "title": "Attribute Properties" + "title": "Numeric Literals" }, { - "id": "fls_ahmnqhm8anlb", + "id": "fls_2ed4axpsy9u0", "informational": false, - "link": "attributes.html#built-in-attributes", - "number": "13.2", + "link": "lexical-elements.html#integer-literals", + "number": "2.4.4.1", "paragraphs": [ { - "checksum": "8831d99bc357aecc925ce16cd58e47b873e3b06390d5fb7a155f553383be820f", - "id": "fls_92tqo8uas8kd", - "link": "attributes.html#fls_92tqo8uas8kd", - "number": "13.2:1" + "checksum": "838a11239236c63c53764779b279c93d7dd7d3de4c52d1940ac02c6568cca205", + "id": "fls_vkk2krfn93ry", + "link": "lexical-elements.html#fls_vkk2krfn93ry", + "number": "2.4.4.1:1" + }, + { + "checksum": "45573384a56c61616dc2f95c2ed80da8e157435fd2eaacf7dba7d34bd26d9f5c", + "id": "fls_nxqncu5yq4eu", + "link": "lexical-elements.html#fls_nxqncu5yq4eu", + "number": "2.4.4.1:2" }, { - "checksum": "819b429cae29c60933aa33ed73c325f87704b027ac0198d05fae9f4bf69e4f9e", - "id": "fls_bxucstrfcco8", - "link": "attributes.html#fls_bxucstrfcco8", - "number": "13.2:2" + "checksum": "b53030bb2f4fce14a508b6f1efada25625e0fd5ba0b3fecfd8c97e0cb04cda6a", + "id": "fls_rn8xfd66yvst", + "link": "lexical-elements.html#fls_rn8xfd66yvst", + "number": "2.4.4.1:3" }, { - "checksum": "44b470444d5c0ced9e021ec324c2d117754a70bf3f44d00e5303fef3bee9f401", - "id": "fls_wle815gb9ai2", - "link": "attributes.html#fls_wle815gb9ai2", - "number": "13.2:3" + "checksum": "fb04cc8a0da09c17517d97ccfff5433116894258fe657d52d61fa56ae4f58b22", + "id": "fls_2268lchxkzjp", + "link": "lexical-elements.html#fls_2268lchxkzjp", + "number": "2.4.4.1:4" }, { - "checksum": "2e27368f11256b44dd2ff0e2e21daa255d6bed7e9e9c923ff8043070e7560521", - "id": "fls_tvn08dtuilue", - "link": "attributes.html#fls_tvn08dtuilue", - "number": "13.2:4" + "checksum": "39e541881b32e1399af9d22c81f2c9acf7374597615a6e067f19a2a1483716bc", + "id": "fls_4v7awnutbpoe", + "link": "lexical-elements.html#fls_4v7awnutbpoe", + "number": "2.4.4.1:5" }, { - "checksum": "6269ed848875ae982e4a9072782f1cfa3780e55c8ea94a205bbb145084c27786", - "id": "fls_eOJS3mxa9xgu", - "link": "attributes.html#fls_eOJS3mxa9xgu", - "number": "13.2:5" + "checksum": "e930d37f51da1de444b9cdce508609ab005c7df01cf636cbae19629ed63527cd", + "id": "fls_f1e29aj0sqvl", + "link": "lexical-elements.html#fls_f1e29aj0sqvl", + "number": "2.4.4.1:6" }, { - "checksum": "2f27a78e70b3ce7bcc2fa8fbd9d7e48bcf5f81303e22b4473c9ad67be8183e5c", - "id": "fls_q4c023zdsfgn", - "link": "attributes.html#fls_q4c023zdsfgn", - "number": "13.2:6" + "checksum": "0c2fc993dbe174e0cd94651585b88f744f4e4a827c5145dd0137aa6eab3387f4", + "id": "fls_u83mffscqm6", + "link": "lexical-elements.html#fls_u83mffscqm6", + "number": "2.4.4.1:7" }, { - "checksum": "baed00b7e77547d373721bf0e05d646d88cb96ded2722b7505577f118cac0b81", - "id": "fls_xtu3p0kzwn7b", - "link": "attributes.html#fls_xtu3p0kzwn7b", - "number": "13.2:7" + "checksum": "56156e06e89f2c8d0e82416fb36dbe038b0aad5dd5d553108e3cd3e560351052", + "id": "fls_g10nuv14q4jn", + "link": "lexical-elements.html#fls_g10nuv14q4jn", + "number": "2.4.4.1:8" }, { - "checksum": "a1e7b39f6c99e32a187f228c19c2009de7fe6fdb93f751ce5336f2008300d9e9", - "id": "fls_gxxbf6eag3et", - "link": "attributes.html#fls_gxxbf6eag3et", - "number": "13.2:8" + "checksum": "f684fbad635257607f2b55c5da42894852a1ba30d630ac6d1e217b81f6bfdf8d", + "id": "fls_hpkkvuj1z1ez", + "link": "lexical-elements.html#fls_hpkkvuj1z1ez", + "number": "2.4.4.1:9" }, { - "checksum": "d49e8eff84e5ce88a93c8e6b4f4a4ba514b0028bb3d1b7d17254dc7f599ddb15", - "id": "fls_87o6n9et9jio", - "link": "attributes.html#fls_87o6n9et9jio", - "number": "13.2:9" + "checksum": "fcae3d231f80f39da6c1244a291d2760710458e799952561422e8c9280d35453", + "id": "fls_7yq2fep848ky", + "link": "lexical-elements.html#fls_7yq2fep848ky", + "number": "2.4.4.1:10" }, { - "checksum": "8bdce4eb7ab793087e487d41f14318daea55698317ec8c2b834f93716676d8ba", - "id": "fls_ui0i3rpt5v5u", - "link": "attributes.html#fls_ui0i3rpt5v5u", - "number": "13.2:10" + "checksum": "1f4a7189ec6869ed715b2809e1dfdf0d1da660f106391d0ae75419cb2a3bc674", + "id": "fls_bzm8lwq3qlat", + "link": "lexical-elements.html#fls_bzm8lwq3qlat", + "number": "2.4.4.1:11" }, { - "checksum": "02a0c085b317b40311d3351f46a1962249d001ea8178f149b1db9a76bfa770f6", - "id": "fls_6utorag4adlv", - "link": "attributes.html#fls_6utorag4adlv", - "number": "13.2:11" + "checksum": "da4e7c81f503ecc2ccc9f9996edd96f76f669a45826eeb0bbb333d2e4a34dc91", + "id": "fls_l4cx36brc1r5", + "link": "lexical-elements.html#fls_l4cx36brc1r5", + "number": "2.4.4.1:12" }, { - "checksum": "124eb05c66c007133b357ef970ea0ae8451920e4a7920d8b84d5ec680201b0ed", - "id": "fls_d8spdkjzp496", - "link": "attributes.html#fls_d8spdkjzp496", - "number": "13.2:12" + "checksum": "c1fe3c1f3f5a441d07e72c6702bc49ef8f801249e1a6b368c41d41cb087234e7", + "id": "fls_wthchinwx996", + "link": "lexical-elements.html#fls_wthchinwx996", + "number": "2.4.4.1:13" }, { - "checksum": "0b3817e206a78ddd0a8e8b4ed610b9f55b12348279d741c6b73e04f718ad7af6", - "id": "fls_vidbcv25dyud", - "link": "attributes.html#fls_vidbcv25dyud", - "number": "13.2:13" + "checksum": "9efb52c23e09f3c839307b622ea91a74fb827775d9312350956fc958a2a13a48", + "id": "fls_7uoaet2pm3am", + "link": "lexical-elements.html#fls_7uoaet2pm3am", + "number": "2.4.4.1:14" }, { - "checksum": "323fb400bdae437ffcf0829ed2261a1544d5ce01aec7bf434039ecdd05963f85", - "id": "fls_d0298bmlyuu4", - "link": "attributes.html#fls_d0298bmlyuu4", - "number": "13.2:14" + "checksum": "90e73387f32e74d98fccc01498d6b81106bfa39272684edd996c6bdc2ad8f099", + "id": "fls_p4rw583o2qbi", + "link": "lexical-elements.html#fls_p4rw583o2qbi", + "number": "2.4.4.1:15" }, { - "checksum": "b475a70894edc2d24474d317e039db5c901e1875b816cab114567e81bd8ab90b", - "id": "fls_dtb3t5ht5ngf", - "link": "attributes.html#fls_dtb3t5ht5ngf", - "number": "13.2:15" + "checksum": "a4c17c7e7b65883a7408b8cb26441cee4c5a98f040aec9f252015fc7fa72a28e", + "id": "fls_xrv4q56lmoo3", + "link": "lexical-elements.html#fls_xrv4q56lmoo3", + "number": "2.4.4.1:16" }, { - "checksum": "9b687112a84218dfa3ca1c0398bd960887a07dd5a6bc8638b5b8d2cb839101cc", - "id": "fls_c5n4gzgs79vv", - "link": "attributes.html#fls_c5n4gzgs79vv", - "number": "13.2:16" + "checksum": "5cd753841d4cf1b3ac2e299d851b9d7d782ffec5218f862a6145c1bb97fe48af", + "id": "fls_66e3q5um6cwc", + "link": "lexical-elements.html#fls_66e3q5um6cwc", + "number": "2.4.4.1:17" }, { - "checksum": "21c4c3f4bc27f88194a4d70c18fea7e0c0dd30914282d878277d915ff3696c35", - "id": "fls_xheohvupr8kb", - "link": "attributes.html#fls_xheohvupr8kb", - "number": "13.2:17" + "checksum": "b8c0a318a752dfac35fa253f5eaa5d86aa4d60c81746dbef4f79760bfbbb910e", + "id": "fls_5asyk66y7c9d", + "link": "lexical-elements.html#fls_5asyk66y7c9d", + "number": "2.4.4.1:18" }, { - "checksum": "d69474814037b5865fc97976164e1adab1e4e2d50965ee20568962b021550b4b", - "id": "fls_s5z2q5pl14p4", - "link": "attributes.html#fls_s5z2q5pl14p4", - "number": "13.2:18" + "checksum": "def3e28ea7543e8f1e3cd2976118a6057928353e8c252c4b3dbd25c489171d9e", + "id": "fls_76fifqjka0lx", + "link": "lexical-elements.html#fls_76fifqjka0lx", + "number": "2.4.4.1:19" }, { - "checksum": "a2d51f51657b328e4b3665e2f3a218dfe3cff69eb7b92a4c28eadc7343980475", - "id": "fls_NrTL2FruARAv", - "link": "attributes.html#fls_NrTL2FruARAv", - "number": "13.2:19" + "checksum": "7be03faca47fa6b3d15b6e1bc4e6cb7412ac921daeaa997fec6b05a74ebf315f", + "id": "fls_fsaimo419gf0", + "link": "lexical-elements.html#fls_fsaimo419gf0", + "number": "2.4.4.1:20" }, { - "checksum": "e79694d1fca990a102620dc1063eff04ea1dd5c1dfbe2141a22745bbe30aa92d", - "id": "fls_5ko0q9jnxv5a", - "link": "attributes.html#fls_5ko0q9jnxv5a", - "number": "13.2:20" + "checksum": "a0e4b322fa7da7cd532ed7b176659ab4f67e9391a98e3f8ff3a6aee868b8cc88", + "id": "fls_hvzacbu7yiwc", + "link": "lexical-elements.html#fls_hvzacbu7yiwc", + "number": "2.4.4.1:21" }, { - "checksum": "e7a9d3069445027dd0df2d0ca2be3467b745bfedc8fda368fdea9ae8714a07b1", - "id": "fls_rgjf5ibhurda", - "link": "attributes.html#fls_rgjf5ibhurda", - "number": "13.2:21" + "checksum": "7addf8fc9c1954d1f299f518261046655b25344da4e2566f4a6d2ad56af8f9a2", + "id": "fls_50qipwqi3arw", + "link": "lexical-elements.html#fls_50qipwqi3arw", + "number": "2.4.4.1:22" }, { - "checksum": "876d87894c56c458cc8ab3b579b184d2e05b39c1d9795d16d057ce55b6efec98", - "id": "fls_4d2ArC50kNWL", - "link": "attributes.html#fls_4d2ArC50kNWL", - "number": "13.2:22" + "checksum": "f00c3c8190df9c8356e8d00d40dc820ee8c2066ee14bc6fa8b113f1c2338df01", + "id": "fls_idzhusp2l908", + "link": "lexical-elements.html#fls_idzhusp2l908", + "number": "2.4.4.1:23" }, { - "checksum": "bdd154ea0f22fca637f51b6c83cb046352335e01a835e7578c6131621efacee3", - "id": "fls_29y8icoou1gx", - "link": "attributes.html#fls_29y8icoou1gx", - "number": "13.2:23" + "checksum": "a94d2d7881175e1f816e2299655e250ae163b1ded65deba1d719d1480c2c8213", + "id": "fls_qqrqyc6uhol", + "link": "lexical-elements.html#fls_qqrqyc6uhol", + "number": "2.4.4.1:24" }, { - "checksum": "994a15d14865c9ac299a469fa02491a079be98f08e532c1842561696a2b061a9", - "id": "fls_3fxhz0olhbcy", - "link": "attributes.html#fls_3fxhz0olhbcy", - "number": "13.2:24" + "checksum": "15953208cdd66d1e39876632dc159e551d824c61166d95fef941338342da8c30", + "id": "fls_pexi5jazthq6", + "link": "lexical-elements.html#fls_pexi5jazthq6", + "number": "2.4.4.1:25" + } + ], + "title": "Integer Literals" + }, + { + "id": "fls_29tlg1vyqay2", + "informational": false, + "link": "lexical-elements.html#float-literals", + "number": "2.4.4.2", + "paragraphs": [ + { + "checksum": "5fbdbcd887fb491134091fc535b453fbfc99b8b158a38e18667520fde61df8fd", + "id": "fls_rzi7oeqokd6e", + "link": "lexical-elements.html#fls_rzi7oeqokd6e", + "number": "2.4.4.2:1" }, { - "checksum": "3ee32e5e73d9f2f1211fb65ca1c0f0d9d62156fe5ed050f460f1912ce3b036e3", - "id": "fls_oexj0952o05u", - "link": "attributes.html#fls_oexj0952o05u", - "number": "13.2:25" + "checksum": "49113c092a3bbaaee5ecc5e237faa3a1d6b023cb7963903b2987786386b06cbc", + "id": "fls_2ru1zyrykd37", + "link": "lexical-elements.html#fls_2ru1zyrykd37", + "number": "2.4.4.2:2" }, { - "checksum": "cc38d8d58ef7f13fe4397cce653d8d17edbbf4e2f403bfbe1a8b43ec4ec6bc5b", - "id": "fls_q579e97n1m8j", - "link": "attributes.html#fls_q579e97n1m8j", - "number": "13.2:26" + "checksum": "de707bc1cc7a5d7d39af4acc4398ef7e645ac259968bb70066966823e3320d0b", + "id": "fls_21mhnhplzam7", + "link": "lexical-elements.html#fls_21mhnhplzam7", + "number": "2.4.4.2:3" }, { - "checksum": "c162024d8e33899473123cc87be077f31a4f25434418bb05acf6f8b0020d6d36", - "id": "fls_sn43rofpq6ld", - "link": "attributes.html#fls_sn43rofpq6ld", - "number": "13.2:27" + "checksum": "8851975681aef35016e51dcd1065ff3a7c4dcc59185943068c52b5f39a45220c", + "id": "fls_drqh80k0sfkb", + "link": "lexical-elements.html#fls_drqh80k0sfkb", + "number": "2.4.4.2:4" }, { - "checksum": "ec2aaeb5be71ab80801008bbbe736f283e0d5966c61fb962f3e0eeca5c099c6c", - "id": "fls_56d70gkmin4p", - "link": "attributes.html#fls_56d70gkmin4p", - "number": "13.2:28" + "checksum": "4f2baa57a7d41e696d29d2b12dd05976c69c2dca1d26d41b65c2ae18930aa583", + "id": "fls_cbs7j9pjpusw", + "link": "lexical-elements.html#fls_cbs7j9pjpusw", + "number": "2.4.4.2:5" }, { - "checksum": "7272a1c6f97c65a1a242fddb8442c6a6775d841ecf8e6dca74890ea2b21b90ad", - "id": "fls_mgb1xipm0qwo", - "link": "attributes.html#fls_mgb1xipm0qwo", - "number": "13.2:29" + "checksum": "1c3e432fbd868c7c0e6f540bab2df3a5a05a874a2cf90c0c008273c8750df94a", + "id": "fls_b9w7teaw1f8f", + "link": "lexical-elements.html#fls_b9w7teaw1f8f", + "number": "2.4.4.2:6" }, { - "checksum": "7488d316ab0bbccd6abaa4b7e9fac3051db8b6b7e7abde9eea639f605061ae24", - "id": "fls_rmhlssasdtkj", - "link": "attributes.html#fls_rmhlssasdtkj", - "number": "13.2:30" + "checksum": "0ed79abbdbc256059087c1f145e130bf6ae83c5d9ee2cf63b10c872203678b32", + "id": "fls_eawxng4ndhv0", + "link": "lexical-elements.html#fls_eawxng4ndhv0", + "number": "2.4.4.2:7" }, { - "checksum": "e318a8fb125755feb4d3ff217ad94ecfbed347ec2d3b5fc6b915785092ad356f", - "id": "fls_josaywt6g3rq", - "link": "attributes.html#fls_josaywt6g3rq", - "number": "13.2:31" + "checksum": "5af8baeae865b0b892668889094599d1003adefb8ca8775954c2bbb981df6244", + "id": "fls_yuhza1muo7o", + "link": "lexical-elements.html#fls_yuhza1muo7o", + "number": "2.4.4.2:8" }, { - "checksum": "45520f65cdd77c53947a088461ef31fee2930bb2ed25f58133a20ba182466c85", - "id": "fls_qk4vkn42c2jh", - "link": "attributes.html#fls_qk4vkn42c2jh", - "number": "13.2:32" + "checksum": "3ab353f0319f1a988557a20ca5d4cbbe6564a0901c1e800e65fdb0f7d63244c4", + "id": "fls_4sxt1ct7fyen", + "link": "lexical-elements.html#fls_4sxt1ct7fyen", + "number": "2.4.4.2:9" }, { - "checksum": "8c88e030229dcaf650154b169b91390821d8dfd437a0dbe05acfc8e30cba2e0d", - "id": "fls_f21azsygoovw", - "link": "attributes.html#fls_f21azsygoovw", - "number": "13.2:33" + "checksum": "da8b316abb73b8a595ca4e315dca897882f91c0dce9825eb81538e688460e26b", + "id": "fls_wa72rssp0jnt", + "link": "lexical-elements.html#fls_wa72rssp0jnt", + "number": "2.4.4.2:10" }, { - "checksum": "f89ffdf2e417fe247529cb8ada441fde754938c091666370fb4a4907947ff762", - "id": "fls_4d31lwzblg91", - "link": "attributes.html#fls_4d31lwzblg91", - "number": "13.2:34" - }, + "checksum": "8cf076d0fe9b1af3dae27583a7489cd5da291f7a3ad82ee704b53d75a6b88bd3", + "id": "fls_x2cw7g8g56f8", + "link": "lexical-elements.html#fls_x2cw7g8g56f8", + "number": "2.4.4.2:11" + } + ], + "title": "Float Literals" + }, + { + "id": "fls_ypa86oqxhn9u", + "informational": false, + "link": "lexical-elements.html#character-literals", + "number": "2.4.5", + "paragraphs": [ { - "checksum": "f975580a1b931d94c926c545e6f59accc6089800804e55bf0d717b7fc1f63015", - "id": "fls_muucfla1s8yn", - "link": "attributes.html#fls_muucfla1s8yn", - "number": "13.2:35" + "checksum": "74e9ccd35447ec63b8c0425e182a07fca94414ed3f7ddea9fa310ad919d551f9", + "id": "fls_j9q9ton57rvl", + "link": "lexical-elements.html#fls_j9q9ton57rvl", + "number": "2.4.5:1" }, { - "checksum": "561ac627fa6ab3ebf3be1dfa0723e9ea019b53340fbf84caaf46c3b23415887a", - "id": "fls_wbdtpntjr95w", - "link": "attributes.html#fls_wbdtpntjr95w", - "number": "13.2:36" + "checksum": "eaa3a29da7d1231106d2806b74071e86016eeeac6bda726a1b67b7946fc688fe", + "id": "fls_5v9gx22g5wPm", + "link": "lexical-elements.html#fls_5v9gx22g5wPm", + "number": "2.4.5:2" }, { - "checksum": "f1727bb25344abffe152fe96b9ae047c50c7467805d094633f7c5ba7a263e76a", - "id": "fls_lglwcbsvi9yj", - "link": "attributes.html#fls_lglwcbsvi9yj", - "number": "13.2:37" + "checksum": "22ca23a599d712303316952fe635355f16bf2f9ff55658bf7a1ecc55ecca4ce1", + "id": "fls_vag2oy4q7d4n", + "link": "lexical-elements.html#fls_vag2oy4q7d4n", + "number": "2.4.5:3" }, { - "checksum": "f207e9ce744c5b946fc10ed77f0e4d48c9805dbebca1dbdb5557c6a076c1e78f", - "id": "fls_1gyg8hfb13n7", - "link": "attributes.html#fls_1gyg8hfb13n7", - "number": "13.2:38" + "checksum": "e33f767e24f5ea1c1341bf515463aec11114c952dba17c8c17dfa64f277d2291", + "id": "fls_n8z6p6g564r2", + "link": "lexical-elements.html#fls_n8z6p6g564r2", + "number": "2.4.5:4" + } + ], + "title": "Character Literals" + }, + { + "id": "fls_boyhlu5srp6u", + "informational": false, + "link": "lexical-elements.html#string-literals", + "number": "2.4.6", + "paragraphs": [ + { + "checksum": "c886554b68518a6cb11a6671911a00850d338a207bf5813cbf9a2dfd6f6ee63a", + "id": "fls_7fuctvtvdi7x", + "link": "lexical-elements.html#fls_7fuctvtvdi7x", + "number": "2.4.6:1" }, { - "checksum": "dfd603b48e4714c2db10e165507f36aabf589e751ffecb158b59124480fe5b80", - "id": "fls_6005g57evfbp", - "link": "attributes.html#fls_6005g57evfbp", - "number": "13.2:39" + "checksum": "c2898629e305307a7f0f10353abc8b6b09eed1a9ec43b094adae9757f6831fe5", + "id": "fls_NyiCpU2tzJlQ", + "link": "lexical-elements.html#fls_NyiCpU2tzJlQ", + "number": "2.4.6:2" + } + ], + "title": "String Literals" + }, + { + "id": "fls_hucd52suu6it", + "informational": false, + "link": "lexical-elements.html#simple-string-literals", + "number": "2.4.6.1", + "paragraphs": [ + { + "checksum": "ac492dd896d3af7d678cdc44d9708f856cbc67ce41ee48385f730c82f0fb65eb", + "id": "fls_1pdzwkt5txfj", + "link": "lexical-elements.html#fls_1pdzwkt5txfj", + "number": "2.4.6.1:1" }, { - "checksum": "9d7b1a8681c416a8fb1ffb7f988ebc46ebe4d70caee8109ff9f24e727f1f94fa", - "id": "fls_3y4o8kq58dt8", - "link": "attributes.html#fls_3y4o8kq58dt8", - "number": "13.2:40" + "checksum": "8eeeebd628d07b2ea71c78a21c61dd978f7d9af4bd0b88a95768966881dd52b4", + "id": "fls_wawtu6j3fiqn", + "link": "lexical-elements.html#fls_wawtu6j3fiqn", + "number": "2.4.6.1:2" }, { - "checksum": "469d4af2bf4bb8bf7d70abbc6b17c1c1bb6fad4e1fefe9cabd15d3451fcdc72e", - "id": "fls_vsix3pqf519x", - "link": "attributes.html#fls_vsix3pqf519x", - "number": "13.2:41" + "checksum": "4a6e317133ecb5ae53aefdc730f509e09e0c3713395895908d88d18e106527b7", + "id": "fls_ycy5ee6orjx", + "link": "lexical-elements.html#fls_ycy5ee6orjx", + "number": "2.4.6.1:3" }, { - "checksum": "83ee9ec2496eca5f9f1bf7225a184b339394c8fcd2aa146177bb9bffc0eac79a", - "id": "fls_c8uqw8p0qrh5", - "link": "attributes.html#fls_c8uqw8p0qrh5", - "number": "13.2:42" + "checksum": "cb50f037e2188c9a58f86b87d2c9d1f90186b0dcbe6117c343aa5fba6b2d7daa", + "id": "fls_6nt5kls21xes", + "link": "lexical-elements.html#fls_6nt5kls21xes", + "number": "2.4.6.1:4" + } + ], + "title": "Simple String Literals" + }, + { + "id": "fls_usr6iuwpwqqh", + "informational": false, + "link": "lexical-elements.html#raw-string-literals", + "number": "2.4.6.2", + "paragraphs": [ + { + "checksum": "cf9f7d7333258a4d9a235a6a611a80fd87d664cafd5732da2eb6c1155dfad367", + "id": "fls_36suwhbwmq1t", + "link": "lexical-elements.html#fls_36suwhbwmq1t", + "number": "2.4.6.2:1" }, { - "checksum": "953d885e7fb1e0ea03c0ade5eaa5934bf60dd032fd3261d585adf05d7da0b2e8", - "id": "fls_b3jobjxmqppy", - "link": "attributes.html#fls_b3jobjxmqppy", - "number": "13.2:43" + "checksum": "d3b402e15a8bff639df20fcf15d4306d568a4d8b0e65132224975ba1cd7ffd25", + "id": "fls_ms43w1towz40", + "link": "lexical-elements.html#fls_ms43w1towz40", + "number": "2.4.6.2:2" + } + ], + "title": "Raw String Literals" + }, + { + "id": "fls_jkab8eevzbte", + "informational": false, + "link": "lexical-elements.html#boolean-literals", + "number": "2.4.7", + "paragraphs": [ + { + "checksum": "9222cb981e760f03c8b46b8708ecf54b556f6fb343a0017cb16fba2bcb98c10f", + "id": "fls_1lll64ftupjd", + "link": "lexical-elements.html#fls_1lll64ftupjd", + "number": "2.4.7:1" }, { - "checksum": "276f7f393035ddb571a6f1e00ad388e9854d9e0dcd79acd44e10b7e2f2939fcc", - "id": "fls_xyhoxm30i7wn", - "link": "attributes.html#fls_xyhoxm30i7wn", - "number": "13.2:44" + "checksum": "68a6151c72c232478bda2c413c1552acfd1e8304956b5733ef8a13c86768b079", + "id": "fls_pgngble3ilyx", + "link": "lexical-elements.html#fls_pgngble3ilyx", + "number": "2.4.7:2" + } + ], + "title": "Boolean Literals" + }, + { + "id": "fls_q8l2jza7d9xa", + "informational": false, + "link": "lexical-elements.html#comments", + "number": "2.5", + "paragraphs": [ + { + "checksum": "80a97a25c037e20dbb697978e3c79c22243ea2bb094a1a60f10b8cc358d57568", + "id": "fls_8obn3dtzpe5f", + "link": "lexical-elements.html#fls_8obn3dtzpe5f", + "number": "2.5:1" }, { - "checksum": "b5faaae83480acf33dea941d05390fe8ad7ccec79bfab2b30e87b0b938047593", - "id": "fls_nowfw1ffhupd", - "link": "attributes.html#fls_nowfw1ffhupd", - "number": "13.2:45" + "checksum": "2dea71c3ed9833c779a0c63b8eae45c64771074fdbc263b67cd2115d7e9cf4ab", + "id": "fls_qsbnl11be35s", + "link": "lexical-elements.html#fls_qsbnl11be35s", + "number": "2.5:2" }, { - "checksum": "5ce7389b0120f2188d0456318c21e6e97516400b08b9931e9fc08d0e36c18a35", - "id": "fls_5i27houut1mu", - "link": "attributes.html#fls_5i27houut1mu", - "number": "13.2:46" + "checksum": "21cfcc3e2a54f9de067b507a9febe04379152937834f9c805769db5f4f31efaf", + "id": "fls_nayisy85kyq2", + "link": "lexical-elements.html#fls_nayisy85kyq2", + "number": "2.5:3" }, { - "checksum": "ef068eea3b4195d63c4faeaf8ddec06130b9c97886f65d6a735641fdfa0bbe99", - "id": "fls_1v9p4vr1nszn", - "link": "attributes.html#fls_1v9p4vr1nszn", - "number": "13.2:47" + "checksum": "44db9ffd84a7092979605a0738feeec5f4b7058b977a23c47673c33c3ece8211", + "id": "fls_k3hj30hjkdhw", + "link": "lexical-elements.html#fls_k3hj30hjkdhw", + "number": "2.5:4" }, { - "checksum": "85810ed8c5e753c8587f859f84c0c189be76078846edaa0d4b9844829fc5646f", - "id": "fls_jvkgtnulrqgh", - "link": "attributes.html#fls_jvkgtnulrqgh", - "number": "13.2:48" + "checksum": "46b1ba35f675d689f5cccd754f8935bdbf89c5ad8ddf1e0563957ca73a891019", + "id": "fls_tspijl68lduc", + "link": "lexical-elements.html#fls_tspijl68lduc", + "number": "2.5:5" }, { - "checksum": "634ed90b7e9a1e93528b551b7e87ccc2f19cabaabd8cfe119731630c1555b2f8", - "id": "fls_k9p2xrs3dotn", - "link": "attributes.html#fls_k9p2xrs3dotn", - "number": "13.2:49" + "checksum": "270e8134659947ebca11488a29a6802edd92f2d00c5c5cc2cdd352343a96c6da", + "id": "fls_KZp0yiFLTqxb", + "link": "lexical-elements.html#fls_KZp0yiFLTqxb", + "number": "2.5:6" }, { - "checksum": "cd700e1e3816ad09e427ed80ad2362eeff80ffa5182360299496b6583c87ec3e", - "id": "fls_73n30xdcx8e", - "link": "attributes.html#fls_73n30xdcx8e", - "number": "13.2:50" + "checksum": "7db44552637b47a7c80a482ccb21eff193aec3ef608b793139b79c90b9d259e9", + "id": "fls_63gzofa9ktic", + "link": "lexical-elements.html#fls_63gzofa9ktic", + "number": "2.5:7" }, { - "checksum": "415ad2d08817bd284d6e72dfa33b0e8321eb4afb36b1cad3da54238b9df07fd6", - "id": "fls_e7zusnfka5dt", - "link": "attributes.html#fls_e7zusnfka5dt", - "number": "13.2:51" + "checksum": "1d2db5c6599512c9ff5347dd4643d081ca0ef713a638861316d0eb850fea2aef", + "id": "fls_scko7crha0um", + "link": "lexical-elements.html#fls_scko7crha0um", + "number": "2.5:8" }, { - "checksum": "b241235f00eb9bda3db61fcee7dba7a5a395a008dda75dba1c18489f5ec52c23", - "id": "fls_85ul6x76ew9", - "link": "attributes.html#fls_85ul6x76ew9", - "number": "13.2:52" + "checksum": "89aba4c16b95e9fa5e40e6f8fda1c986eb5097820542fc57bf4d63ee2641be25", + "id": "fls_RYVL9KgaxKvl", + "link": "lexical-elements.html#fls_RYVL9KgaxKvl", + "number": "2.5:9" }, { - "checksum": "ad228252c455413a9d11054129ea0a307eb4301392fb4dc7aaaba0db8b102e6c", - "id": "fls_xkhm1sht2ju5", - "link": "attributes.html#fls_xkhm1sht2ju5", - "number": "13.2:53" + "checksum": "b5cdea2b94f91084652299806e1a80b282c551014c7d65952547fca3a2bb7cf8", + "id": "fls_7n6d3jx61ose", + "link": "lexical-elements.html#fls_7n6d3jx61ose", + "number": "2.5:10" }, { - "checksum": "3a524553119d48abe6bc7fdc94871d5862f0815aab4e5cfb1f63f69d71656f2b", - "id": "fls_w9za4moh6gb3", - "link": "attributes.html#fls_w9za4moh6gb3", - "number": "13.2:54" + "checksum": "a34430d6423f60015c14850fbc8c713a11943c37acb592477ed6bb0ed902b6a2", + "id": "fls_6fxcs17n4kw", + "link": "lexical-elements.html#fls_6fxcs17n4kw", + "number": "2.5:11" }, { - "checksum": "8aafb6d088ea34e1340f6dc625d312ae87a35784cb19b992667687abc8f9bb36", - "id": "fls_3vubhygy9jje", - "link": "attributes.html#fls_3vubhygy9jje", - "number": "13.2:55" + "checksum": "fe2e7af7111eab22f62c00b2ba9fc0c53748fcdb5ec02a7e20dd1e4247706e15", + "id": "fls_uze7l7cxonk1", + "link": "lexical-elements.html#fls_uze7l7cxonk1", + "number": "2.5:12" }, { - "checksum": "90292e11d3564cdfea3b3a715f63a2a2bc7ff5c0e0a7bf7394d45a73612e1542", - "id": "fls_mhaplbf40j02", - "link": "attributes.html#fls_mhaplbf40j02", - "number": "13.2:56" + "checksum": "6cff261ef0c0d257749ff6809ac237900fb6673ccb75e5186c7b4fa8857f9330", + "id": "fls_gy23lwlqw2mc", + "link": "lexical-elements.html#fls_gy23lwlqw2mc", + "number": "2.5:13" }, { - "checksum": "044666250ed6ab719cc195deadec75751cf88ccd03d5d74f0424888448852461", - "id": "fls_23huzf3c4arx", - "link": "attributes.html#fls_23huzf3c4arx", - "number": "13.2:57" + "checksum": "cf00d52ba7c749b5e814bc2852d5e73d2b5454950abb829df0c20d7bb9ba9d60", + "id": "fls_w7d0skpov1is", + "link": "lexical-elements.html#fls_w7d0skpov1is", + "number": "2.5:14" }, { - "checksum": "5b250c789906dbcf10378e94398f0b4e5540359c213277369259c48af0bbe0f9", - "id": "fls_i63y9xnnwq2z", - "link": "attributes.html#fls_i63y9xnnwq2z", - "number": "13.2:58" + "checksum": "255a7dcb2fc2b925e5aa49e3be573dcce5c450e40835c21a4ce5008439cdbeb8", + "id": "fls_32ncjvj2kn7z", + "link": "lexical-elements.html#fls_32ncjvj2kn7z", + "number": "2.5:15" }, { - "checksum": "546608d35bfe5a90d5a66a076a3d517f618f561c82800080bab893cd87c9f7aa", - "id": "fls_yic8ksed28no", - "link": "attributes.html#fls_yic8ksed28no", - "number": "13.2:59" + "checksum": "fc50838ac3e706c4c30cfb01beba8783dc8fdb5dd7d6cb42ea6b4123e33b6e5b", + "id": "fls_ok0zvo9vcmzo", + "link": "lexical-elements.html#fls_ok0zvo9vcmzo", + "number": "2.5:16" }, { - "checksum": "bc97a9ab750dc2ab4e8945d338a63dc76c13eb9d172e00871b1bc6678c592cfc", - "id": "fls_p1ugiol1e5v5", - "link": "attributes.html#fls_p1ugiol1e5v5", - "number": "13.2:60" + "checksum": "4c1420ace1da3c3694b1aebd35cada39def878b5c4f642ff6950ad600c7138a5", + "id": "fls_nWtKuPi8Fw6v", + "link": "lexical-elements.html#fls_nWtKuPi8Fw6v", + "number": "2.5:17" + } + ], + "title": "Comments" + }, + { + "id": "fls_lish33a1naw5", + "informational": false, + "link": "lexical-elements.html#keywords", + "number": "2.6", + "paragraphs": [ + { + "checksum": "b661cfaf9bef72a6f8070ed5158ce24d582cc5371b80a7c78f52e7d49157b48f", + "id": "fls_dti0uu7rz81w", + "link": "lexical-elements.html#fls_dti0uu7rz81w", + "number": "2.6:1" }, { - "checksum": "6212b84580c14ced11ff1299672bb121d44059cb744a474b7e412ee35d653f43", - "id": "fls_7xh2iphiteam", - "link": "attributes.html#fls_7xh2iphiteam", - "number": "13.2:61" + "checksum": "9d03ce7dba3147733fdacd077fd30354c8d7b6a79883472bedba6a9643f63d6b", + "id": "fls_sxg1o4oxql51", + "link": "lexical-elements.html#fls_sxg1o4oxql51", + "number": "2.6:2" } ], - "title": "Built-in Attributes" + "title": "Keywords" }, { - "id": "fls_h4k49eadninz", + "id": "fls_mec5cg5aptf8", "informational": false, - "link": "attributes.html#code-generation-attributes", - "number": "13.2.1", - "paragraphs": [], - "title": "Code Generation Attributes" + "link": "lexical-elements.html#strict-keywords", + "number": "2.6.1", + "paragraphs": [ + { + "checksum": "8394e357df5177d8ada084b53fdd141fc0e1a1ca7b937b28a47415ac0dce750f", + "id": "fls_bsh7qsyvox21", + "link": "lexical-elements.html#fls_bsh7qsyvox21", + "number": "2.6.1:1" + } + ], + "title": "Strict Keywords" }, { - "id": "fls_kpwbpp5hc00s", + "id": "fls_cbsgp6k0qa82", "informational": false, - "link": "attributes.html#attribute-cold", - "number": "13.2.1.1", + "link": "lexical-elements.html#reserved-keywords", + "number": "2.6.2", "paragraphs": [ { - "checksum": "fb40c1f17f95fed4bda4668940636f75c4436a94cdf00ee6fd2ae36e0ea1c564", - "id": "fls_x860jl4103p", - "link": "attributes.html#fls_x860jl4103p", - "number": "13.2.1.1:1" + "checksum": "3f05a14ae9bd0f2dcdc7c65c76de451ed9485badef6dc6bb29c3fae69124e92d", + "id": "fls_w4b97ewwnql", + "link": "lexical-elements.html#fls_w4b97ewwnql", + "number": "2.6.2:1" + } + ], + "title": "Reserved Keywords" + }, + { + "id": "fls_9kjpxri0axvg", + "informational": false, + "link": "lexical-elements.html#weak-keywords", + "number": "2.6.3", + "paragraphs": [ + { + "checksum": "507c02a61084d5e2447a4401c3c9ff54ecfb2b0e46adf98b0e9f96d5fbcd8eb7", + "id": "fls_bv87t1gvj7bz", + "link": "lexical-elements.html#fls_bv87t1gvj7bz", + "number": "2.6.3:1" }, { - "checksum": "e8b558243f7bf0325a129db3b6a9dcfdeaf35a9b8aee0f97064da28323c33ce0", - "id": "fls_8zdexi5lgm2f", - "link": "attributes.html#fls_8zdexi5lgm2f", - "number": "13.2.1.1:2" + "checksum": "c2adb1a3f82e557d2e62b7aa4e734ce8a14427d039fd743bf82710befacdae6a", + "id": "fls_bl55g03jmayf", + "link": "lexical-elements.html#fls_bl55g03jmayf", + "number": "2.6.3:2" + }, + { + "checksum": "7190ee3532730e20b19057227f945e1ad04d6f993bc33984e2dad01773e09ab2", + "id": "fls_c354oryv513p", + "link": "lexical-elements.html#fls_c354oryv513p", + "number": "2.6.3:3" + }, + { + "checksum": "dc15f002b42ce194ade6b4b0f4f6b5ca7bb28176b493eac737f309f66b3e42da", + "id": "fls_r9fhuiq1ys1p", + "link": "lexical-elements.html#fls_r9fhuiq1ys1p", + "number": "2.6.3:4" + }, + { + "checksum": "aa39e6b294be595698343bd1c8da7549bf90acb7b9d7532f7db483335321ba4c", + "id": "fls_g0JEluWqBpNc", + "link": "lexical-elements.html#fls_g0JEluWqBpNc", + "number": "2.6.3:5" } ], - "title": "Attribute cold" - }, + "title": "Weak Keywords" + } + ], + "title": "Lexical Elements" + }, + { + "informational": false, + "link": "implementations.html", + "sections": [ { - "id": "fls_ypio6boj3pwf", + "id": "fls_fk2m2irwpeof", "informational": false, - "link": "attributes.html#attribute-inline", - "number": "13.2.1.2", + "link": "implementations.html", + "number": "11", "paragraphs": [ { - "checksum": "8af34535fc44405dd0d1185fc8ff42acca2853c54da038fc47f8bf644aeedc58", - "id": "fls_jwyhky49ssup", - "link": "attributes.html#fls_jwyhky49ssup", - "number": "13.2.1.2:1" + "checksum": "68d68054bfe8ffe57002321f94f09f2c21faac92f10052af0b8ebb6bb2e4bdd4", + "id": "fls_ivxpoxggy7s6", + "link": "implementations.html#fls_ivxpoxggy7s6", + "number": "11:1" }, { - "checksum": "4d894bc4ab40b2e9fb23428a9ce2913d411c123546417eb804e57322674bae21", - "id": "fls_s7bf7tf9206d", - "link": "attributes.html#fls_s7bf7tf9206d", - "number": "13.2.1.2:2" + "checksum": "a88885418df7946e8facb892e33e099cc8789c354fbf7bd977f460e40ec2089d", + "id": "fls_yopmjbnw8tbl", + "link": "implementations.html#fls_yopmjbnw8tbl", + "number": "11:2" }, { - "checksum": "132910b8fef15bd7f371019b12f4f3ff9e87a2bad1099553cefb639d39ab7d5c", - "id": "fls_930o6urn669w", - "link": "attributes.html#fls_930o6urn669w", - "number": "13.2.1.2:3" + "checksum": "28523ce67d9161886a6bbdc6415a2a18a12cac0bd7cf96d2e560d4acaa243c96", + "id": "fls_eIHc8Y9fBtr0", + "link": "implementations.html#fls_eIHc8Y9fBtr0", + "number": "11:3" + }, + { + "checksum": "073aeb6b836eee108b5b6d5a5ded4f1b8df8ab96ec075686dca1d2b7fc6c1b34", + "id": "fls_Mcpdzzcw43M7", + "link": "implementations.html#fls_Mcpdzzcw43M7", + "number": "11:4" + }, + { + "checksum": "6d7038fd93f535f1a95c93f6917f3cebd5b30ad181afde8d89173856ec36d502", + "id": "fls_v0n0bna40dqr", + "link": "implementations.html#fls_v0n0bna40dqr", + "number": "11:5" + }, + { + "checksum": "e18c1caa38c5924e033a0026fc017b1c792df55f87c598c5783efe25cb62819f", + "id": "fls_797etpdk5dyb", + "link": "implementations.html#fls_797etpdk5dyb", + "number": "11:6" + }, + { + "checksum": "74cac3914710c2f1a1f624a3c5a1c9aaf33da46b9946695f9bee4eaeb22c5e42", + "id": "fls_ry3an0mwb63g", + "link": "implementations.html#fls_ry3an0mwb63g", + "number": "11:7" + }, + { + "checksum": "dd34aa75e7947124723372145385a391a6527acf3d57821cf6bfb3530926d0df", + "id": "fls_8pwr7ibvhmhu", + "link": "implementations.html#fls_8pwr7ibvhmhu", + "number": "11:8" + }, + { + "checksum": "ba2818ea733c9b43c0160165759dc06495876220780d457240575201aebbf397", + "id": "fls_47x0ep8of8wr", + "link": "implementations.html#fls_47x0ep8of8wr", + "number": "11:9" + }, + { + "checksum": "8223ac177e0dcc171dbac1fb21b191141842026a92038b9694be40797d2bdad8", + "id": "fls_agitlryvyc16", + "link": "implementations.html#fls_agitlryvyc16", + "number": "11:10" + }, + { + "checksum": "0ecbb270fa78be720b325ddc3197182ea1ca39be3069bd7d37f201389c9b3991", + "id": "fls_mx5xjcejwa6u", + "link": "implementations.html#fls_mx5xjcejwa6u", + "number": "11:11" + }, + { + "checksum": "7ec52a2c02f0e55bea6e0a3c728c22e5720e34bdeabc0740d52707774946acbe", + "id": "fls_z78dg261oob6", + "link": "implementations.html#fls_z78dg261oob6", + "number": "11:12" }, { - "checksum": "c0e37dc273ef977b6bdcb9424c48613b76cb248df209151bdc9e32a5e5e9d029", - "id": "fls_z7ufiqqujgdh", - "link": "attributes.html#fls_z7ufiqqujgdh", - "number": "13.2.1.2:4" + "checksum": "1f973a701dba719c9b5c7879e8f7e39c271e60ebd4708b781a5ec557ca35bb6b", + "id": "fls_89yNjGNB7KI3", + "link": "implementations.html#fls_89yNjGNB7KI3", + "number": "11:13" }, { - "checksum": "02ef19bf80c5200669e96e008b98fccff3c5fa277c7fad57dece77bdf97528fc", - "id": "fls_f0n4g5uky9tp", - "link": "attributes.html#fls_f0n4g5uky9tp", - "number": "13.2.1.2:5" + "checksum": "94597e560f592f649b38ef27778a62819bc7e5c2e01faa0ef53fa29225d8083c", + "id": "fls_yuyesijndu9n", + "link": "implementations.html#fls_yuyesijndu9n", + "number": "11:14" }, { - "checksum": "cd14e98a49d9891ed4fcd4b3b9a2704b3acc39acbfd6deb0698c5b2810227f7e", - "id": "fls_r3p4din7rjz8", - "link": "attributes.html#fls_r3p4din7rjz8", - "number": "13.2.1.2:6" + "checksum": "7093aa443b2d63b8c3ad1d99dc26a6f1df03d367428b954e62e372eab3d4f569", + "id": "fls_o62i75sjzp9y", + "link": "implementations.html#fls_o62i75sjzp9y", + "number": "11:15" + }, + { + "checksum": "8ad70788c23781cfbb4cc77f8e9b280870f2088b7f3522cf5adf7a4030b29b48", + "id": "fls_a2utf0tmuhy4", + "link": "implementations.html#fls_a2utf0tmuhy4", + "number": "11:16" } ], - "title": "Attribute inline" + "title": "Implementations" }, { - "id": "fls_zakwockktml8", + "id": "fls_46ork6fz5o2e", "informational": false, - "link": "attributes.html#attribute-no-builtins", - "number": "13.2.1.3", + "link": "implementations.html#implementation-coherence", + "number": "11.1", "paragraphs": [ { - "checksum": "8009c0049e3490fddc7e24a87c11b90f80ecb8de0220062b76efe87d97c52f20", - "id": "fls_x36c6j1ivbvp", - "link": "attributes.html#fls_x36c6j1ivbvp", - "number": "13.2.1.3:1" + "checksum": "f593f63f6406327889e137ab55ec05d98a6d439176ec14afafae261241a14f1c", + "id": "fls_fv1l4yjuut7p", + "link": "implementations.html#fls_fv1l4yjuut7p", + "number": "11.1:1" }, { - "checksum": "7089a3f20c79b60dbcbc66b2e3df9d1c5911e691c738cd1846adf88d4ac1e68b", - "id": "fls_k2k10qtn6f0g", - "link": "attributes.html#fls_k2k10qtn6f0g", - "number": "13.2.1.3:2" - } - ], - "title": "Attribute no_builtins" - }, - { - "id": "fls_spdmit5fy7el", - "informational": false, - "link": "attributes.html#attribute-target-feature", - "number": "13.2.1.4", - "paragraphs": [ + "checksum": "1452e1e4d864f7a4178e6f1be77e9694766805cd6a0556f67f5314caf0a5d8de", + "id": "fls_swdusjwzgksx", + "link": "implementations.html#fls_swdusjwzgksx", + "number": "11.1:2" + }, { - "checksum": "22c8e64d5070f5f98dfe2031a0d503ba976e3328bcb924c00ebffd8a8fa115e3", - "id": "fls_3qj3jvmtxvx6", - "link": "attributes.html#fls_3qj3jvmtxvx6", - "number": "13.2.1.4:1" + "checksum": "9763aff40d5e4ca431a133884a391a71d1e3434b2dd8e6ab1c928ba846346526", + "id": "fls_ir7hp941ky8t", + "link": "implementations.html#fls_ir7hp941ky8t", + "number": "11.1:3" }, { - "checksum": "585e4a1fa321aa3303ab0bd5552d8d1721f633efa71ec8860f55edcc75647378", - "id": "fls_agpkz1v3c281", - "link": "attributes.html#fls_agpkz1v3c281", - "number": "13.2.1.4:2" + "checksum": "3a7e9191eb41e4bba88408faba2f7f149137b7df440996588e945fad99c82867", + "id": "fls_3tbm20k2ixol", + "link": "implementations.html#fls_3tbm20k2ixol", + "number": "11.1:4" }, { - "checksum": "e472ceda7230cf454f09e04024a0eaaa04baecb00eb12585de90bdece33f7f1d", - "id": "fls_91b7nd6qslsb", - "link": "attributes.html#fls_91b7nd6qslsb", - "number": "13.2.1.4:3" + "checksum": "46b1fe27e9a10a5a463205624c88ba78b4851fbf47c3d1e31d45026df49b1774", + "id": "fls_lscc9ileg3gm", + "link": "implementations.html#fls_lscc9ileg3gm", + "number": "11.1:5" }, { - "checksum": "f31211e5b9f382a935e4e483c3fc10fc958066dcb3b64be6772d797738339919", - "id": "fls_yz4itbk700ot", - "link": "attributes.html#fls_yz4itbk700ot", - "number": "13.2.1.4:4" + "checksum": "90514614f2da8202c20d849b7c71d72ae95423a582d7fcb612d03343c5ce1b8a", + "id": "fls_9klwbsh3vlxu", + "link": "implementations.html#fls_9klwbsh3vlxu", + "number": "11.1:6" }, { - "checksum": "094f219b2ede048c90c66692b41e7a30763694b917cfced20fa4eb76afc6a626", - "id": "fls_pdyotoq8uqi2", - "link": "attributes.html#fls_pdyotoq8uqi2", - "number": "13.2.1.4:5" + "checksum": "79f486e433c0cc3589e42f1efbdc6df12866131778c2b136b5be7d27ff1b67ec", + "id": "fls_9gmc1tcscq9v", + "link": "implementations.html#fls_9gmc1tcscq9v", + "number": "11.1:7" }, { - "checksum": "78bb46c24f2ca946ed0b4e7c6089e40d13e6c39431bb0fee1bab3542dde16f26", - "id": "fls_vdbjoy6gbk7l", - "link": "attributes.html#fls_vdbjoy6gbk7l", - "number": "13.2.1.4:6" + "checksum": "6b6dac78cb7d28fe378157a5827ca98ef8996833871c9acbbad28e04b8781132", + "id": "fls_UkQhjEWSJpDq", + "link": "implementations.html#fls_UkQhjEWSJpDq", + "number": "11.1:8" }, { - "checksum": "897555f2c7487adb369d557e88e95f68598fd643686dc9402f4f295226b210f6", - "id": "fls_k3szii6nviza", - "link": "attributes.html#fls_k3szii6nviza", - "number": "13.2.1.4:7" + "checksum": "cbf8117eb6e59dd31ff3217f236513679c4236d8d108c4451693dfdeb766d3d2", + "id": "fls_fSybUG40hA5r", + "link": "implementations.html#fls_fSybUG40hA5r", + "number": "11.1:9" }, { - "checksum": "eb4568cb53c1a9e06aea725449fa9eba6f2a425e98354dca4df23d483faa90e2", - "id": "fls_xsdkkfgv0pz6", - "link": "attributes.html#fls_xsdkkfgv0pz6", - "number": "13.2.1.4:8" + "checksum": "35936abe2e47a3f84e6b97c5b8773f0e57035b2a2311e2ec92b001e91f057096", + "id": "fls_z8APl0CEF7a0", + "link": "implementations.html#fls_z8APl0CEF7a0", + "number": "11.1:10" }, { - "checksum": "608441af0bf33fcb985d864c6438f0424b8c0024d8e8d616516acbf8feb8fee0", - "id": "fls_8HPcvIaahaB9", - "link": "attributes.html#fls_8HPcvIaahaB9", - "number": "13.2.1.4:9" + "checksum": "16c8bf3ad79c585e2a19ab5043608ba5cdfbe59b334ba09913fe7cb101ef98a0", + "id": "fls_RJJafhpVsi6M", + "link": "implementations.html#fls_RJJafhpVsi6M", + "number": "11.1:11" }, { - "checksum": "ad80d367854d025876fe951c841bb59171240dc3569f2634f8af6ed33ff9e914", - "id": "fls_lxcfj6XCKyMh", - "link": "attributes.html#fls_lxcfj6XCKyMh", - "number": "13.2.1.4:10" + "checksum": "bd35a79daaebf148b48f637a939718b4e78fe6d95ec05e092dabf33c372e5045", + "id": "fls_dtUJxhNkl8Ty", + "link": "implementations.html#fls_dtUJxhNkl8Ty", + "number": "11.1:12" }, { - "checksum": "dc4cd53cd07e16818bffb2ecd22fd3f7eb8a09f1e4abea7359ba3227288a44b1", - "id": "fls_fh7i1Pcmqjdm", - "link": "attributes.html#fls_fh7i1Pcmqjdm", - "number": "13.2.1.4:11" + "checksum": "8e6a64bbe4e6692fa29ab6cab825bed32aa92944541ab8b3cab95f46ec47ae1a", + "id": "fls_zJKovQrXQWdU", + "link": "implementations.html#fls_zJKovQrXQWdU", + "number": "11.1:13" }, { - "checksum": "d04661f1ac206b79927599e6d6a0bfe1136b68c96a331d7cc8b49bb7b5d07f00", - "id": "fls_M9ne2sqhRB5L", - "link": "attributes.html#fls_M9ne2sqhRB5L", - "number": "13.2.1.4:12" + "checksum": "881eb7081d32bca6dd31a402732083c1a10cfbb0703d65d4903481c9df0fa6cc", + "id": "fls_V6R8yQtsqNyv", + "link": "implementations.html#fls_V6R8yQtsqNyv", + "number": "11.1:14" }, { - "checksum": "94b07f6d75030423999a90142907265ec0a64635257aee11028e5d641063a50c", - "id": "fls_xWcGvl83bVWC", - "link": "attributes.html#fls_xWcGvl83bVWC", - "number": "13.2.1.4:13" + "checksum": "0bff693cd5c7bd24006d97966faf22d8ad51776dd27b155aa3c4c53705f1e230", + "id": "fls_CpC6XQN1iWqU", + "link": "implementations.html#fls_CpC6XQN1iWqU", + "number": "11.1:15" }, { - "checksum": "d3d0d81c7000536ec1b34ad7e74c9d33eb39d2f4a11c957088da3c60ca76e3af", - "id": "fls_5CVTkOoLHAvW", - "link": "attributes.html#fls_5CVTkOoLHAvW", - "number": "13.2.1.4:14" + "checksum": "3a1f51af690831f150b9917ff7ed785fc0ad8572bfdb024cbbe419e2b1536063", + "id": "fls_dj7YGw4e4i4H", + "link": "implementations.html#fls_dj7YGw4e4i4H", + "number": "11.1:16" }, { - "checksum": "3845bcb7567d8d9e66bcfcc0a654e06bad83bebb46a876b54c509242e1d3f66b", - "id": "fls_hThb8qn820wJ", - "link": "attributes.html#fls_hThb8qn820wJ", - "number": "13.2.1.4:15" + "checksum": "a69a7eba188277ac25d75030529f9289094778fd2d87d38a1ed7ab9c1871938e", + "id": "fls_koy70k770ayu", + "link": "implementations.html#fls_koy70k770ayu", + "number": "11.1:17" + } + ], + "title": "Implementation Coherence" + }, + { + "id": "fls_e1pgdlv81vul", + "informational": false, + "link": "implementations.html#implementation-conformance", + "number": "11.2", + "paragraphs": [ + { + "checksum": "235dcfef5a150c402f42c33dd6eabd7189375ddb2bb8e3cd3cc162fe22a53e9a", + "id": "fls_YyUSuAYG4lX6", + "link": "implementations.html#fls_YyUSuAYG4lX6", + "number": "11.2:1" }, { - "checksum": "49ec6a3537a6d434982bebfc93e4b2dab76e0408afca075eb02d51e368697849", - "id": "fls_1T0VYMtG3loD", - "link": "attributes.html#fls_1T0VYMtG3loD", - "number": "13.2.1.4:16" + "checksum": "5f1a4f88e1dd9fd77d93c773080c7b2e8fa3179f7d8e4de72e39d0ff16a2a387", + "id": "fls_v31idwjau90d", + "link": "implementations.html#fls_v31idwjau90d", + "number": "11.2:2" }, { - "checksum": "c58720c6b68a1afc328578498e55c128aa688af5b89a0af7c37bb3457a6a17c4", - "id": "fls_AlBD9ckWKecQ", - "link": "attributes.html#fls_AlBD9ckWKecQ", - "number": "13.2.1.4:17" + "checksum": "f18161b732608a6732ea2886b4c609dc56b7dde6df16c57cf321542c887aa1d8", + "id": "fls_k3wfh5japmyw", + "link": "implementations.html#fls_k3wfh5japmyw", + "number": "11.2:3" }, { - "checksum": "2ea2a829b952259c5e3afbcd3380fa54a7fc88125892652cbb201913984b7bc5", - "id": "fls_72cwAExPDMQ8", - "link": "attributes.html#fls_72cwAExPDMQ8", - "number": "13.2.1.4:18" + "checksum": "428b016d82301c34cac20a058d8e9f87628abcd977fc4d1ff1557bc1bf2d24f8", + "id": "fls_11qrqfuc3rmh", + "link": "implementations.html#fls_11qrqfuc3rmh", + "number": "11.2:4" }, { - "checksum": "d09120b12c65a7fe812199037d86b39d1d38ce1b024ccd0a13802593f093668b", - "id": "fls_pSSIwmUAnUly", - "link": "attributes.html#fls_pSSIwmUAnUly", - "number": "13.2.1.4:19" + "checksum": "7e921c53c7799271e80ccbadf2d47a340d1ef1bd9326fe4e2bdf54e5dcf2d127", + "id": "fls_qmhduwunxww0", + "link": "implementations.html#fls_qmhduwunxww0", + "number": "11.2:5" }, { - "checksum": "13794029852993d23ab1cec2d6742f1e1aca6a0a824cffc6e3742074ab90817f", - "id": "fls_YOW8lcBQ5lNI", - "link": "attributes.html#fls_YOW8lcBQ5lNI", - "number": "13.2.1.4:20" + "checksum": "aa4594177b713ab6ee2d502e2c0f20256ac5a5e546fab64bcc52db72ee407c5d", + "id": "fls_2500ivh0cc3y", + "link": "implementations.html#fls_2500ivh0cc3y", + "number": "11.2:6" }, { - "checksum": "805e0ccd6302372755ec7d7d9c166ede1fe33dc04d90e681338c1609c7433820", - "id": "fls_8PrvVL6dkr31", - "link": "attributes.html#fls_8PrvVL6dkr31", - "number": "13.2.1.4:21" + "checksum": "f3fb2ce2caeb76b89e224e1c88594d2dc95ddc66e180f8cb6d50b428f01eedcf", + "id": "fls_18gimgfy0kw9", + "link": "implementations.html#fls_18gimgfy0kw9", + "number": "11.2:7" }, { - "checksum": "2a750f04a08177128570c4f41ac7048b7ff5f8b40de571baef0b44fee9aabcdb", - "id": "fls_hKvaSExdlKgd", - "link": "attributes.html#fls_hKvaSExdlKgd", - "number": "13.2.1.4:22" + "checksum": "0b6f276c677931c78d760ff5def7e67a7e4cbd817e1f21f0343edb674c80e0b4", + "id": "fls_fi4qmauirlsm", + "link": "implementations.html#fls_fi4qmauirlsm", + "number": "11.2:8" }, { - "checksum": "545ee5833860bcaa0b09932ad5620f836dff0b2e2f410c4d31c3e1d565600e42", - "id": "fls_r5KEicrmEGJt", - "link": "attributes.html#fls_r5KEicrmEGJt", - "number": "13.2.1.4:23" + "checksum": "31bad81c5be19c117a8675a891a00247edb13fe1ab49a36e1f439da9b3123b6f", + "id": "fls_2s8lh3k4rw6u", + "link": "implementations.html#fls_2s8lh3k4rw6u", + "number": "11.2:9" }, { - "checksum": "f29c40b7092bacf659efd63cdb93521672f99f5df0f785d45a8159f7a9028860", - "id": "fls_Qnx9nNiJeIAX", - "link": "attributes.html#fls_Qnx9nNiJeIAX", - "number": "13.2.1.4:24" + "checksum": "fe5486ca02f9ffadaed847c11ea13af8afba0eb18ea38527ec5b8cc5d45cbf83", + "id": "fls_bb874uu2alt3", + "link": "implementations.html#fls_bb874uu2alt3", + "number": "11.2:10" }, { - "checksum": "e3f949f5ca26f7ab9cb8273e2a8cd0d22e6358e4a3b7226b22d76c4edc7cb296", - "id": "fls_ZrcPWy4pxlTX", - "link": "attributes.html#fls_ZrcPWy4pxlTX", - "number": "13.2.1.4:25" + "checksum": "43150bf6a5f61f75ca365a77174d7d659f67e1c8cd00dcd74b3f72cdc7282045", + "id": "fls_so8em6rphkhv", + "link": "implementations.html#fls_so8em6rphkhv", + "number": "11.2:11" }, { - "checksum": "c89d3584820e4afd9fa4879a368d118a3269eb7fcf5d7548f5812f5d3b037214", - "id": "fls_YW7otDxAliSj", - "link": "attributes.html#fls_YW7otDxAliSj", - "number": "13.2.1.4:26" + "checksum": "2928f339f8abfbe9b10de44c5ab57f730fdc2e7881dc3cce685844a2ada75373", + "id": "fls_ldu9bmb9cy10", + "link": "implementations.html#fls_ldu9bmb9cy10", + "number": "11.2:12" }, { - "checksum": "3f4b80364c4cc96da4e299a7e6771581713daa9ebfa980924f1c654128b1b34c", - "id": "fls_G4aOoX8a7i7r", - "link": "attributes.html#fls_G4aOoX8a7i7r", - "number": "13.2.1.4:27" + "checksum": "1a07917d9eeb24596f2800e26f4930f6ecc94fc0027f33f7cce840c026ee28a5", + "id": "fls_5cr6un2gzdft", + "link": "implementations.html#fls_5cr6un2gzdft", + "number": "11.2:13" }, { - "checksum": "27fceb61f07a31faf22bccecf31e526e2d22bf0615fbe8998ffcd1dd528e8e35", - "id": "fls_eglaup5zyfsk", - "link": "attributes.html#fls_eglaup5zyfsk", - "number": "13.2.1.4:28" + "checksum": "dc51b8a6f90c2e32e2ec099057c7d4745f9082e4521e0f0cb64ba03ac87c95ed", + "id": "fls_pshfe3ioh0mg", + "link": "implementations.html#fls_pshfe3ioh0mg", + "number": "11.2:14" }, { - "checksum": "e37b94209979512d83141b81df8da7e950dc0f59ee4df88e7a1977cf6547eb6a", - "id": "fls_tkayqh7li81o", - "link": "attributes.html#fls_tkayqh7li81o", - "number": "13.2.1.4:29" + "checksum": "bc54fae0d59dee07617ca65931de1869df56a18c8afe575dcc287c8d2d9305ed", + "id": "fls_8yq1g7nzv9px", + "link": "implementations.html#fls_8yq1g7nzv9px", + "number": "11.2:15" + } + ], + "title": "Implementation Conformance" + } + ], + "title": "Implementations" + }, + { + "informational": false, + "link": "items.html", + "sections": [ + { + "id": "fls_wb86edg02t6a", + "informational": false, + "link": "items.html", + "number": "3", + "paragraphs": [ + { + "checksum": "7828a434b91eb775a25b624f9c2c6e10d5b02ed8e26529f7471e4a787e24e10f", + "id": "fls_s3b1cba9lfj5", + "link": "items.html#fls_s3b1cba9lfj5", + "number": "3:1" }, { - "checksum": "f04d0e556354ad85a7cb772b90a5bea40dfc80d7258b591367ef1c4299bd26cd", - "id": "fls_vv8o31s0zmli", - "link": "attributes.html#fls_vv8o31s0zmli", - "number": "13.2.1.4:30" + "checksum": "c820b8889c9e57bcae1988c78c8217a0fcd495f92409b93715942f79e683ae67", + "id": "fls_hil5f7y4xdhe", + "link": "items.html#fls_hil5f7y4xdhe", + "number": "3:2" + } + ], + "title": "Items" + } + ], + "title": "Items" + }, + { + "informational": false, + "link": "inline-assembly.html", + "sections": [ + { + "id": "fls_z1il3w9nulzy", + "informational": true, + "link": "inline-assembly.html", + "number": "22", + "paragraphs": [ + { + "checksum": "2ecbb717faf9cd41966fd3fa61c9973605adda7174928d76aeec692e2f57f1e1", + "id": "fls_leamdp1r3hez", + "link": "inline-assembly.html#fls_leamdp1r3hez", + "number": "22:1" }, { - "checksum": "75c3e60c0e86babf3644778265ff04d82086fd20428ca94add2ab7fa29a4b786", - "id": "fls_g0762ekvl2ah", - "link": "attributes.html#fls_g0762ekvl2ah", - "number": "13.2.1.4:31" + "checksum": "00ed2a429f932fb29fe5c4c1f6e590de43efcbdaccd713e8bdc9bfd3b22cfd12", + "id": "fls_3fg60jblx0xb", + "link": "inline-assembly.html#fls_3fg60jblx0xb", + "number": "22:2" }, { - "checksum": "f941c343b6b7d9d96bcda5e79fe20a1b97031d28a96a15c7ec8d6bb9487a79ca", - "id": "fls_HGKiXfM5fGVo", - "link": "attributes.html#fls_HGKiXfM5fGVo", - "number": "13.2.1.4:32" + "checksum": "fb5d9789deafd6c411d8abdd1a60bfdfea453bfbfe713df9016b5b07c26df2c9", + "id": "fls_helnk2iz8qhp", + "link": "inline-assembly.html#fls_helnk2iz8qhp", + "number": "22:3" }, { - "checksum": "f1f41a196874835015ff054c202bbeaca27370ac8300bea5bf53d03d074d72df", - "id": "fls_gtoLNHFmmBzd", - "link": "attributes.html#fls_gtoLNHFmmBzd", - "number": "13.2.1.4:33" + "checksum": "4b4a76da9d4b5011fcbcd054fffc111ce72238f797e8cf7b2bbbccec650c24c3", + "id": "fls_vanmt2dv7hzd", + "link": "inline-assembly.html#fls_vanmt2dv7hzd", + "number": "22:4" }, { - "checksum": "e9b710a4e7a5d951c56b3f75f26e35b7a826e3d5c1958e4e71d5502857541ad0", - "id": "fls_406ltkru11tk", - "link": "attributes.html#fls_406ltkru11tk", - "number": "13.2.1.4:34" + "checksum": "55a932bf32ca4b5f1f13535ebf918728efa7830451bde120075904c8cc821ff0", + "id": "fls_g8fuy1qnebxe", + "link": "inline-assembly.html#fls_g8fuy1qnebxe", + "number": "22:5" }, { - "checksum": "4e93115858ebed6f679bcd9090c45327132a80d56ebcefa7ba556b066fc97d72", - "id": "fls_4mhyvwj8pbpk", - "link": "attributes.html#fls_4mhyvwj8pbpk", - "number": "13.2.1.4:35" + "checksum": "2932ba93879a19d635e09c54bc40e411c50ad1cd415e1048e6a594284226a29f", + "id": "fls_2n9el98anp8c", + "link": "inline-assembly.html#fls_2n9el98anp8c", + "number": "22:6" }, { - "checksum": "5140f4b9a43b91808591e2b954a2980ff337e13a4e8335e3e29f7498a06a869e", - "id": "fls_ijsmqk7pgyiz", - "link": "attributes.html#fls_ijsmqk7pgyiz", - "number": "13.2.1.4:36" + "checksum": "c16628799da95c60a31934e0f2f6de7cb46011a537bb8effa46c86007e1dc6d7", + "id": "fls_pnoh31rvu9a6", + "link": "inline-assembly.html#fls_pnoh31rvu9a6", + "number": "22:7" + } + ], + "title": "Inline Assembly" + }, + { + "id": "fls_j9l8wn6wgm06", + "informational": false, + "link": "inline-assembly.html#registers", + "number": "22.1", + "paragraphs": [ + { + "checksum": "39d60a2f6181ef9205caf904fd95f7ba0a390aeb492700ff04033297298fff41", + "id": "fls_w5a6rybvptn6", + "link": "inline-assembly.html#fls_w5a6rybvptn6", + "number": "22.1:1" }, { - "checksum": "7858b9613091a34085b288d29faa10cc8eba087a29ddb9ec313ffbdd3f6db23e", - "id": "fls_a1jggfetycxh", - "link": "attributes.html#fls_a1jggfetycxh", - "number": "13.2.1.4:37" + "checksum": "73d6369a132933d1c5b8a2cfddbb5f48324fe1b292208ff88d01629411bb9802", + "id": "fls_rr8rsy7oilf0", + "link": "inline-assembly.html#fls_rr8rsy7oilf0", + "number": "22.1:2" }, { - "checksum": "417c3d70bcfa0a289a99c07935492966631944ffed968fac67820222ee2f9f1d", - "id": "fls_rmbadmoeaoxu", - "link": "attributes.html#fls_rmbadmoeaoxu", - "number": "13.2.1.4:38" + "checksum": "12f179e5416873944a2cc637d0784da3593495a2183782ee5eb02950eeae9da6", + "id": "fls_5ruu8n6r9mvp", + "link": "inline-assembly.html#fls_5ruu8n6r9mvp", + "number": "22.1:3" }, { - "checksum": "a692dae36ad20687143af0ddd8381c71260bb5e4917fbc7b203f47f6e384c986", - "id": "fls_f9ydn049isbv", - "link": "attributes.html#fls_f9ydn049isbv", - "number": "13.2.1.4:39" + "checksum": "b9873fd50a4bcfa74822967e3a60e5e0de2ddcfdaf3e20b8687b32685ff64c0b", + "id": "fls_HV3Y1A2xn0zr", + "link": "inline-assembly.html#fls_HV3Y1A2xn0zr", + "number": "22.1:4" }, { - "checksum": "077e96f720e94612bd7c2f3a598cf2ebc2834093f5091304e84625b0bdcb8845", - "id": "fls_UPhNSZS89sYr", - "link": "attributes.html#fls_UPhNSZS89sYr", - "number": "13.2.1.4:40" + "checksum": "0c60e99f8efc707247c3e040ba544bc81e25f9c00c6018150077ad6e1e2b7015", + "id": "fls_vesfzh8h6qzu", + "link": "inline-assembly.html#fls_vesfzh8h6qzu", + "number": "22.1:5" }, { - "checksum": "48d324179e28ebc6815701ef7ddfbb70bd16b5165022acadd3f55eac59fab408", - "id": "fls_HoNwaLtDzwZi", - "link": "attributes.html#fls_HoNwaLtDzwZi", - "number": "13.2.1.4:41" + "checksum": "a070ce88d95ce414fcca9658f1ecaa93e57637fe94c94a90c0d5f7c2674a098b", + "id": "fls_ffwqxlh60i5w", + "link": "inline-assembly.html#fls_ffwqxlh60i5w", + "number": "22.1:6" }, { - "checksum": "f54827bbb9be1b9aa7a97109fbb8f561186d61fce03a00dbef0a3d56e2dd2ffe", - "id": "fls_VqjfIihpRe9m", - "link": "attributes.html#fls_VqjfIihpRe9m", - "number": "13.2.1.4:42" + "checksum": "bc22aaa1e0a33be94f875c5a08d1d776471f1e063929ab385e062a1114dde726", + "id": "fls_3p8akc7gcsnx", + "link": "inline-assembly.html#fls_3p8akc7gcsnx", + "number": "22.1:7" }, { - "checksum": "f975dc2d5c0c2c15b963eccd4795930fa5cce62aa848ff640e9de66a52ea3cbc", - "id": "fls_7c31e21g6bdj", - "link": "attributes.html#fls_7c31e21g6bdj", - "number": "13.2.1.4:43" + "checksum": "7c3fa30aaa8c0f3bc6f842ae4f591e7d875e79c277ac8f86b165d0913d3060a2", + "id": "fls_7dlx7nt77xk", + "link": "inline-assembly.html#fls_7dlx7nt77xk", + "number": "22.1:8" }, { - "checksum": "4bcec9bb92108de19e67a173df0098642ddb36e508bfcd6556bd6ee10daced06", - "id": "fls_6d5a375j2775", - "link": "attributes.html#fls_6d5a375j2775", - "number": "13.2.1.4:44" + "checksum": "948be818103f72ba1df6ed3e5e0ca91049b4ccb4cd3b6ef73f2a2555d14fa31d", + "id": "fls_w4z7yh5qyyed", + "link": "inline-assembly.html#fls_w4z7yh5qyyed", + "number": "22.1:9" }, { - "checksum": "2e1bf630eca35a69526f65ac5f8fbdfe0c2f92ca2b3ac6d6be31c9e1440e29a3", - "id": "fls_xthidh2nyrno", - "link": "attributes.html#fls_xthidh2nyrno", - "number": "13.2.1.4:45" + "checksum": "763f676df4ed30cc6df7cd82179c4d111f881ba1c05d0d9cb16b54676f276e17", + "id": "fls_rzp8eg6z6x3q", + "link": "inline-assembly.html#fls_rzp8eg6z6x3q", + "number": "22.1:10" }, { - "checksum": "cbb68dabefc941e4d38db9be75022e8aef69299cd2368f981991501d97245972", - "id": "fls_w02pk6kf9w9e", - "link": "attributes.html#fls_w02pk6kf9w9e", - "number": "13.2.1.4:46" + "checksum": "34cd0c382f4f6e1a64167e53b3bd89895d35af1d3829d9d540bf6db843f635f8", + "id": "fls_e1w41918j49", + "link": "inline-assembly.html#fls_e1w41918j49", + "number": "22.1:11" }, { - "checksum": "2765819a612a9117bbe617d8b73b4ff6a353b2f5cd2953bc1c894701ba157e98", - "id": "fls_lzl1gpco3osx", - "link": "attributes.html#fls_lzl1gpco3osx", - "number": "13.2.1.4:47" + "checksum": "29847045f981474ebef77cf27630d2c65b6b9cf14a6df2196c16f636c42d885f", + "id": "fls_q0s90h7xmnn4", + "link": "inline-assembly.html#fls_q0s90h7xmnn4", + "number": "22.1:12" }, { - "checksum": "39dab62220a7b89e525ec4893fc94dc76846950ca49a8ea1faaf23d8edccfa93", - "id": "fls_pGHKFrgGlFtg", - "link": "attributes.html#fls_pGHKFrgGlFtg", - "number": "13.2.1.4:48" + "checksum": "f7001d16fded0766de3bb30882a297cbb7bd22abe36f34fae3214176bfd9411d", + "id": "fls_3pt63w76isay", + "link": "inline-assembly.html#fls_3pt63w76isay", + "number": "22.1:13" }, { - "checksum": "9a1429077ce3fd1214a1e364203492d57772729632414f91b3c2cd2f3d0559ac", - "id": "fls_9x2on8w44k4f", - "link": "attributes.html#fls_9x2on8w44k4f", - "number": "13.2.1.4:49" + "checksum": "3344b7fd47aab10734e11041c74314f0ed34b6cdaf296d6d01a6c691f0c13183", + "id": "fls_f3clxd3vidhh", + "link": "inline-assembly.html#fls_f3clxd3vidhh", + "number": "22.1:14" }, { - "checksum": "9f65c9f29e3107f40aa936d5e59e18a3d0286fa072e95a7d207f4d4d2aabce0e", - "id": "fls_K6TM6oTLL1BA", - "link": "attributes.html#fls_K6TM6oTLL1BA", - "number": "13.2.1.4:50" + "checksum": "a8af143fd34f01e8f65e9f888d7d760a3b031967381aee5176d597fde23be59f", + "id": "fls_vyeczg1cjxys", + "link": "inline-assembly.html#fls_vyeczg1cjxys", + "number": "22.1:15" }, { - "checksum": "fd611c50b61fad5a73b3dd0e1310a52ad1ec01c9d148b72a8c7eeef524ba5eb0", - "id": "fls_gRf8F9PIGySt", - "link": "attributes.html#fls_gRf8F9PIGySt", - "number": "13.2.1.4:51" + "checksum": "988c5d9c0ec05e872a195f4cee6b0aa813fa0e0d2ddd4954cb63400dda9c839b", + "id": "fls_h5t153uhzoq3", + "link": "inline-assembly.html#fls_h5t153uhzoq3", + "number": "22.1:16" }, { - "checksum": "8d9df987fc1e3ee77cdeb2bc55f5b6880c9fd1a50967d27e3e311e35006d00c6", - "id": "fls_NNj4H6A9VTR8", - "link": "attributes.html#fls_NNj4H6A9VTR8", - "number": "13.2.1.4:52" + "checksum": "cc0d67b9c4b0380c9a19ee2cc5f245a03585452dc63cab72a429c021d97dc83a", + "id": "fls_jhph577nqds1", + "link": "inline-assembly.html#fls_jhph577nqds1", + "number": "22.1:17" }, { - "checksum": "ef7995c90eb7ac743ab249f3f128110c3326402f81541d8d1d54a811badef3d6", - "id": "fls_qZO82VdU5Iz9", - "link": "attributes.html#fls_qZO82VdU5Iz9", - "number": "13.2.1.4:53" + "checksum": "1c7dc5e2b8e5778cfaca9e65467b897d26825ba493e2eb3cec5435253680cdfb", + "id": "fls_mobj1y67vxvb", + "link": "inline-assembly.html#fls_mobj1y67vxvb", + "number": "22.1:18" }, { - "checksum": "dd2313cfedf44267dbb54d88f3314ca7c7076bfc167b691f549028e3e4965bef", - "id": "fls_rilqwazchfpp", - "link": "attributes.html#fls_rilqwazchfpp", - "number": "13.2.1.4:54" + "checksum": "1cbe768ef7b25e59f62f8866acbe1cdbfc5a93786922d54a7ef4a8fb4496c6a0", + "id": "fls_9ke412je1hqn", + "link": "inline-assembly.html#fls_9ke412je1hqn", + "number": "22.1:19" }, { - "checksum": "c66a6be45c57e16772d12a0c52d3d765424f9d564f839da0cb77fc8c01347d4f", - "id": "fls_f0of1395z9pn", - "link": "attributes.html#fls_f0of1395z9pn", - "number": "13.2.1.4:55" + "checksum": "2950a79b4c2b514e58a5e7111b5028fd5c547bfac4ea6877f6a9c0caa7e705ef", + "id": "fls_hndlas58937e", + "link": "inline-assembly.html#fls_hndlas58937e", + "number": "22.1:20" }, { - "checksum": "2cc4b06fbda49374ef17f4974acd16d167b72b9b46c9b13f70587ab9a098587c", - "id": "fls_v9gf6selc17l", - "link": "attributes.html#fls_v9gf6selc17l", - "number": "13.2.1.4:56" + "checksum": "550d7fbd6817adc4a359813352ec379bf58efa791fd796c493eeaa84183afeda", + "id": "fls_5x0yvjil3z8p", + "link": "inline-assembly.html#fls_5x0yvjil3z8p", + "number": "22.1:21" }, { - "checksum": "55fa6aaa332a37320f9c7223150099eba092430986498c5ae328de57fa2e9487", - "id": "fls_jyb5s2r8w1po", - "link": "attributes.html#fls_jyb5s2r8w1po", - "number": "13.2.1.4:57" + "checksum": "e940f5a23bca352679d2dc94ddb731f3a7a16e94572cc09fb942ecfe75186792", + "id": "fls_gxvlvnqs436h", + "link": "inline-assembly.html#fls_gxvlvnqs436h", + "number": "22.1:22" }, { - "checksum": "6743b7e3fdc6f0ff88d22af6ed0826b488e6d15d91849cf2f44fe00b8824ed1c", - "id": "fls_xx51fjkbgg5g", - "link": "attributes.html#fls_xx51fjkbgg5g", - "number": "13.2.1.4:58" - } - ], - "title": "Attribute target_feature" - }, - { - "id": "fls_6qj249hphj1s", - "informational": false, - "link": "attributes.html#attribute-track-caller", - "number": "13.2.1.5", - "paragraphs": [ - { - "checksum": "e23f26c1bdaac35fb187af2c3437a156c142109266b6922552c87a35c4fd7652", - "id": "fls_h8yepgchjxv9", - "link": "attributes.html#fls_h8yepgchjxv9", - "number": "13.2.1.5:1" + "checksum": "2910e2dc873a5bfe39300faefa630bf1d91844c07f36271ac590d8320e12b73d", + "id": "fls_mra7zuu7uzmb", + "link": "inline-assembly.html#fls_mra7zuu7uzmb", + "number": "22.1:23" }, { - "checksum": "be22b61cc84fba1c898524be4f927f7ae8b8aec54323ad08f5b3a0d7ae1c6ff9", - "id": "fls_w1pxtzp7acty", - "link": "attributes.html#fls_w1pxtzp7acty", - "number": "13.2.1.5:2" + "checksum": "5d80d07626a153308f8addc2b5e9471836ba12ce1bdb076f4af716b9a4e18c2c", + "id": "fls_maa7w0jwvat2", + "link": "inline-assembly.html#fls_maa7w0jwvat2", + "number": "22.1:24" }, { - "checksum": "778290a828a1c18436fe29f9d858726e4e341c74a14f532e55779389a4733012", - "id": "fls_zch43jpetmdu", - "link": "attributes.html#fls_zch43jpetmdu", - "number": "13.2.1.5:3" + "checksum": "c65f4c6b8db4f51f6eaf6ddacab1fdb08f9479f2b15b0b5f882da8c81c84054f", + "id": "fls_az7kcaq70h4d", + "link": "inline-assembly.html#fls_az7kcaq70h4d", + "number": "22.1:25" }, { - "checksum": "cf4ab277047420d2b26238c76cfc2a3cc9ba2c6e60092568995ceba4e201831f", - "id": "fls_y1e258p4rby5", - "link": "attributes.html#fls_y1e258p4rby5", - "number": "13.2.1.5:4" + "checksum": "57205b0e3779a9c0f99384a2e74b444075d08f48e6ca14466b92c6288be50961", + "id": "fls_xudmsflrhvo3", + "link": "inline-assembly.html#fls_xudmsflrhvo3", + "number": "22.1:26" }, { - "checksum": "ef4fb5367f8b8eec661ad431d7645eeb45c5a5691ee03447d7b267f66d6b2bc7", - "id": "fls_vkz8t751gfhk", - "link": "attributes.html#fls_vkz8t751gfhk", - "number": "13.2.1.5:5" + "checksum": "68737b33213261aa2bc36b9d8e4d651c615017faf9304e654e7db3705b897e44", + "id": "fls_px77cr1k8coy", + "link": "inline-assembly.html#fls_px77cr1k8coy", + "number": "22.1:27" }, { - "checksum": "5b17e06e43d9f47282dbe761e0e463c0d2b276faa4045afc0ab58815012d19db", - "id": "fls_ddg0u5lej74x", - "link": "attributes.html#fls_ddg0u5lej74x", - "number": "13.2.1.5:6" - } - ], - "title": "Attribute track_caller" - }, - { - "id": "fls_sd6rumpeb355", - "informational": false, - "link": "attributes.html#attribute-naked", - "number": "13.2.1.6", - "paragraphs": [ + "checksum": "a05b1f6d34aed3cf3fc1a88b0e4ffde453b1c30cc42ba145fc45a0eb33ae7654", + "id": "fls_y1m7tlqk2dv7", + "link": "inline-assembly.html#fls_y1m7tlqk2dv7", + "number": "22.1:28" + }, { - "checksum": "137f3fc9833b471ee26f83a1a24deacc325fcc2b16843c9957d541c9c57c71b8", - "id": "fls_8eg0iN2diBZt", - "link": "attributes.html#fls_8eg0iN2diBZt", - "number": "13.2.1.6:1" + "checksum": "7c1cd463725b7565f2460515d804abdd2d46fec45b4ac95a28c0b1ff8e554c5c", + "id": "fls_3dqq9319okv5", + "link": "inline-assembly.html#fls_3dqq9319okv5", + "number": "22.1:29" }, { - "checksum": "9c57c083a646e3281524ef2281d711ff34fe80ce7958c597aec3a475b5811989", - "id": "fls_WnqgR5OyecIs", - "link": "attributes.html#fls_WnqgR5OyecIs", - "number": "13.2.1.6:2" + "checksum": "2b98ca72c3cc9d2473a8d91c2d66d0d938ba4fb430280f13c5a0d23e30124b4a", + "id": "fls_5l9qo5c0gek8", + "link": "inline-assembly.html#fls_5l9qo5c0gek8", + "number": "22.1:30" }, { - "checksum": "9d654d742fc65068a0c5b2afad757634e4b17bf84fefa468a0a19536918e7442", - "id": "fls_NCHa4Dc1M6oM", - "link": "attributes.html#fls_NCHa4Dc1M6oM", - "number": "13.2.1.6:3" + "checksum": "4230c88382f71375962eef4624b0fc37fa2ae2ebcaa2c7b401f490fc1fa26ce4", + "id": "fls_1m68zqsxjuyp", + "link": "inline-assembly.html#fls_1m68zqsxjuyp", + "number": "22.1:31" }, { - "checksum": "11a2b6bc669fc7c1e8d10ee30bf62091e5e74cfc7c6e9e66157f2984fc1e1c77", - "id": "fls_H7ccwNk2HGAi", - "link": "attributes.html#fls_H7ccwNk2HGAi", - "number": "13.2.1.6:4" + "checksum": "b481fc8900e3203d5e51d7456b51a12825e1ffc5c307841c43da63a0b09eefb9", + "id": "fls_bck9slu4hsn1", + "link": "inline-assembly.html#fls_bck9slu4hsn1", + "number": "22.1:32" }, { - "checksum": "fc359929be3f5c5328c068832c7788e882355d02aa26a75d7c175abbc22efa2f", - "id": "fls_ocOk47SGiRVb", - "link": "attributes.html#fls_ocOk47SGiRVb", - "number": "13.2.1.6:5" + "checksum": "e2996fb9db79d277b33c4f337dc25fa4d319a5b8869182db47869241a7d6deec", + "id": "fls_3x72q39c8iwt", + "link": "inline-assembly.html#fls_3x72q39c8iwt", + "number": "22.1:33" }, { - "checksum": "9f2adfd5bcfe3283d7338f6cf77b417fddca8cb2689140e031da6ed4b4dc4693", - "id": "fls_VUL2qySBvRqy", - "link": "attributes.html#fls_VUL2qySBvRqy", - "number": "13.2.1.6:6" + "checksum": "e6bbea8fcf8570922fb2dbb84419a008ed40c04e0333e8759a764083b6de6314", + "id": "fls_yfbrla8c801g", + "link": "inline-assembly.html#fls_yfbrla8c801g", + "number": "22.1:34" }, { - "checksum": "bec2e0ffe9307dbf67461ed0d828670c1fe5c056cea089725f327e54b44c0c3a", - "id": "fls_0tYMAQY1V55p", - "link": "attributes.html#fls_0tYMAQY1V55p", - "number": "13.2.1.6:7" + "checksum": "d8849406f6b817683c149ab81aefd6d29b706724c6023b66047d2edba3924f38", + "id": "fls_3nuf1gcldamv", + "link": "inline-assembly.html#fls_3nuf1gcldamv", + "number": "22.1:35" }, { - "checksum": "e1ffbaafd9d77623ddc68ff25c0694ad436de3c2ef9015f1a51f47e71fce913c", - "id": "fls_wGbJrz4OpKKb", - "link": "attributes.html#fls_wGbJrz4OpKKb", - "number": "13.2.1.6:8" - } - ], - "title": "Attribute naked" - }, - { - "id": "fls_cdx9zb1yxcc8", - "informational": false, - "link": "attributes.html#conditional-compilation-attributes", - "number": "13.2.2", - "paragraphs": [], - "title": "Conditional Compilation Attributes" - }, - { - "id": "fls_fymvsy6ig99a", - "informational": false, - "link": "attributes.html#attribute-cfg", - "number": "13.2.2.1", - "paragraphs": [ + "checksum": "a32c2a79d6b620d5519660e13751e5cc22eea8de15c0b390067701e98f30a2a7", + "id": "fls_nquf1uaoezx6", + "link": "inline-assembly.html#fls_nquf1uaoezx6", + "number": "22.1:36" + }, { - "checksum": "8ce58553238726c00673ba1eb2613f145c385fd18764cfb47b1674bfa60a342b", - "id": "fls_xrjp7xw9jutz", - "link": "attributes.html#fls_xrjp7xw9jutz", - "number": "13.2.2.1:1" + "checksum": "c8194fd8db5397f0ac15a419724a3e5891b702d2969fb1bcb372ce18c98f8601", + "id": "fls_91oeyxc75vu5", + "link": "inline-assembly.html#fls_91oeyxc75vu5", + "number": "22.1:37" }, { - "checksum": "58fc80fa15a48bf125434201acfd61890abb0876c32f33ee234150c5a2db53fa", - "id": "fls_l96kyix5xsof", - "link": "attributes.html#fls_l96kyix5xsof", - "number": "13.2.2.1:2" + "checksum": "1fb8568338e94e2bd2cef7b0aa0fb1faadd19bbab344274b1b54a2f404f8e783", + "id": "fls_r5btazdpwqtw", + "link": "inline-assembly.html#fls_r5btazdpwqtw", + "number": "22.1:38" }, { - "checksum": "20bbdb81e00aa2ce6e15572b261930193367b58565a11d3b7df5fe4d2fb92058", - "id": "fls_y1MUhnKCxK6T", - "link": "attributes.html#fls_y1MUhnKCxK6T", - "number": "13.2.2.1:3" + "checksum": "2c4843ee0d6e3e1ad8d3a6bc4c387786a63a146301b093178a14861ed7e86461", + "id": "fls_vpibsan8aful", + "link": "inline-assembly.html#fls_vpibsan8aful", + "number": "22.1:39" }, { - "checksum": "e3e52c127b3a45a28120d4a22599dc479401160fd2ab551667790d586a58ed83", - "id": "fls_tncxxsyutppf", - "link": "attributes.html#fls_tncxxsyutppf", - "number": "13.2.2.1:4" + "checksum": "fdcc738daa05173b5609e74f2b89807360390675b83c3a52323024cf32c4c93a", + "id": "fls_lj6xcaaecokk", + "link": "inline-assembly.html#fls_lj6xcaaecokk", + "number": "22.1:40" }, { - "checksum": "0a8af2d1ed68a8e58507a513800cba27fe171d005323ccf9b1dea50098d45eda", - "id": "fls_Rp73YEE3aFdI", - "link": "attributes.html#fls_Rp73YEE3aFdI", - "number": "13.2.2.1:5" + "checksum": "d7006f1141083f004dce3c7fcfd3553336d9f21e9cddcd91cd327d990f2b43ad", + "id": "fls_bb1qjfin4zjc", + "link": "inline-assembly.html#fls_bb1qjfin4zjc", + "number": "22.1:41" }, { - "checksum": "b1317fa9240a0683317601af9dbd0d64b3bdfb9f8516093a3aa3febe38ef4940", - "id": "fls_m0zxktz168e0", - "link": "attributes.html#fls_m0zxktz168e0", - "number": "13.2.2.1:6" + "checksum": "32c579a792bd4dfc3a9a6d6af1d96694c344082c3e65ab27cea73e72c5b24be5", + "id": "fls_7qj6pxuq2x9e", + "link": "inline-assembly.html#fls_7qj6pxuq2x9e", + "number": "22.1:42" }, { - "checksum": "c678d8736b809e8b047bb8dec6b729baecd22ff0dc7cfca0cea327a1174294dc", - "id": "fls_XsxeOd32JI8x", - "link": "attributes.html#fls_XsxeOd32JI8x", - "number": "13.2.2.1:7" + "checksum": "2ad948609b5dfb44bd6f0f3b491e171298e449c937fe7d727429e71a03180fe6", + "id": "fls_2xkw4nqt1s5a", + "link": "inline-assembly.html#fls_2xkw4nqt1s5a", + "number": "22.1:43" }, { - "checksum": "7b842e2904a1bd499fdf9166a26058c88d4357ca86b54fce379427f3db4f6c87", - "id": "fls_tvsadfy9uibu", - "link": "attributes.html#fls_tvsadfy9uibu", - "number": "13.2.2.1:8" + "checksum": "2db64ea95f20d553dde55920a82cd6514d6071a691fc62a0a2db860ef6134582", + "id": "fls_dpzi4ygox7jw", + "link": "inline-assembly.html#fls_dpzi4ygox7jw", + "number": "22.1:44" }, { - "checksum": "ef3975edc33ff52d4a52d8454218b527f15a9cc09e83f4f9c87dc2283412ca35", - "id": "fls_JWx8vQwl19Fu", - "link": "attributes.html#fls_JWx8vQwl19Fu", - "number": "13.2.2.1:9" + "checksum": "8967b0adae59edc96e1b580347bfc98961cd1a02ef5b17848e4d07b31d0bd2e0", + "id": "fls_yr5ztipvgezk", + "link": "inline-assembly.html#fls_yr5ztipvgezk", + "number": "22.1:45" }, { - "checksum": "c1b07345fa9901b40f4158770a884da21b22244131f2624ce79defce035cb4e0", - "id": "fls_SziFAQsio0ab", - "link": "attributes.html#fls_SziFAQsio0ab", - "number": "13.2.2.1:10" + "checksum": "8acdb975ac984cda5246944ae4bb019bf2a339227c9774d52bc7a0676f46fc87", + "id": "fls_n8ccafjut1yd", + "link": "inline-assembly.html#fls_n8ccafjut1yd", + "number": "22.1:46" }, { - "checksum": "fbaba2bbfc24b35f86a4f7dd04dbe8282ddf984c2d109a9ee52a777dc8b3360f", - "id": "fls_jbl9xyynjo0g", - "link": "attributes.html#fls_jbl9xyynjo0g", - "number": "13.2.2.1:11" - } - ], - "title": "Attribute cfg" - }, - { - "id": "fls_dd9xh3wdjudo", - "informational": false, - "link": "attributes.html#attribute-cfg-attr", - "number": "13.2.2.2", - "paragraphs": [ - { - "checksum": "d4c02f4ddc98da8bafd4fc43fb7cb00dc77d84e6b42cf83332e1d8ab9e29250f", - "id": "fls_r66jhict6rlq", - "link": "attributes.html#fls_r66jhict6rlq", - "number": "13.2.2.2:1" + "checksum": "d044514182338ccadc9d1a05981c817324f604a615020a9507188e023305649a", + "id": "fls_iv23mcgw6l3r", + "link": "inline-assembly.html#fls_iv23mcgw6l3r", + "number": "22.1:47" }, { - "checksum": "58b7d08514f893172d59bfa69761494f4bb62b45412529d75cd3eb80fe2a6e9d", - "id": "fls_rzw12sagm585", - "link": "attributes.html#fls_rzw12sagm585", - "number": "13.2.2.2:2" - } - ], - "title": "Attribute cfg_attr" - }, - { - "id": "fls_wednba84zi3y", - "informational": false, - "link": "attributes.html#derivation-attributes", - "number": "13.2.3", - "paragraphs": [], - "title": "Derivation Attributes" - }, - { - "id": "fls_bqw87nz4qbrb", - "informational": false, - "link": "attributes.html#attribute-automatically-derived", - "number": "13.2.3.1", - "paragraphs": [ + "checksum": "2aab1e6272ff7b563a82b6b37c18bca96b4c87c262d8f2e8b2c9cc0e81c7ec0e", + "id": "fls_yr7bac5k3uk7", + "link": "inline-assembly.html#fls_yr7bac5k3uk7", + "number": "22.1:48" + }, { - "checksum": "921207c7548372ce92fdca679bc3476d41831899b57c4e34b13268b01997e0f8", - "id": "fls_5u1e0lkt0ab1", - "link": "attributes.html#fls_5u1e0lkt0ab1", - "number": "13.2.3.1:1" - } - ], - "title": "Attribute automatically_derived" - }, - { - "id": "fls_r6gj1p4gajnq", - "informational": false, - "link": "attributes.html#attribute-derive", - "number": "13.2.3.2", - "paragraphs": [ + "checksum": "f7bcce04d256c567958ac3c7b038e42b735b6997898d7ff47aac48170c896eee", + "id": "fls_gvb2zcrseqci", + "link": "inline-assembly.html#fls_gvb2zcrseqci", + "number": "22.1:49" + }, { - "checksum": "a1c77a2b2980c21e6e863dd1b876f7c14683644e96cf49df977a847b4e9e5e1a", - "id": "fls_4btm6zwf445a", - "link": "attributes.html#fls_4btm6zwf445a", - "number": "13.2.3.2:1" + "checksum": "3b2f9835bae0171b78f5e494a3aa32451cd8c1785291f2d6d8393e0839e5bf45", + "id": "fls_z1b9nf49nbjh", + "link": "inline-assembly.html#fls_z1b9nf49nbjh", + "number": "22.1:50" }, { - "checksum": "0a92a3c710cbd1738e619ab4ed564919e5913e3252de308e5f6f347d1833cea7", - "id": "fls_pjmbmj2b35y8", - "link": "attributes.html#fls_pjmbmj2b35y8", - "number": "13.2.3.2:2" + "checksum": "cb975dffbdba381c89169413a7000e7dce7ec25ef91c34b0c74b850f3647a2f4", + "id": "fls_etfkcesnrlwt", + "link": "inline-assembly.html#fls_etfkcesnrlwt", + "number": "22.1:51" }, { - "checksum": "01d955a36e6ea356f588c104a043bbb024bd45ea952c638344667bf6abc1fc81", - "id": "fls_xtty2ino4vwc", - "link": "attributes.html#fls_xtty2ino4vwc", - "number": "13.2.3.2:3" - } - ], - "title": "Attribute derive" - }, - { - "id": "fls_t2590yyvclgb", - "informational": true, - "link": "attributes.html#diagnostics-attributes", - "number": "13.2.4", - "paragraphs": [ + "checksum": "1f33ae168aae6dde1001c1413bdefa412cf8161d3e84988f35963972258881d8", + "id": "fls_8sqzva4ibf0i", + "link": "inline-assembly.html#fls_8sqzva4ibf0i", + "number": "22.1:52" + }, { - "checksum": "3211f6c48f79ae56d33f1f9bacd442f463c800f82da1ca3bc76e3afbd75c27ea", - "id": "fls_ghumzt9ybtit", - "link": "attributes.html#fls_ghumzt9ybtit", - "number": "13.2.4:1" - } - ], - "title": "Diagnostics Attributes" - }, - { - "id": "fls_8wcliky2svcs", - "informational": false, - "link": "attributes.html#documentation-attributes", - "number": "13.2.5", - "paragraphs": [], - "title": "Documentation Attributes" - }, - { - "id": "fls_63v1fqedzwfd", - "informational": false, - "link": "attributes.html#attribute-doc", - "number": "13.2.5.1", - "paragraphs": [ + "checksum": "c6aa9d96351da3b4fe364c7887ea1ba84b5643d1d7a3838ea2efb41907b794de", + "id": "fls_8bo9p6mybuzi", + "link": "inline-assembly.html#fls_8bo9p6mybuzi", + "number": "22.1:53" + }, { - "checksum": "b5b9bec9d76d46cbea5f3747e5f7b0132aac36d1c007bca045c1a1e350d88cde", - "id": "fls_1ee9qjcgbwme", - "link": "attributes.html#fls_1ee9qjcgbwme", - "number": "13.2.5.1:1" - } - ], - "title": "Attribute doc" - }, - { - "id": "fls_pgp7ezcc9lh8", - "informational": false, - "link": "attributes.html#foreign-function-interface-attributes", - "number": "13.2.6", - "paragraphs": [], - "title": "Foreign Function Interface Attributes" - }, - { - "id": "fls_sun645voqex6", - "informational": false, - "link": "attributes.html#attribute-crate-name", - "number": "13.2.6.1", - "paragraphs": [ + "checksum": "649109caddec8bfbd71e14d93cd7e1c50c27a223b51ebacfd28ace0d02e1526b", + "id": "fls_dk4z9dracvps", + "link": "inline-assembly.html#fls_dk4z9dracvps", + "number": "22.1:54" + }, { - "checksum": "61437013bccd601dda4663fa8275e8def835508d51f858d4584acb2816792d24", - "id": "fls_tsdk8jyajcg", - "link": "attributes.html#fls_tsdk8jyajcg", - "number": "13.2.6.1:1" + "checksum": "d7a0475661c593f7c0566fc7018842861e7876cde7b82b1866534efc7c2e0443", + "id": "fls_7c7lgvw8gbkb", + "link": "inline-assembly.html#fls_7c7lgvw8gbkb", + "number": "22.1:55" }, { - "checksum": "31e103c274c243fd0657f6c9fb22d4587184086c9c66befda6643f82b2435479", - "id": "fls_6riphqysh0gd", - "link": "attributes.html#fls_6riphqysh0gd", - "number": "13.2.6.1:2" - } - ], - "title": "Attribute crate_name" - }, - { - "id": "fls_ujig607lmwbm", - "informational": false, - "link": "attributes.html#attribute-crate-type", - "number": "13.2.6.2", - "paragraphs": [ + "checksum": "03796b443d5b6bee6e75a9e91f79f885c5c7a9c39ba82beb8257fc4d9bb65116", + "id": "fls_sb0ffx31gul9", + "link": "inline-assembly.html#fls_sb0ffx31gul9", + "number": "22.1:56" + }, { - "checksum": "54779685564cfbdb9ff97ae91287f885632bf1e75e9e44af0e3115fe73cdb3e6", - "id": "fls_2i2g55nqqpc1", - "link": "attributes.html#fls_2i2g55nqqpc1", - "number": "13.2.6.2:1" + "checksum": "d3cdc1dbdbdaf3bf51d1c1d3e15c1fa5aef040e28178566c5cb0d654ebcab926", + "id": "fls_f9psgjl6ervm", + "link": "inline-assembly.html#fls_f9psgjl6ervm", + "number": "22.1:57" }, { - "checksum": "05939963a9001f26e930c1bf9bad738b146dad4bd95c5cbee3e0ef2503896e7b", - "id": "fls_1zziddjuzjeq", - "link": "attributes.html#fls_1zziddjuzjeq", - "number": "13.2.6.2:2" - } - ], - "title": "Attribute crate_type" - }, - { - "id": "fls_olzilmy8n0nl", - "informational": false, - "link": "attributes.html#attribute-export-name", - "number": "13.2.6.3", - "paragraphs": [ + "checksum": "c72b9ed4c401c89d36486c8d501951d76d0d37f6c2e0e8b06a1d4e20c2431428", + "id": "fls_3y3b7znxznzu", + "link": "inline-assembly.html#fls_3y3b7znxznzu", + "number": "22.1:58" + }, { - "checksum": "fa8489539e2aa3b8aa9a16e4531a9c0f7e082581000b26430071b5fe8bddd791", - "id": "fls_r3fwpuuaoeie", - "link": "attributes.html#fls_r3fwpuuaoeie", - "number": "13.2.6.3:1" + "checksum": "81ecd8fe1fe33b56420762ca519c95224c123ed4439893049875d345aba5c512", + "id": "fls_7y201ev5b0mq", + "link": "inline-assembly.html#fls_7y201ev5b0mq", + "number": "22.1:59" }, { - "checksum": "35ac008e660d6499f218d0c11b1c822a4fda466a0469f3424a0c9aa028f72ea6", - "id": "fls_spwr6gf7kpds", - "link": "attributes.html#fls_spwr6gf7kpds", - "number": "13.2.6.3:2" - } - ], - "title": "Attribute export_name" - }, - { - "id": "fls_o0f9ae22ug1x", - "informational": false, - "link": "attributes.html#attribute-link", - "number": "13.2.6.4", - "paragraphs": [ + "checksum": "a44bc0de8a6ee0c018ae873e38b231a58ebb326951a3fd5fe21546388c740593", + "id": "fls_r2aoetnwhjkf", + "link": "inline-assembly.html#fls_r2aoetnwhjkf", + "number": "22.1:60" + }, { - "checksum": "8f57b8472dff4cd91f355578e5c3bfa9096c22950b9766df341e266371a1a946", - "id": "fls_yslpkdngo8hj", - "link": "attributes.html#fls_yslpkdngo8hj", - "number": "13.2.6.4:1" + "checksum": "bc842bb6d2622c3a029ef4614723d21fd262ddf085253092dd84e1a420f77ca7", + "id": "fls_54n4ie3frc39", + "link": "inline-assembly.html#fls_54n4ie3frc39", + "number": "22.1:61" }, { - "checksum": "6eb5a07a373ecc60dd23324335a8b659caf2269885af5c3585f4df1c13ca727c", - "id": "fls_6rohnk4swj6c", - "link": "attributes.html#fls_6rohnk4swj6c", - "number": "13.2.6.4:2" + "checksum": "7a946b3318f99a6748a231b8c03c16781c4292e0fcbbc4ac706a962861f317b5", + "id": "fls_iok8gc906tc8", + "link": "inline-assembly.html#fls_iok8gc906tc8", + "number": "22.1:62" }, { - "checksum": "d9db9f413583821ddb835f34a03fd02f2a1d38f6eab189bb99008475e2400f3f", - "id": "fls_o83pf3bcrzma", - "link": "attributes.html#fls_o83pf3bcrzma", - "number": "13.2.6.4:3" + "checksum": "770cd7d9c31bbb765ad40be40ea824f38ba9e412b3e507365f9c6a754c17551e", + "id": "fls_2mgjkyngbmbr", + "link": "inline-assembly.html#fls_2mgjkyngbmbr", + "number": "22.1:63" }, { - "checksum": "0353cd50793fff24e61d961587929ddeeb5a7f46853756fa4e2d70319d7500a6", - "id": "fls_5541q1qoxdpf", - "link": "attributes.html#fls_5541q1qoxdpf", - "number": "13.2.6.4:4" + "checksum": "5c59322286016f2c772ca739ba5233cd7e28720d91ce9189183e1852db7b095a", + "id": "fls_6tlnpguf23y3", + "link": "inline-assembly.html#fls_6tlnpguf23y3", + "number": "22.1:64" }, { - "checksum": "eef115065e6c96a05b7ae0eb410b3b05418ce07352bb23ed9baaf23d7f5d5450", - "id": "fls_wpqawdpevkj6", - "link": "attributes.html#fls_wpqawdpevkj6", - "number": "13.2.6.4:5" + "checksum": "c7750120307aedb80af8ae7313dd6345535fd86e2c63679b6d61344356d21904", + "id": "fls_bz5xyv89i5m7", + "link": "inline-assembly.html#fls_bz5xyv89i5m7", + "number": "22.1:65" }, { - "checksum": "60ae8311dc7a096a8d78ddf2ae480d5cd77d966a20facb0ce94dcea8d1eab84e", - "id": "fls_HPl6poXGiAqV", - "link": "attributes.html#fls_HPl6poXGiAqV", - "number": "13.2.6.4:6" + "checksum": "18febf551f1317bbf8aa6f68c01133df1bacd637fbb0500335b6725a840eb292", + "id": "fls_soitzjqze3rf", + "link": "inline-assembly.html#fls_soitzjqze3rf", + "number": "22.1:66" }, { - "checksum": "b0db32db9b79b0e6ad02182b8befad2a9aa177097a1fa014a59781f5686d36a2", - "id": "fls_h9dfs6kzmobp", - "link": "attributes.html#fls_h9dfs6kzmobp", - "number": "13.2.6.4:7" + "checksum": "5b49f664f474f3d6edcb8801c7593e791e1d1ad1349e8d219df81fa95929c9a4", + "id": "fls_b2c02r5y6zm9", + "link": "inline-assembly.html#fls_b2c02r5y6zm9", + "number": "22.1:67" }, { - "checksum": "ae7e1e29c04ecbee336f1381e8308022f3ad0ccb61bd5c5c7c353b7eea560997", - "id": "fls_3a3r4jf7hzqr", - "link": "attributes.html#fls_3a3r4jf7hzqr", - "number": "13.2.6.4:8" + "checksum": "641caa94a7b88594686c64a9e6c37cd6597bc61fb18259f047eb9c3a31ae1428", + "id": "fls_2ufm8y5ttcxf", + "link": "inline-assembly.html#fls_2ufm8y5ttcxf", + "number": "22.1:68" }, { - "checksum": "4ea15c08520b2c2fb958beab6ad0fc324e6aa088b62d5fb1e6931d8d6507bc5d", - "id": "fls_1tdheukgm6ai", - "link": "attributes.html#fls_1tdheukgm6ai", - "number": "13.2.6.4:9" + "checksum": "d1c20d9b16467ccff234ee51ec9f7445d1242be7328a3b7fc74105e82aa6848f", + "id": "fls_ub321ic94bbl", + "link": "inline-assembly.html#fls_ub321ic94bbl", + "number": "22.1:69" }, { - "checksum": "7fb19964f38021c6916eb50ab245d90c1f006905de59ccc42d0d0f3d1235641f", - "id": "fls_3i9ijypnh8nx", - "link": "attributes.html#fls_3i9ijypnh8nx", - "number": "13.2.6.4:10" + "checksum": "8e992777ce18f719a5a9f6087fe030f71520760bfe4215255991b93ae2a6775a", + "id": "fls_gtwmwsmyrdxe", + "link": "inline-assembly.html#fls_gtwmwsmyrdxe", + "number": "22.1:70" }, { - "checksum": "e9be1b611b17fb0d613033d31855e2094fd26b9bbc43a9b80f8182da2e833906", - "id": "fls_iDjcQczFQknm", - "link": "attributes.html#fls_iDjcQczFQknm", - "number": "13.2.6.4:11" + "checksum": "22204cedd5304e36d4991c6ec1da6276140e3d0d2decbed86fe6c623c9a4cd8b", + "id": "fls_vy8alu9yuza9", + "link": "inline-assembly.html#fls_vy8alu9yuza9", + "number": "22.1:71" }, { - "checksum": "fdf51bc6996e603cffe22c0b534103b20e91952afdf7739053ad88ccbdc56f61", - "id": "fls_rcERq1PfKPJI", - "link": "attributes.html#fls_rcERq1PfKPJI", - "number": "13.2.6.4:12" + "checksum": "d8eeb48a603b537546fcc36fa3e17ccba43d166b30fd0d2792edc9e29f75d4ba", + "id": "fls_zEtLZ5KjQcHS", + "link": "inline-assembly.html#fls_zEtLZ5KjQcHS", + "number": "22.1:72" } ], - "title": "Attribute link" + "title": "Registers" }, { - "id": "fls_p44fky7fifc", + "id": "fls_pz2ioqchjtym", "informational": false, - "link": "attributes.html#attribute-link-name", - "number": "13.2.6.5", + "link": "inline-assembly.html#register-classes", + "number": "22.2", "paragraphs": [ { - "checksum": "6321fe23e29ea0ad41b3c85bedafbf9ef00aee8ccef4be33847faf98c00dcc83", - "id": "fls_g09jhukl0ez2", - "link": "attributes.html#fls_g09jhukl0ez2", - "number": "13.2.6.5:1" + "checksum": "bca76cef1fe93380b4b2764a1780560ecfc7d399d0133d974aa8b4272185398c", + "id": "fls_7gxb7ztpuofj", + "link": "inline-assembly.html#fls_7gxb7ztpuofj", + "number": "22.2:1" }, { - "checksum": "089ca2eda0df3b0c76c2d4a1e66e7b57615201c37cb7791d3217d9bf7b581da9", - "id": "fls_d00wni4edi8f", - "link": "attributes.html#fls_d00wni4edi8f", - "number": "13.2.6.5:2" + "checksum": "072e7d6791f5c1d9aca09df3602ec2e87a96ea771b13d9baee44cc7da4745443", + "id": "fls_on0i2cpk254y", + "link": "inline-assembly.html#fls_on0i2cpk254y", + "number": "22.2:2" }, { - "checksum": "0600f1f844b645dd649dcfdebecbdc3af837dcba7089013ae17dfc7ba6295fec", - "id": "fls_0Athv8KFA5FO", - "link": "attributes.html#fls_0Athv8KFA5FO", - "number": "13.2.6.5:3" - } - ], - "title": "Attribute link_name" - }, - { - "id": "fls_hffpo88r61rh", - "informational": false, - "link": "attributes.html#attribute-link-section", - "number": "13.2.6.6", - "paragraphs": [ + "checksum": "f507ddb8a4d78d5f4516c08061e3fd00ce3680761f88d8533d74cebf1747d8e5", + "id": "fls_40ksem5g5xx9", + "link": "inline-assembly.html#fls_40ksem5g5xx9", + "number": "22.2:3" + }, { - "checksum": "10a4631db6c550ee4410b3605bb85beb0c3c0dc21dfefb51ce2f0d1b3e42343b", - "id": "fls_5loqzajiz34m", - "link": "attributes.html#fls_5loqzajiz34m", - "number": "13.2.6.6:1" + "checksum": "4f9cf8829042709ab419bcfdac8f32f55808e17f6f72d0a868bc08b92f89d719", + "id": "fls_fqvjk6caipq", + "link": "inline-assembly.html#fls_fqvjk6caipq", + "number": "22.2:4" }, { - "checksum": "fdb84e96608c3986056b7d198e88ed046825e6c72eff37d9b7ad5d107635d528", - "id": "fls_cyxk12wuicml", - "link": "attributes.html#fls_cyxk12wuicml", - "number": "13.2.6.6:2" - } - ], - "title": "Attribute link_section" - }, - { - "id": "fls_obik2w9gvhln", - "informational": false, - "link": "attributes.html#attribute-link-ordinal", - "number": "13.2.6.7", - "paragraphs": [ + "checksum": "a745f47cfa17415d3efa516e8a511b98dc10024c933e4b234e51839ab8b2c4bf", + "id": "fls_u0ie66ep3glg", + "link": "inline-assembly.html#fls_u0ie66ep3glg", + "number": "22.2:5" + }, { - "checksum": "b9f69058d6cf44ac40747cbd7dd38c8689c1de672c433b6fd76c37a59413c0a8", - "id": "fls_fuv29BIqcawW", - "link": "attributes.html#fls_fuv29BIqcawW", - "number": "13.2.6.7:1" + "checksum": "834825290b52066ecedfd94cfeaaa564b134590cadb34cc3d93faffcfa68982a", + "id": "fls_wcvcansd88je", + "link": "inline-assembly.html#fls_wcvcansd88je", + "number": "22.2:6" }, { - "checksum": "bae500fd492b5390f081b17a3c19a753b385115b83d2678de450e7fc08802073", - "id": "fls_qh5sXG4znAXa", - "link": "attributes.html#fls_qh5sXG4znAXa", - "number": "13.2.6.7:2" + "checksum": "19a597e10c09d8cd5266148bbaa227ca26d47e590e8cbe4a3a0b4a3b7b65ebf9", + "id": "fls_sbllpky4d7ka", + "link": "inline-assembly.html#fls_sbllpky4d7ka", + "number": "22.2:7" }, { - "checksum": "1c20d78c53465204598d099ba8c270f12e905456c6b799086d88a295046e6b7f", - "id": "fls_ASnJJiXWGlKI", - "link": "attributes.html#fls_ASnJJiXWGlKI", - "number": "13.2.6.7:3" - } - ], - "title": "Attribute link_ordinal" - }, - { - "id": "fls_ch9nkxkloozv", - "informational": false, - "link": "attributes.html#attribute-no-link", - "number": "13.2.6.8", - "paragraphs": [ + "checksum": "22f409acc30151195193e1ac9bc8011511fad86bb8a83517b941ceba06117903", + "id": "fls_nmx5xs829ms", + "link": "inline-assembly.html#fls_nmx5xs829ms", + "number": "22.2:8" + }, { - "checksum": "b264d167b04d1f62c99717023640f39c72ae2c165f8c8802648308cf461c78db", - "id": "fls_ayhn6g6sgt3h", - "link": "attributes.html#fls_ayhn6g6sgt3h", - "number": "13.2.6.8:1" + "checksum": "e7b421b9d33bd170c0b1cb09c6bf1d3514538ae75bad8431dc915f4629b428fe", + "id": "fls_nars4y8tv2w6", + "link": "inline-assembly.html#fls_nars4y8tv2w6", + "number": "22.2:9" }, { - "checksum": "aad978ad1b9b2134fb756fc3db820f6b58d2638aa0c07cf0f6910e996e04b5df", - "id": "fls_76ox8n3eef5", - "link": "attributes.html#fls_76ox8n3eef5", - "number": "13.2.6.8:2" - } - ], - "title": "Attribute no_link" - }, - { - "id": "fls_fh27ljezn3qz", - "informational": false, - "link": "attributes.html#attribute-no-main", - "number": "13.2.6.9", - "paragraphs": [ + "checksum": "6b05bd0ba53bef61a1eb498458f8f387e92b3a84409fe2949e603bde1d9a915d", + "id": "fls_b5juxguclqjs", + "link": "inline-assembly.html#fls_b5juxguclqjs", + "number": "22.2:10" + }, { - "checksum": "515473b5a7edc71b0872a2f78ed4aac163ef1b1fd6716ca8b8a8a87717e85105", - "id": "fls_84a9k0fzmnfk", - "link": "attributes.html#fls_84a9k0fzmnfk", - "number": "13.2.6.9:1" + "checksum": "aea699b2678adf58e4f4bf56b5b412b83f1ded902ad91eb6d363bfc48e11ecb6", + "id": "fls_vmigixoxm5uf", + "link": "inline-assembly.html#fls_vmigixoxm5uf", + "number": "22.2:11" }, { - "checksum": "861df3866ad296473e5cbbc9995aa29386bc3d4af7b1a33a57de0862d30fd94f", - "id": "fls_6qig3s3qpj0i", - "link": "attributes.html#fls_6qig3s3qpj0i", - "number": "13.2.6.9:2" - } - ], - "title": "Attribute no_main" - }, - { - "id": "fls_mvd7nz8k3wcy", - "informational": false, - "link": "attributes.html#attribute-no-mangle", - "number": "13.2.6.10", - "paragraphs": [ + "checksum": "6481ad4cc845d4e7ffc277d6a32513834168222c01697bb90ede7f041f4b3ab3", + "id": "fls_vdqtbc4t69v2", + "link": "inline-assembly.html#fls_vdqtbc4t69v2", + "number": "22.2:12" + }, { - "checksum": "d11ddb1c212025856ed6074fd11d79b465db9c57c2818f14597a4a2b51ef0028", - "id": "fls_q5swm5meafmx", - "link": "attributes.html#fls_q5swm5meafmx", - "number": "13.2.6.10:1" + "checksum": "a2eef70002f2666dc49f2def9707cdfa752018ddd635ddf2f70e7086f1782b72", + "id": "fls_t2d77dazjyjo", + "link": "inline-assembly.html#fls_t2d77dazjyjo", + "number": "22.2:13" }, { - "checksum": "68ed1d8594d8c5b565c9c3ba1dad3e246f93c1b2b85a0620b07350282ecfb68d", - "id": "fls_esaew4fqk8mm", - "link": "attributes.html#fls_esaew4fqk8mm", - "number": "13.2.6.10:2" + "checksum": "ac9efee8bc991667f505b75c53a5d2409171184521200c1315775ac64f7bfc7c", + "id": "fls_jh02uk3ypett", + "link": "inline-assembly.html#fls_jh02uk3ypett", + "number": "22.2:14" }, { - "checksum": "9865aff343470dc6f503617f53b6b0518ff23e429de527be62858ede954a50ad", - "id": "fls_lvnclpxbye9u", - "link": "attributes.html#fls_lvnclpxbye9u", - "number": "13.2.6.10:3" + "checksum": "0eaa7e064279eea86a9d365b6c251eb9d9b53356bcf44f642cefe63992aa99b6", + "id": "fls_rjk5laiyqagy", + "link": "inline-assembly.html#fls_rjk5laiyqagy", + "number": "22.2:15" }, { - "checksum": "41b882e78b95a2fd3d2d109426bb31a61f34606a58d041b2592a662c22c5305b", - "id": "fls_VKuSiswPMll7", - "link": "attributes.html#fls_VKuSiswPMll7", - "number": "13.2.6.10:4" + "checksum": "ae959551a354c13f23e569b07e21962fd29e91edad42a1637c61c47ffbba3178", + "id": "fls_26bq6wbwznx", + "link": "inline-assembly.html#fls_26bq6wbwznx", + "number": "22.2:16" }, { - "checksum": "7a8578b15d438d33bfe5ea2e9b8441da5cd13f514c196d3711fda2f876d4e701", - "id": "fls_I029Rvr5BX5P", - "link": "attributes.html#fls_I029Rvr5BX5P", - "number": "13.2.6.10:5" - } - ], - "title": "Attribute no_mangle" - }, - { - "id": "fls_aibb2quva4mn", - "informational": false, - "link": "attributes.html#attribute-repr", - "number": "13.2.6.11", - "paragraphs": [ + "checksum": "0e6100d8c3a6f0652c904a5a5da3ac3fdbc07c732bcb94f9ced925a3a67ea4df", + "id": "fls_6d25i0lkzd7u", + "link": "inline-assembly.html#fls_6d25i0lkzd7u", + "number": "22.2:17" + }, { - "checksum": "10eda6760b60f8b3456de8229a6557a58fb131d2fbbdb50e40f210219814c453", - "id": "fls_vetjq9sw84qc", - "link": "attributes.html#fls_vetjq9sw84qc", - "number": "13.2.6.11:1" + "checksum": "c763cce07ab00dfd3323500717b1eed3e55412bb79178870af3708ab08e16908", + "id": "fls_y52suhleyid2", + "link": "inline-assembly.html#fls_y52suhleyid2", + "number": "22.2:18" }, { - "checksum": "0a18a601c320e54cccbb64a16e0ff23f67f5a8c9ee5e3fced2951001ef7cfd88", - "id": "fls_is2esjz1sy36", - "link": "attributes.html#fls_is2esjz1sy36", - "number": "13.2.6.11:2" - } - ], - "title": "Attribute repr" - }, - { - "id": "fls_19lnti3wabfd", - "informational": false, - "link": "attributes.html#attribute-unsafe", - "number": "13.2.6.12", - "paragraphs": [ + "checksum": "96cd932df092222c77d3bcb7d31fad0c2be26093f163662b926fb5cc7acf7f83", + "id": "fls_7b7c8xtm8fr7", + "link": "inline-assembly.html#fls_7b7c8xtm8fr7", + "number": "22.2:19" + }, { - "checksum": "8342623b9650ffe1d4caaa6ba0d2c04837aecf96d6d8fa91600844a511c2ffe5", - "id": "fls_z5DTDKM9mU3o", - "link": "attributes.html#fls_z5DTDKM9mU3o", - "number": "13.2.6.12:1" + "checksum": "e0ac82fc15ff715d9cddb3bdc897151a9a4cf2819d813ac6da7a07d48fd07ad0", + "id": "fls_ue0se3dcop6w", + "link": "inline-assembly.html#fls_ue0se3dcop6w", + "number": "22.2:20" }, { - "checksum": "7bb763e6b566d916330764f843b5092bda30af6db45395cc44ef7efbe9fecf63", - "id": "fls_5pjo3nGOxbVw", - "link": "attributes.html#fls_5pjo3nGOxbVw", - "number": "13.2.6.12:2" + "checksum": "9a47e3602b2201c92e0dce3aa02e6d20feae619aec5f1e0369434cb2af047e9e", + "id": "fls_2m49cyfqffvo", + "link": "inline-assembly.html#fls_2m49cyfqffvo", + "number": "22.2:21" }, { - "checksum": "a927fa1655a602138331609181e3a8ce24c846c5dab93681a15c212346e2f64b", - "id": "fls_gpxlWbQUNsj8", - "link": "attributes.html#fls_gpxlWbQUNsj8", - "number": "13.2.6.12:3" + "checksum": "fba730a9b194b9c95c6372bcd456c87e55aaa5d71933a5ffbf3afbc6ce1bdaf8", + "id": "fls_mj1t0f9lp6v8", + "link": "inline-assembly.html#fls_mj1t0f9lp6v8", + "number": "22.2:22" }, { - "checksum": "287b25e9a268f4b0147d1c69c4e4d637601a42133ab7056edc4e33917f3a07d6", - "id": "fls_PhrfxSBvXTPV", - "link": "attributes.html#fls_PhrfxSBvXTPV", - "number": "13.2.6.12:4" + "checksum": "de2789ffcd9d02a9bbc9c39b7b94cd196bfd6f0167acfd400d37b10d6188efc3", + "id": "fls_tpkkubhjt7lk", + "link": "inline-assembly.html#fls_tpkkubhjt7lk", + "number": "22.2:23" }, { - "checksum": "a267d2d8dc5df8de8cecf7df5fe42a741b7e0fff7a559232e3c33dfc5398c3e6", - "id": "fls_pCSew95bKJJ5", - "link": "attributes.html#fls_pCSew95bKJJ5", - "number": "13.2.6.12:5" + "checksum": "2eabf1ca886e5459326f2967d018156022a1148d68a4f0cad387ba9857bdbd77", + "id": "fls_ivq874v4lmga", + "link": "inline-assembly.html#fls_ivq874v4lmga", + "number": "22.2:24" }, { - "checksum": "38376757927eaf5909477f2dca05aa7fb506242593d5e386b266d6ee5137b289", - "id": "fls_NRLgmOXxuljQ", - "link": "attributes.html#fls_NRLgmOXxuljQ", - "number": "13.2.6.12:6" + "checksum": "5f20a84f69b2f4df1e103ed21420855eb0edd88111d65c915c9323f4e86af1d4", + "id": "fls_2wdcrocczwyi", + "link": "inline-assembly.html#fls_2wdcrocczwyi", + "number": "22.2:25" }, { - "checksum": "e65b61832ae1e972d420b56ea27d68dea076d20d6d2dc5f66b662790de5d45b5", - "id": "fls_ykpxNByUDyHG", - "link": "attributes.html#fls_ykpxNByUDyHG", - "number": "13.2.6.12:7" + "checksum": "f98c5961068bd12ec95c8e6cc0e0981321c82dd46936f9674f1da53e1f4da5d9", + "id": "fls_v04te7p28dth", + "link": "inline-assembly.html#fls_v04te7p28dth", + "number": "22.2:26" }, { - "checksum": "bbacacadea0e15e11df2b03e93522e4dc19b9d42d1ebe55281c843437893bfca", - "id": "fls_2oP2nbDPtUg7", - "link": "attributes.html#fls_2oP2nbDPtUg7", - "number": "13.2.6.12:8" - } - ], - "title": "Attribute unsafe" - }, - { - "id": "fls_7skf24auayqy", - "informational": false, - "link": "attributes.html#attribute-used", - "number": "13.2.6.13", - "paragraphs": [ + "checksum": "68b2c581d553c7dc02a6bcb848acc9c08aaa51f9dfd03525f402337590b1c00f", + "id": "fls_uypct69j2h6a", + "link": "inline-assembly.html#fls_uypct69j2h6a", + "number": "22.2:27" + }, { - "checksum": "15b90a46b8d8e960e0c5c083b1de7c180ee05c88e6844587b34798d179ad1ee6", - "id": "fls_s4ii078wgpk", - "link": "attributes.html#fls_s4ii078wgpk", - "number": "13.2.6.13:1" + "checksum": "80f90c8b08776edc6b8d2896a8d06cd39c50126b7b0a36a79b50b336f5e3b3cd", + "id": "fls_gyet9huf6nr", + "link": "inline-assembly.html#fls_gyet9huf6nr", + "number": "22.2:28" }, { - "checksum": "34849743163e54f2380ebe33f0c44562d6903c45fbcb2d17d9b42cde2c5d27f4", - "id": "fls_k293nzcffks4", - "link": "attributes.html#fls_k293nzcffks4", - "number": "13.2.6.13:2" - } - ], - "title": "Attribute used" - }, - { - "id": "fls_cjq792yj6vft", - "informational": false, - "link": "attributes.html#limits-attributes", - "number": "13.2.7", - "paragraphs": [], - "title": "Limits Attributes" - }, - { - "id": "fls_u2hzlzpzh7yy", - "informational": false, - "link": "attributes.html#attribute-recursion-limit", - "number": "13.2.7.1", - "paragraphs": [ + "checksum": "fd2227497b01180261f81adb03ea6a85c78ad4ed2fef19428a5722d64a04012c", + "id": "fls_5ekbq9hacho9", + "link": "inline-assembly.html#fls_5ekbq9hacho9", + "number": "22.2:29" + }, { - "checksum": "e029467a0536bae96ae25a73cd801fbfc9c8874c35c9cef6110fda5cb0ae9916", - "id": "fls_o55cxc67sya7", - "link": "attributes.html#fls_o55cxc67sya7", - "number": "13.2.7.1:1" + "checksum": "22aada8b4f1f9e603a61146c28eb2887803435066842ef0118a7f1917ebeb814", + "id": "fls_furnyxmwqn09", + "link": "inline-assembly.html#fls_furnyxmwqn09", + "number": "22.2:30" }, { - "checksum": "181e8dbd1bc511f8b6a2fd59595070b2e85422b0c0b2fa446b31cf0408650ba2", - "id": "fls_o9p8fa8zhe15", - "link": "attributes.html#fls_o9p8fa8zhe15", - "number": "13.2.7.1:2" - } - ], - "title": "Attribute recursion_limit" - }, - { - "id": "fls_tdjjuwbr7mkg", - "informational": false, - "link": "attributes.html#attribute-type-length-limit", - "number": "13.2.7.2", - "paragraphs": [ + "checksum": "38657e66bcc21e14e87aa5238007bcac36dce73156c9c5af823e904c25658fc4", + "id": "fls_1c4ts991vkpq", + "link": "inline-assembly.html#fls_1c4ts991vkpq", + "number": "22.2:31" + }, { - "checksum": "4a50bf4d79c29ee4da73fa4b82e49dbde7e5bb057e6709e84b888a7458ea5356", - "id": "fls_dfnkzj8ob3uq", - "link": "attributes.html#fls_dfnkzj8ob3uq", - "number": "13.2.7.2:1" + "checksum": "f12f6d554c25c5f85af01ebeffe90a992373ef8a69195825942db06fb5c04ea8", + "id": "fls_iwnb72jb9iwj", + "link": "inline-assembly.html#fls_iwnb72jb9iwj", + "number": "22.2:32" }, { - "checksum": "c1cc259e5bd84ef7201ecf3eea15ae58c6db27ab49f9b20cca614c7397dc0cd0", - "id": "fls_61vt1r8g51nh", - "link": "attributes.html#fls_61vt1r8g51nh", - "number": "13.2.7.2:2" - } - ], - "title": "Attribute type_length_limit" - }, - { - "id": "fls_2084b06dr0wz", - "informational": false, - "link": "attributes.html#macros-attributes", - "number": "13.2.8", - "paragraphs": [], - "title": "Macros Attributes" - }, - { - "id": "fls_qyudjghzfyjh", - "informational": false, - "link": "attributes.html#attribute-collapse-debuginfo", - "number": "13.2.8.1", - "paragraphs": [ + "checksum": "15d41d994ebc27fa82da90d54388cdf07ae3e0f9ede2cfeb23369a24746bdeb7", + "id": "fls_mw3axoixjgnq", + "link": "inline-assembly.html#fls_mw3axoixjgnq", + "number": "22.2:33" + }, { - "checksum": "c412ca736f3a1f514ecfa77bc8072cda92f7f2b321b96f0715c267adb8ca8d34", - "id": "fls_EzKHtWHmXMAZ", - "link": "attributes.html#fls_EzKHtWHmXMAZ", - "number": "13.2.8.1:1" + "checksum": "fe29bb239760aa31dc2989d7ef1b1e0c39eb0b86dba45b014c7a1944e999b5e4", + "id": "fls_mnzt6bxhycv9", + "link": "inline-assembly.html#fls_mnzt6bxhycv9", + "number": "22.2:34" }, { - "checksum": "1d0371f26b1d25705d10c9bb99d25dbcf8528ee5d114056d6623a5e2f0292f33", - "id": "fls_BCvJpfQMYEcD", - "link": "attributes.html#fls_BCvJpfQMYEcD", - "number": "13.2.8.1:2" - } - ], - "title": "Attribute collapse_debuginfo" - }, - { - "id": "fls_e0a96eb6ux3y", - "informational": false, - "link": "attributes.html#attribute-macro-export", - "number": "13.2.8.2", - "paragraphs": [ + "checksum": "8ef8f937a7b7b7adb9ce03d888fcf5148a91620cc4af1b10b59ac502f648b5c7", + "id": "fls_drg7v8hxb5ca", + "link": "inline-assembly.html#fls_drg7v8hxb5ca", + "number": "22.2:35" + }, { - "checksum": "b7f82a1f9a690d7f22754bcc39c0c5a7aa96121a1dd60e82b27d66a66723bc28", - "id": "fls_3ma7zkk6john", - "link": "attributes.html#fls_3ma7zkk6john", - "number": "13.2.8.2:1" + "checksum": "a04359b7328e6f0222e6c4296470962b896234b8db289d9a7c215eba9ef0a7db", + "id": "fls_78gb8z1fyluc", + "link": "inline-assembly.html#fls_78gb8z1fyluc", + "number": "22.2:36" }, { - "checksum": "482470d45ab2936cb24974dba4d6dd68a83506c1c8d812b93b1125611ef28893", - "id": "fls_h26iw5wh4lla", - "link": "attributes.html#fls_h26iw5wh4lla", - "number": "13.2.8.2:2" + "checksum": "2c9e81813ba55fc43a7bcbb08d580d2da6ced81202c68a5c37a3be0227a000a7", + "id": "fls_7dii7lee457t", + "link": "inline-assembly.html#fls_7dii7lee457t", + "number": "22.2:37" + }, + { + "checksum": "240ab19a4ecb8be24c7140737462ed6c98ed2adeaae044fc076f6af615f3fa39", + "id": "fls_ujhjocg1361b", + "link": "inline-assembly.html#fls_ujhjocg1361b", + "number": "22.2:38" } ], - "title": "Attribute macro_export" + "title": "Register Classes" }, { - "id": "fls_qxjy0f758x5s", + "id": "fls_hejgghwzblf", "informational": false, - "link": "attributes.html#attribute-macro-use", - "number": "13.2.8.3", + "link": "inline-assembly.html#register-arguments", + "number": "22.3", "paragraphs": [ { - "checksum": "cb9be301d970ad119fddcd5b1aed8838b378d008a99aee3b37680273ef7c2735", - "id": "fls_uua0nthq9id", - "link": "attributes.html#fls_uua0nthq9id", - "number": "13.2.8.3:1" + "checksum": "51ff09ea4d125dfa2dddd25429197d68e0ab90254402dd2d93ea531c65a622a0", + "id": "fls_455dmnp4cxqv", + "link": "inline-assembly.html#fls_455dmnp4cxqv", + "number": "22.3:1" }, { - "checksum": "c57f6b3b535491797fe56ebb3d700049dd61c9daed4faeabf4b81ff930713d72", - "id": "fls_oq4kyo5z5tj5", - "link": "attributes.html#fls_oq4kyo5z5tj5", - "number": "13.2.8.3:2" + "checksum": "fa26d928624b04e379be24a2fc2001a49d414a019eebebf261e5a6c5951a3532", + "id": "fls_6bv3s8be5xif", + "link": "inline-assembly.html#fls_6bv3s8be5xif", + "number": "22.3:2" }, { - "checksum": "0f7a33e20540faa9a9f1f692378ac792ba6bdad3b1cae65e1aa6f5f1d46f15ad", - "id": "fls_skexvtpbjknn", - "link": "attributes.html#fls_skexvtpbjknn", - "number": "13.2.8.3:3" + "checksum": "c48eae85f32cfba112d97ce5307d7272d0911f9df4e67ba250f055efc0a0be0b", + "id": "fls_uddjvkz4g899", + "link": "inline-assembly.html#fls_uddjvkz4g899", + "number": "22.3:3" }, { - "checksum": "1f21807e410d91d0bdf2a148125d253f2e11d2ef02a10360f295161deb2db79d", - "id": "fls_v03924dr0u0z", - "link": "attributes.html#fls_v03924dr0u0z", - "number": "13.2.8.3:4" + "checksum": "de9b4bea7caed53ee7b0afc9fdfb7ce959dbe46af01b3c32dcdaca669dd8a0c0", + "id": "fls_sqs5to20p0te", + "link": "inline-assembly.html#fls_sqs5to20p0te", + "number": "22.3:4" }, { - "checksum": "d3ba7cfda60161cb47300e194e3d076342330aae22d93b71577c8de8eaa19e68", - "id": "fls_eha2hoey857x", - "link": "attributes.html#fls_eha2hoey857x", - "number": "13.2.8.3:5" + "checksum": "40c4d335089982458288126ed9615e8a2616285afc565532e391dd1ff35326f2", + "id": "fls_dzlycyk24euk", + "link": "inline-assembly.html#fls_dzlycyk24euk", + "number": "22.3:5" }, { - "checksum": "15633d7af605c9b73e99840c7e31720cc2a83e5731086fef2f9861de505c03ce", - "id": "fls_p6jlgmn2sg7j", - "link": "attributes.html#fls_p6jlgmn2sg7j", - "number": "13.2.8.3:6" - } - ], - "title": "Attribute macro_use" - }, - { - "id": "fls_qkmkev85o5jf", - "informational": false, - "link": "attributes.html#attribute-proc-macro", - "number": "13.2.8.4", - "paragraphs": [ - { - "checksum": "a7056881313a129fd3ec31f81d6d3bb01a2c9d5f56b3d256ff553f5d47df7eda", - "id": "fls_u48dtmh97g", - "link": "attributes.html#fls_u48dtmh97g", - "number": "13.2.8.4:1" + "checksum": "5154cd8c69374c45e9d741be110ba567134bb0b39eabcb8c458adbc972fdd2df", + "id": "fls_ics6gdzww1p", + "link": "inline-assembly.html#fls_ics6gdzww1p", + "number": "22.3:6" }, { - "checksum": "c2045a082f6a84673475a58ccde2204fadd47f7c6e23407bfaed9319e1242fdd", - "id": "fls_t4ez0zg1m569", - "link": "attributes.html#fls_t4ez0zg1m569", - "number": "13.2.8.4:2" - } - ], - "title": "Attribute proc_macro" - }, - { - "id": "fls_ejhlylrcajo", - "informational": false, - "link": "attributes.html#attribute-proc-macro-attribute", - "number": "13.2.8.5", - "paragraphs": [ + "checksum": "1fec5e0696f6525d77986c3fb4fe9afdfc0cb637119ad785681ba1c292856bae", + "id": "fls_mmc1w8jjr55r", + "link": "inline-assembly.html#fls_mmc1w8jjr55r", + "number": "22.3:7" + }, { - "checksum": "d914e82b19183ec81ba7ca878ce84320d00af280cfaeebaf06a1bce3ce91dc34", - "id": "fls_huznzmkuhdky", - "link": "attributes.html#fls_huznzmkuhdky", - "number": "13.2.8.5:1" + "checksum": "1a6421d6b7caaf88bbb46cf868ffb92933d8f68b11221f3cc918d093f8888e72", + "id": "fls_9hhtcey2d4t6", + "link": "inline-assembly.html#fls_9hhtcey2d4t6", + "number": "22.3:8" }, { - "checksum": "51be5aebe1c07fea1dd288f810fd58b529feabbb2db4633c447eac8036526b22", - "id": "fls_gc3ly8fsodf1", - "link": "attributes.html#fls_gc3ly8fsodf1", - "number": "13.2.8.5:2" - } - ], - "title": "Attribute proc_macro_attribute" - }, - { - "id": "fls_q6qecp6e413", - "informational": false, - "link": "attributes.html#attribute-proc-macro-derive", - "number": "13.2.8.6", - "paragraphs": [ + "checksum": "b2936dd6ad92f8cfa53239fd025460f7d3e1b679ede4b445d3115ece8999ed98", + "id": "fls_8aynifgq02gt", + "link": "inline-assembly.html#fls_8aynifgq02gt", + "number": "22.3:9" + }, { - "checksum": "ccd312dde256071afed24a8368cb4561a8238f97767345ad8286d082eb213a6e", - "id": "fls_l82yswg0k3px", - "link": "attributes.html#fls_l82yswg0k3px", - "number": "13.2.8.6:1" + "checksum": "0f946393a931cc5b78f9934819f7aa4ba6b514ee373b349ecce52f591ebcf435", + "id": "fls_5a3vfresnv5z", + "link": "inline-assembly.html#fls_5a3vfresnv5z", + "number": "22.3:10" }, { - "checksum": "c57f5a4e85fdec7a28646b6dac958146ae5ffacab2268b24082814db828cc895", - "id": "fls_ir9i4i2x5gyx", - "link": "attributes.html#fls_ir9i4i2x5gyx", - "number": "13.2.8.6:2" + "checksum": "4c9f00fb04a33159473b68a31e8ee74f70f23444dfe8e434256b0773119cc988", + "id": "fls_fta1gb5tzi3a", + "link": "inline-assembly.html#fls_fta1gb5tzi3a", + "number": "22.3:11" }, { - "checksum": "f06497bca4b72dd176f21ed2508cb69fa3833ef6cbd8a4b22af928fc995b81b8", - "id": "fls_NydVxyb43TH6", - "link": "attributes.html#fls_NydVxyb43TH6", - "number": "13.2.8.6:3" - } - ], - "title": "Attribute proc_macro_derive" - }, - { - "id": "fls_7bb5ua4g06k8", - "informational": false, - "link": "attributes.html#modules-attributes", - "number": "13.2.9", - "paragraphs": [], - "title": "Modules Attributes" - }, - { - "id": "fls_1zbaajz5prpn", - "informational": false, - "link": "attributes.html#attribute-path", - "number": "13.2.9.1", - "paragraphs": [ + "checksum": "ad39aebc78473e0d03c855868bf7740446e20ac900ed24bdf0f239dc211dfb67", + "id": "fls_sopiivuae0x7", + "link": "inline-assembly.html#fls_sopiivuae0x7", + "number": "22.3:12" + }, { - "checksum": "82da18a25355abd0b9d6d12edfe37ae45678bc9c547052772f63a9a356614526", - "id": "fls_lht4pcezmbxw", - "link": "attributes.html#fls_lht4pcezmbxw", - "number": "13.2.9.1:1" + "checksum": "2d30b2a811ba061cae0405a18f7f1f991bd3a4c2505bccd7e7be3afa49baf79b", + "id": "fls_81Ju1TEqJ48K", + "link": "inline-assembly.html#fls_81Ju1TEqJ48K", + "number": "22.3:13" }, { - "checksum": "1779cf2215b4d1593fa40611459d55cb2246586eee4503f9d2e823cc1871c703", - "id": "fls_qb6anohvc03k", - "link": "attributes.html#fls_qb6anohvc03k", - "number": "13.2.9.1:2" + "checksum": "503348cabcbc12fa0d9d17abf0e2019e91f2b649f8a985f9ccda7e84e2ac7ba4", + "id": "fls_kkrcyk96w8x1", + "link": "inline-assembly.html#fls_kkrcyk96w8x1", + "number": "22.3:14" }, { - "checksum": "074e1f0751b8cd12b037a8b551dc3d753dae8a3d8c8238db1bd5f168eab83a8f", - "id": "fls_18tcecx4p2wp", - "link": "attributes.html#fls_18tcecx4p2wp", - "number": "13.2.9.1:3" - } - ], - "title": "Attribute path" - }, - { - "id": "fls_go457hpaf7ov", - "informational": false, - "link": "attributes.html#prelude-attributes", - "number": "13.2.10", - "paragraphs": [], - "title": "Prelude Attributes" - }, - { - "id": "fls_iikmhqsp1r5a", - "informational": false, - "link": "attributes.html#attribute-no-implicit-prelude", - "number": "13.2.10.1", - "paragraphs": [ + "checksum": "a383ba34b050c0c54cc8abca1eb3edacee0c31437bceca025437faa04647913b", + "id": "fls_aniw4ehsn2kb", + "link": "inline-assembly.html#fls_aniw4ehsn2kb", + "number": "22.3:15" + }, { - "checksum": "78b6ef76a225f8a6314dbb484e5f6c33a4323ad9202816ead2f71474e4fcdc82", - "id": "fls_tki5k5uo74gw", - "link": "attributes.html#fls_tki5k5uo74gw", - "number": "13.2.10.1:1" + "checksum": "86940ba8544cf93b499195ba8a42affc88d320b1f29834d89a351cfbdd019105", + "id": "fls_j9XOoXDmN5Dq", + "link": "inline-assembly.html#fls_j9XOoXDmN5Dq", + "number": "22.3:16" }, { - "checksum": "81930ef591f96d6b1e90e5ad685dadeadd18244ffd6bf2b93e1e564d00df7002", - "id": "fls_cmrqxc5oax4r", - "link": "attributes.html#fls_cmrqxc5oax4r", - "number": "13.2.10.1:2" + "checksum": "3fd38b4ea292044ef82d8766b16d3c418062ba805b387bedc2880ed0d110fec4", + "id": "fls_jU8zg4k8dFsY", + "link": "inline-assembly.html#fls_jU8zg4k8dFsY", + "number": "22.3:17" }, { - "checksum": "63933d48ff890b9977fb1e7739ec4a0c3fce66da852cfc19921e9fef4c0c30c6", - "id": "fls_c7v2hbdb7g2d", - "link": "attributes.html#fls_c7v2hbdb7g2d", - "number": "13.2.10.1:3" - } - ], - "title": "Attribute no_implicit_prelude" - }, - { - "id": "fls_9xnaxd7qbakp", - "informational": false, - "link": "attributes.html#attribute-no-std", - "number": "13.2.10.2", - "paragraphs": [ + "checksum": "454698f13e45b8c93ac6b98fdde5ffd3c98fc9a27d568f69fb7431ffb7eee9bf", + "id": "fls_y2wCBvXDtQK2", + "link": "inline-assembly.html#fls_y2wCBvXDtQK2", + "number": "22.3:18" + }, { - "checksum": "bf65af2170c2fce6c798624dc20ef98cdd4c56ba8a6d771db6d63c5b33b9fe50", - "id": "fls_qnxihxsvkyf6", - "link": "attributes.html#fls_qnxihxsvkyf6", - "number": "13.2.10.2:1" + "checksum": "29c83fda93c055d8e8e1b5413124cd7b9321a5fcd2661ff0c6b2ad53c3a0cb10", + "id": "fls_66owmltvhnu4", + "link": "inline-assembly.html#fls_66owmltvhnu4", + "number": "22.3:19" }, { - "checksum": "812969d17f6cf1c8f09c3aa7e496056b918a7936c5656724b5023957f1a6480d", - "id": "fls_kxav9vw59ts4", - "link": "attributes.html#fls_kxav9vw59ts4", - "number": "13.2.10.2:2" + "checksum": "8f3aef39cde848c0aff944c3688510c0623f11c4956e906b2c149933ca1bfc31", + "id": "fls_72p8e4bo6pns", + "link": "inline-assembly.html#fls_72p8e4bo6pns", + "number": "22.3:20" }, { - "checksum": "c0823a0a3da1888952864c8a1e51dcb076f7acb24133027a4c96805704a36b87", - "id": "fls_ve1shwjq09pl", - "link": "attributes.html#fls_ve1shwjq09pl", - "number": "13.2.10.2:3" + "checksum": "1d170272d80212e9ab38968ac21b6adb73b67fea20a811908cf097679cd04403", + "id": "fls_z0dbmmp5yblf", + "link": "inline-assembly.html#fls_z0dbmmp5yblf", + "number": "22.3:21" }, { - "checksum": "be41407d903f76e4c4f3bb28728743dc02d28c58b2abb01664514d25f6707420", - "id": "fls_wgwsn7laoju7", - "link": "attributes.html#fls_wgwsn7laoju7", - "number": "13.2.10.2:4" + "checksum": "bcda305ab21eb743bfbaf94192a7f091188f70e80d49d4ecc69cd944d560dbc0", + "id": "fls_4jdnt8uap95i", + "link": "inline-assembly.html#fls_4jdnt8uap95i", + "number": "22.3:22" }, { - "checksum": "55f1527bb22419e4a271f37b4ddc4109e3478e34cbcaf56381afe6e82f4ff76a", - "id": "fls_lxkd6hdboav4", - "link": "attributes.html#fls_lxkd6hdboav4", - "number": "13.2.10.2:5" - } - ], - "title": "Attribute no_std" - }, - { - "id": "fls_nbbvukrdngev", - "informational": false, - "link": "attributes.html#runtime-attributes", - "number": "13.2.11", - "paragraphs": [], - "title": "Runtime Attributes" - }, - { - "id": "fls_fs0lcfllamj", - "informational": false, - "link": "attributes.html#attribute-global-allocator", - "number": "13.2.11.1", - "paragraphs": [ + "checksum": "0937925a39e22d804ee545814336d49c733f31ea7756377335fefff6db62282f", + "id": "fls_wd2hzsbzdg2y", + "link": "inline-assembly.html#fls_wd2hzsbzdg2y", + "number": "22.3:23" + }, { - "checksum": "b627ddaa5ef649b306598dd5f6297c67a76f75ec92d0b5746f31fa0163690273", - "id": "fls_5b8aewlgeon8", - "link": "attributes.html#fls_5b8aewlgeon8", - "number": "13.2.11.1:1" + "checksum": "f7df1b813dc778c45c902762fd85f901ebede6cf627c93ddffa2731614f81305", + "id": "fls_sqy00lg5j7c6", + "link": "inline-assembly.html#fls_sqy00lg5j7c6", + "number": "22.3:24" }, { - "checksum": "da27332328b097f036a002143a0c62df334d3fafe943281b9e5ac3f53256ac87", - "id": "fls_homoidh8mu1r", - "link": "attributes.html#fls_homoidh8mu1r", - "number": "13.2.11.1:2" - } - ], - "title": "Attribute global_allocator" - }, - { - "id": "fls_ls5eryuoxlp9", - "informational": false, - "link": "attributes.html#attribute-panic-handler", - "number": "13.2.11.2", - "paragraphs": [ + "checksum": "7dbe8e81fc6ab3a89a0c7db915942f414747d3e1426a7f0a9e0eb76bbcf6f54a", + "id": "fls_vxba1ttvz6hh", + "link": "inline-assembly.html#fls_vxba1ttvz6hh", + "number": "22.3:25" + }, { - "checksum": "811d25ac05c366571fee950dbae9130bfedbc07a6e6b74ecfd339fc993199f16", - "id": "fls_ryz8qy1wdnma", - "link": "attributes.html#fls_ryz8qy1wdnma", - "number": "13.2.11.2:1" + "checksum": "485c4204bb3fc6bde480eae99cb508323f08d53843fe840b571c140e2d09efaf", + "id": "fls_xkbnla2avrn0", + "link": "inline-assembly.html#fls_xkbnla2avrn0", + "number": "22.3:26" }, { - "checksum": "19a78bf1c35131ec0625e919fd7f92ef2acc5b5986bdae84101d8449b0bf671a", - "id": "fls_ncyod5gegMqL", - "link": "attributes.html#fls_ncyod5gegMqL", - "number": "13.2.11.2:2" + "checksum": "2eae510aa3c0984999efed6c99d780375dd3245fbf78f98b65affdd980165179", + "id": "fls_w6jhcv616l9o", + "link": "inline-assembly.html#fls_w6jhcv616l9o", + "number": "22.3:27" }, { - "checksum": "7be989fe417f083b8e5dd74846cda7e32d86a89db6194539d9733eb40ba837bc", - "id": "fls_jzIHzPkJqVm8", - "link": "attributes.html#fls_jzIHzPkJqVm8", - "number": "13.2.11.2:3" + "checksum": "bb5fda81378ad922bd59db0c0afb2d1243c2d1e2d61865303fb327146cc086cc", + "id": "fls_xweobiwapog1", + "link": "inline-assembly.html#fls_xweobiwapog1", + "number": "22.3:28" }, { - "checksum": "12c2336bf9e9b05b5fe0f3bab73c7b1cdb604d082afa1e7984eb3b79f71b3642", - "id": "fls_qXii07xWRUoT", - "link": "attributes.html#fls_qXii07xWRUoT", - "number": "13.2.11.2:4" + "checksum": "8148c38c96dbc7562e0a236a6f7c2bf76b1224b0b409a58dbe086b5bd1d63c38", + "id": "fls_4matyejw6cls", + "link": "inline-assembly.html#fls_4matyejw6cls", + "number": "22.3:29" }, { - "checksum": "a88dbeff7797209a81595c30a58d7f150b9104f217bcef43b2ee461555a5a0ce", - "id": "fls_ZEjvn4mmfSct", - "link": "attributes.html#fls_ZEjvn4mmfSct", - "number": "13.2.11.2:5" + "checksum": "fc4d057ac8b2c2a9823f6f4514305fd0ecc50af30f6f7ac3ed854e7f8a7e14c0", + "id": "fls_hklqabav1jju", + "link": "inline-assembly.html#fls_hklqabav1jju", + "number": "22.3:30" }, { - "checksum": "051e4b8ff27f299cff0e443ea677b2f935831974162b34dba8fd7b30feb610a7", - "id": "fls_iWPw0QFE5TrP", - "link": "attributes.html#fls_iWPw0QFE5TrP", - "number": "13.2.11.2:6" + "checksum": "a8f57c97461cbdfb32ce6148c10dcf2b2deb5070adcb2fe1bb4850b3c211ba8d", + "id": "fls_nq22h8gragil", + "link": "inline-assembly.html#fls_nq22h8gragil", + "number": "22.3:31" }, { - "checksum": "c82af1ca7021a4e17089b436f3d65889b6c5d9a6e7600fae295b40ff0f0f6ec6", - "id": "fls_WirXqd7UV4M0", - "link": "attributes.html#fls_WirXqd7UV4M0", - "number": "13.2.11.2:7" + "checksum": "a2c973cb292004179620ef85189058fef5ca0a6338ee56b885dfe5087c24658f", + "id": "fls_uxgcrs57bznk", + "link": "inline-assembly.html#fls_uxgcrs57bznk", + "number": "22.3:32" }, { - "checksum": "8e4d1d0997dd94de884dbc95268544335e331949cd1d45db0be53a80200a5714", - "id": "fls_5CGTQTuT0I5v", - "link": "attributes.html#fls_5CGTQTuT0I5v", - "number": "13.2.11.2:8" + "checksum": "a70c379b4de9989a193e6532e92d5156358a876a75e38c960fda76d0d60c3647", + "id": "fls_ym05938ejwng", + "link": "inline-assembly.html#fls_ym05938ejwng", + "number": "22.3:33" }, { - "checksum": "7b2ec9db6d8b1c8ace22368047c47d159462712924d519696cffb84e602fcbfc", - "id": "fls_8gqun8lma9wz", - "link": "attributes.html#fls_8gqun8lma9wz", - "number": "13.2.11.2:9" + "checksum": "3aee30c9effa5a942a1ada18c2ffb67bead416de8b1aa12b44d92fba4836a4e4", + "id": "fls_5l77g8h8et2o", + "link": "inline-assembly.html#fls_5l77g8h8et2o", + "number": "22.3:34" }, { - "checksum": "f8d09837569e3e2a9dace1f81b8514e10951252bd8ca780e1a7de6210fc4abe5", - "id": "fls_ka66jcu8gir7", - "link": "attributes.html#fls_ka66jcu8gir7", - "number": "13.2.11.2:10" - } - ], - "title": "Attribute panic_handler" - }, - { - "id": "fls_1l4mnlfk5rr2", - "informational": false, - "link": "attributes.html#attribute-windows-subsystem", - "number": "13.2.11.3", - "paragraphs": [ + "checksum": "71c84d73a44e48e642f979b9a86af1793a323f1925a436f71dd359c01bb520e4", + "id": "fls_xlcliuums5b0", + "link": "inline-assembly.html#fls_xlcliuums5b0", + "number": "22.3:35" + }, { - "checksum": "7102958597ca2f19205e0e9f7c950d9b597adf5a8c1e6fe96e7661413a02f860", - "id": "fls_7mzjahvdzpy5", - "link": "attributes.html#fls_7mzjahvdzpy5", - "number": "13.2.11.3:1" + "checksum": "2bdd537d58814885175ea0a364633afe6412e67633eb7dac5eeeebbfce4eac16", + "id": "fls_5p4hyl7mxgai", + "link": "inline-assembly.html#fls_5p4hyl7mxgai", + "number": "22.3:36" }, { - "checksum": "b8684eebecaefb67c2e17229476d5544ed6ff0e7e8b51d4b8cab06a169d1541c", - "id": "fls_t3c0t3lcnebk", - "link": "attributes.html#fls_t3c0t3lcnebk", - "number": "13.2.11.3:2" + "checksum": "3e4ce3eefa93e708f4edf4590415a8f929092e0676f38915c2d3bb267538656b", + "id": "fls_ilepg263w5o7", + "link": "inline-assembly.html#fls_ilepg263w5o7", + "number": "22.3:37" }, { - "checksum": "d52e19f42f5927bd34a651b16184a11e9fce5ae8de504bde89af1da5a51e0c59", - "id": "fls_go7pfkgpjk2t", - "link": "attributes.html#fls_go7pfkgpjk2t", - "number": "13.2.11.3:3" - } - ], - "title": "Attribute windows_subsystem" - }, - { - "id": "fls_riyi0gy48fxw", - "informational": false, - "link": "attributes.html#testing-attributes", - "number": "13.2.12", - "paragraphs": [], - "title": "Testing Attributes" - }, - { - "id": "fls_x849a4u7h82j", - "informational": false, - "link": "attributes.html#attribute-ignore", - "number": "13.2.12.1", - "paragraphs": [ - { - "checksum": "233bac50acbcf5ce70191e350e3e9d720fd83236b2a754a5846e557024e5fb50", - "id": "fls_qmdylxse9yhu", - "link": "attributes.html#fls_qmdylxse9yhu", - "number": "13.2.12.1:1" + "checksum": "bffcbb94249f82f25927f78213e778f1a7f985c4920412526cbb8efaef4b7845", + "id": "fls_tubmavru8wvn", + "link": "inline-assembly.html#fls_tubmavru8wvn", + "number": "22.3:38" }, { - "checksum": "001e5f299105a44ca3acba687e810549c184a7062f15f48c4c2907feedcd00db", - "id": "fls_9m8e59fc1tyh", - "link": "attributes.html#fls_9m8e59fc1tyh", - "number": "13.2.12.1:2" - } - ], - "title": "Attribute ignore" - }, - { - "id": "fls_aes2d94g12b9", - "informational": false, - "link": "attributes.html#attribute-should-panic", - "number": "13.2.12.2", - "paragraphs": [ + "checksum": "541ae9ba48b53ec96b54e4a5a32502dfa1000cf8754fe5ecc7d2312b34d2ee37", + "id": "fls_b1xi3u9k4pdl", + "link": "inline-assembly.html#fls_b1xi3u9k4pdl", + "number": "22.3:39" + }, { - "checksum": "bc29bb034a6a845f15d32f52bc63929f460f59dfafc941ba3d3ac7f591786021", - "id": "fls_w7dq8gnzel36", - "link": "attributes.html#fls_w7dq8gnzel36", - "number": "13.2.12.2:1" + "checksum": "cb91ec0812f0c384bf095f2797ac51734ba4121d8e8ab16363d157860a106878", + "id": "fls_i9ds6724tv20", + "link": "inline-assembly.html#fls_i9ds6724tv20", + "number": "22.3:40" }, { - "checksum": "0f5bcffb6ab2fd79ab1a1578fd84c818378597bd4bf7bc940dba19ff7cc9de02", - "id": "fls_bm5x846zfnb8", - "link": "attributes.html#fls_bm5x846zfnb8", - "number": "13.2.12.2:2" + "checksum": "e2937f6d2545e4b849a96cff1214ad6058a60ad6ba062f6d3f55147d0b5bbedf", + "id": "fls_trldyekxxlzx", + "link": "inline-assembly.html#fls_trldyekxxlzx", + "number": "22.3:41" }, { - "checksum": "4b053fefee6cf33683101e489e70e2569e4deb8d7aa26b06a840e03085ddb586", - "id": "fls_bcoq5aus8nkr", - "link": "attributes.html#fls_bcoq5aus8nkr", - "number": "13.2.12.2:3" - } - ], - "title": "Attribute should_panic" - }, - { - "id": "fls_dv2j1fvvnk1t", - "informational": false, - "link": "attributes.html#attribute-test", - "number": "13.2.12.3", - "paragraphs": [ + "checksum": "0c794e05658bfc67f5509b086682137d1e225b3d3125bc988b0f7dade8a602dd", + "id": "fls_efmpbyi4qjmf", + "link": "inline-assembly.html#fls_efmpbyi4qjmf", + "number": "22.3:42" + }, { - "checksum": "41f8276015dc58f76e82db668219043600611837b52a93fd9dbb18436201041a", - "id": "fls_o2elhg5w1rj9", - "link": "attributes.html#fls_o2elhg5w1rj9", - "number": "13.2.12.3:1" + "checksum": "c1e90e11afbfaff93f5efda1e0676fe9bc08b30d4293f557d10af6cef6e2f5b5", + "id": "fls_4x3w50w7qm8w", + "link": "inline-assembly.html#fls_4x3w50w7qm8w", + "number": "22.3:43" }, { - "checksum": "de64cdcc854596c8cf8a9e3bb1b6312bf4faac5928890297f69918e148a51d43", - "id": "fls_ert22u8rvkxt", - "link": "attributes.html#fls_ert22u8rvkxt", - "number": "13.2.12.3:2" + "checksum": "cc1f051a457238506a16c5250f10a10b89a5a6c4be1b61afbf9fb31e8db6299a", + "id": "fls_6cne58tlquze", + "link": "inline-assembly.html#fls_6cne58tlquze", + "number": "22.3:44" }, { - "checksum": "580975987fb8829aa62f5481e0c98f2f9a739c3d0a5cc0719d34be6815601aa3", - "id": "fls_c9ckjrq6emdj", - "link": "attributes.html#fls_c9ckjrq6emdj", - "number": "13.2.12.3:3" + "checksum": "07f663b7ccb32c2c12d99fcfcdaf84bba2f001085ffcbdc46af147dbd632e2bf", + "id": "fls_5w718fne9jsh", + "link": "inline-assembly.html#fls_5w718fne9jsh", + "number": "22.3:45" }, { - "checksum": "e7ee08c2d3407ba055bfeec7a59f1c8c56257bf55ff14412206210e1dbbe08a2", - "id": "fls_4uykzqpq6svl", - "link": "attributes.html#fls_4uykzqpq6svl", - "number": "13.2.12.3:4" + "checksum": "7f8e00a6debc5e2204cfaa0b86e6518ec9b97c56b7803af2fb2df7eada835828", + "id": "fls_tel7kogaqytg", + "link": "inline-assembly.html#fls_tel7kogaqytg", + "number": "22.3:46" }, { - "checksum": "2c4a77cf5f2b9daf7f0535ffb85ee39298f774999cb40b3f78672fb5a2bd7a24", - "id": "fls_aqzd30s267pt", - "link": "attributes.html#fls_aqzd30s267pt", - "number": "13.2.12.3:5" + "checksum": "080df59e1c6daaf9dcbaf8723b6d2151909c977c1ca58d4543b4648be9c8db54", + "id": "fls_aw61psz5drg8", + "link": "inline-assembly.html#fls_aw61psz5drg8", + "number": "22.3:47" }, { - "checksum": "c19b0501aa117ddef6f66c5dc6c307c9c23cdfb62ecb953be67f2cf7448cda23", - "id": "fls_ze6cs75y9aft", - "link": "attributes.html#fls_ze6cs75y9aft", - "number": "13.2.12.3:6" + "checksum": "f4784be0f3397b59835c7c80fc3fa280f40f0c5a558cfeb0dd74943416b87ae3", + "id": "fls_sv2x3x81b32j", + "link": "inline-assembly.html#fls_sv2x3x81b32j", + "number": "22.3:48" }, { - "checksum": "6bfc6057de86432e2367e8c9682ff52750a502d7bd68f37c400345417f514d4b", - "id": "fls_pcs0prrh23y3", - "link": "attributes.html#fls_pcs0prrh23y3", - "number": "13.2.12.3:7" + "checksum": "74f321168c8014a105d3fe61537f3cf35443beabfa8f6fc079c2b904d6de8b85", + "id": "fls_nebb0nhxf5ix", + "link": "inline-assembly.html#fls_nebb0nhxf5ix", + "number": "22.3:49" }, { - "checksum": "c55f9c613d6d247902e066735179a60ae013f13645180b2f0558fee98a1bcf07", - "id": "fls_niky8lbkvej9", - "link": "attributes.html#fls_niky8lbkvej9", - "number": "13.2.12.3:8" + "checksum": "07b64182b91f96ec3d687a71dbf44e3ef75125b7a754d09ee0c8274a774ab7c9", + "id": "fls_j0pxc8g8kcxm", + "link": "inline-assembly.html#fls_j0pxc8g8kcxm", + "number": "22.3:50" }, { - "checksum": "2799842d7aed69f223fca391a95a67b96cb9d904f240678550cd095843a9163c", - "id": "fls_qfuntdm2g184", - "link": "attributes.html#fls_qfuntdm2g184", - "number": "13.2.12.3:9" - } - ], - "title": "Attribute test" - }, - { - "id": "fls_r3zwgf9sg1xp", - "informational": false, - "link": "attributes.html#type-attributes", - "number": "13.2.13", - "paragraphs": [], - "title": "Type Attributes" - }, - { - "id": "fls_9tmvuqrmk3ug", - "informational": false, - "link": "attributes.html#attribute-non-exhaustive", - "number": "13.2.13.1", - "paragraphs": [ + "checksum": "f173b49637243cd66db248543679179b3894437aad904b62189d0106a6448977", + "id": "fls_wwh6xyclxwqj", + "link": "inline-assembly.html#fls_wwh6xyclxwqj", + "number": "22.3:51" + }, { - "checksum": "db123f9e8d90ea8a65b70081bc86bdbeb74728f7fbb3abf94a19106591eacfd5", - "id": "fls_szvrd79cgzsg", - "link": "attributes.html#fls_szvrd79cgzsg", - "number": "13.2.13.1:1" + "checksum": "9806b7c79391b43a6ff2c6de978f08dbf262be1f09d5994c4a3b5176b86d35da", + "id": "fls_qcb47z1ap9dz", + "link": "inline-assembly.html#fls_qcb47z1ap9dz", + "number": "22.3:52" }, { - "checksum": "d921bf1556555621ac4497f95036f2b36a900b3ca72209b3c8257b411f3434aa", - "id": "fls_1of56vl2ewq0", - "link": "attributes.html#fls_1of56vl2ewq0", - "number": "13.2.13.1:2" + "checksum": "d82b2f675143ea3e841f686833a7a7a875694a71bea0a335101d9f496b02b415", + "id": "fls_h01au4vk8mjd", + "link": "inline-assembly.html#fls_h01au4vk8mjd", + "number": "22.3:53" }, { - "checksum": "2b72505af01da754c8938fcc663e58c83b4bbcaabd1dc98ae2ab5e8da65fd8a2", - "id": "fls_hkyzdmmdyoin", - "link": "attributes.html#fls_hkyzdmmdyoin", - "number": "13.2.13.1:3" + "checksum": "f531c0d0ea7572debc684b12dbc0edf4d3924d960344eb4cbe9fdd03c62ba2cd", + "id": "fls_92ijsf4p6yn", + "link": "inline-assembly.html#fls_92ijsf4p6yn", + "number": "22.3:54" }, { - "checksum": "a408a04e473714cf160f9d5888fb173e91412fe640b793002c6cc41c7426ec78", - "id": "fls_7b0fvwrmz0mh", - "link": "attributes.html#fls_7b0fvwrmz0mh", - "number": "13.2.13.1:4" + "checksum": "f7e4dae4c4ddaa16ca26afebb3f35a670745389e64b88c65844e029c7a0cd97b", + "id": "fls_xkui7j3gnfg0", + "link": "inline-assembly.html#fls_xkui7j3gnfg0", + "number": "22.3:55" }, { - "checksum": "6cec52bab3d21f307cca338424ee3bb3802f2d59a4e3b224c7f00f72640ffd19", - "id": "fls_oqfrg9tqgaj8", - "link": "attributes.html#fls_oqfrg9tqgaj8", - "number": "13.2.13.1:5" + "checksum": "1e37a5e34d46afce6f7c07e6ac3a4bf3326a0f8b0b5f47b6398afc090f6bfe24", + "id": "fls_eahyqniqs2pn", + "link": "inline-assembly.html#fls_eahyqniqs2pn", + "number": "22.3:56" }, { - "checksum": "51678d511ec9b523bd052b51dce30e863238b3e09f7cf8640da14f382d4df5be", - "id": "fls_aql3c89840ix", - "link": "attributes.html#fls_aql3c89840ix", - "number": "13.2.13.1:6" + "checksum": "2a483ecef64dcb6a3f27eff5e1df5885c60546538f60c4dbfe1df9f84440e25b", + "id": "fls_5g7p2zo07gfe", + "link": "inline-assembly.html#fls_5g7p2zo07gfe", + "number": "22.3:57" }, { - "checksum": "91faee15159779bdfb246c3082526e3f01c0b8b71644bffad0bc701a7a23e5a0", - "id": "fls_cez7yxfc376c", - "link": "attributes.html#fls_cez7yxfc376c", - "number": "13.2.13.1:7" - } - ], - "title": "Attribute non_exhaustive" - } - ], - "title": "Attributes" - }, - { - "informational": false, - "link": "exceptions-and-errors.html", - "sections": [ - { - "id": "fls_dzq9cdz4ibsz", - "informational": false, - "link": "exceptions-and-errors.html", - "number": "16", - "paragraphs": [ + "checksum": "412c8164a6ff9c0f787cf54c8a347a7a17608a58d8e21510f1752e83c5090438", + "id": "fls_dobbatnjs0yt", + "link": "inline-assembly.html#fls_dobbatnjs0yt", + "number": "22.3:58" + }, { - "checksum": "d0dadbee084de3030e81aaf2325ea8878e99a72e77800a2662c1fe290bbc9d62", - "id": "fls_vsk4vhnuiyyz", - "link": "exceptions-and-errors.html#fls_vsk4vhnuiyyz", - "number": "16:1" + "checksum": "3ace63d8c3c27df97ec81e12c7db56b091597dd8e85b82a39419826a4e0e9964", + "id": "fls_ax8t4uta34ym", + "link": "inline-assembly.html#fls_ax8t4uta34ym", + "number": "22.3:59" }, { - "checksum": "f2e56f24b5565d180411f7693f63a3b70001ae9c0e4e605e1bd11ea5c207f968", - "id": "fls_ebangxc36t74", - "link": "exceptions-and-errors.html#fls_ebangxc36t74", - "number": "16:2" + "checksum": "b4b8fd6cf4f2c93377215e7dc6ab80bfd0fb116d3f2aa401c9b213f57ec725fb", + "id": "fls_dvft4ha00wj3", + "link": "inline-assembly.html#fls_dvft4ha00wj3", + "number": "22.3:60" }, { - "checksum": "1fd314e20308ba8d525a721f6a9bf050a337f71186510503044dc84efe09173e", - "id": "fls_ckeitwiv326r", - "link": "exceptions-and-errors.html#fls_ckeitwiv326r", - "number": "16:3" + "checksum": "719c2d431cf4fa8f1d481db9a1a415bbdba221e1f78a967d89e4fccfc93fa6bb", + "id": "fls_2ekwpx2bwj1b", + "link": "inline-assembly.html#fls_2ekwpx2bwj1b", + "number": "22.3:61" }, { - "checksum": "b346390e8119d57549b96dcb345a29235552cdc69a61d7d2c8c883cf6d34ae61", - "id": "fls_eg0orgibg98m", - "link": "exceptions-and-errors.html#fls_eg0orgibg98m", - "number": "16:4" + "checksum": "9b44340bf99e2336f97d4544df886ba2d990b67388ac619ac2d18fb6b917a484", + "id": "fls_3s2n9dlrlhz9", + "link": "inline-assembly.html#fls_3s2n9dlrlhz9", + "number": "22.3:62" }, { - "checksum": "83c2582339039d863409873a919aa3040ed86f444b3a5f5729e9fe0d65313c5e", - "id": "fls_ko1x0gp9e7y3", - "link": "exceptions-and-errors.html#fls_ko1x0gp9e7y3", - "number": "16:5" + "checksum": "fa9005c5774af5498655cd144555d0013c40b6180373f8c7e8918e1ba12e03aa", + "id": "fls_nbkkz6krcngi", + "link": "inline-assembly.html#fls_nbkkz6krcngi", + "number": "22.3:63" }, { - "checksum": "475f3e3082b2760a32589c7540d5123f0459abea622ba1ae15aa53068c337d7b", - "id": "fls_gwu4cn4ziabe", - "link": "exceptions-and-errors.html#fls_gwu4cn4ziabe", - "number": "16:6" + "checksum": "435b90abb518be657a6bf0d3065224d00b9a224cd9f274e5cf1aeb3a1270b306", + "id": "fls_utrvenwrettz", + "link": "inline-assembly.html#fls_utrvenwrettz", + "number": "22.3:64" + }, + { + "checksum": "7b31b5b6ead309666eb975b9d0834ba47f9a3fa55195978c7d27e62afaadd0f0", + "id": "fls_n85sjh925x", + "link": "inline-assembly.html#fls_n85sjh925x", + "number": "22.3:65" } ], - "title": "Exceptions and Errors" + "title": "Register Arguments" }, { - "id": "fls_k02nt1m5fq1z", + "id": "fls_e0896uk0mdyl", "informational": false, - "link": "exceptions-and-errors.html#panic", - "number": "16.1", + "link": "inline-assembly.html#assembly-instructions", + "number": "22.4", "paragraphs": [ { - "checksum": "f4d500cf3ddd573cf21ec19b03d0e534bb8caa50bdf5d11eecd095eeaae539f2", - "id": "fls_a554v4n0khye", - "link": "exceptions-and-errors.html#fls_a554v4n0khye", - "number": "16.1:1" + "checksum": "e080d5e0ebf43bfbc4219d2d2e397aef7c54aa4e7a03b032120b3ba79a54a4d6", + "id": "fls_4jr7eg6e0g4w", + "link": "inline-assembly.html#fls_4jr7eg6e0g4w", + "number": "22.4:1" }, { - "checksum": "d0ec8ad699ff1c4778bd35b0577f2de16559c4f7a8a67503e9d8b5ffb3feefe0", - "id": "fls_i9njhpte5l0t", - "link": "exceptions-and-errors.html#fls_i9njhpte5l0t", - "number": "16.1:2" + "checksum": "3ec0655943bc0361bb5ee51dee65ef55fc0cdbba100b9689c377aa00b58bbfcf", + "id": "fls_ihjhpy4osl53", + "link": "inline-assembly.html#fls_ihjhpy4osl53", + "number": "22.4:2" }, { - "checksum": "df2cd62113f2dc6ca452da3c12891fd958bc64f17397066b0a128c09a0fef520", - "id": "fls_n6q7bksyn1m", - "link": "exceptions-and-errors.html#fls_n6q7bksyn1m", - "number": "16.1:3" + "checksum": "a9365d11f22e6d0282ff7a7829d262dd0237bb7df54f65c07d06cac38a8ef4e5", + "id": "fls_2d05gcixjrzt", + "link": "inline-assembly.html#fls_2d05gcixjrzt", + "number": "22.4:3" }, { - "checksum": "7ce748762310bab19fffad785d50307098ea7d0e69ac336f7218ce22e6205dfa", - "id": "fls_xmtt04lw517w", - "link": "exceptions-and-errors.html#fls_xmtt04lw517w", - "number": "16.1:4" - } - ], - "title": "Panic" - }, - { - "id": "fls_hi1iz0gbnksi", - "informational": false, - "link": "exceptions-and-errors.html#abort", - "number": "16.2", - "paragraphs": [ + "checksum": "f0766d5049dcbb5d0c9cd71d282ee6fadf59ca4944f8fcfe3f0a3076cbee4359", + "id": "fls_z64f094aivp6", + "link": "inline-assembly.html#fls_z64f094aivp6", + "number": "22.4:4" + }, { - "checksum": "7a15948904b2de411b7c6338a4b97095cd8046a23ef8462801fa784b9b75ba03", - "id": "fls_9a1izu3omkbn", - "link": "exceptions-and-errors.html#fls_9a1izu3omkbn", - "number": "16.2:1" + "checksum": "818e1461216ec078c22ad83372847074fe214f98afcc5de45e628dae31f6c1f9", + "id": "fls_u8lifqig90gq", + "link": "inline-assembly.html#fls_u8lifqig90gq", + "number": "22.4:5" }, { - "checksum": "890c034f6aea73b0142607337332d673a009ec8f47e288cd2cfdab3f5c91cee4", - "id": "fls_iq6olct3rw4u", - "link": "exceptions-and-errors.html#fls_iq6olct3rw4u", - "number": "16.2:2" + "checksum": "a8ae2c3d3244053dde269a59221580fbe43986dc1e0db3bb6a587f6818456d11", + "id": "fls_lfeun3er5sc9", + "link": "inline-assembly.html#fls_lfeun3er5sc9", + "number": "22.4:6" }, { - "checksum": "77bbb6c647c37714e1fa9e1f81b3f63485155de50d4d371608e8f006a638a216", - "id": "fls_wd2q6ft9yzrg", - "link": "exceptions-and-errors.html#fls_wd2q6ft9yzrg", - "number": "16.2:3" + "checksum": "df6f74ba0870e9951d73e552be2119d9b8d9cde3bfe76879dbc0b4d621f4adff", + "id": "fls_mmdmymljq8a3", + "link": "inline-assembly.html#fls_mmdmymljq8a3", + "number": "22.4:7" }, { - "checksum": "b5c6b6bfc6de8e05e13b1fa486c385523fa6472c257f7c8c4b2b86ec91d6664f", - "id": "fls_7bnrbjb0pq5n", - "link": "exceptions-and-errors.html#fls_7bnrbjb0pq5n", - "number": "16.2:4" + "checksum": "d5f187840f02cfdf21c191f7e410d01ce205cd27e448a8ed69b3b623d6a75434", + "id": "fls_xugsn2ghh73c", + "link": "inline-assembly.html#fls_xugsn2ghh73c", + "number": "22.4:8" + }, + { + "checksum": "78dc81cd3902f11ea65d5e75e486faeb11ca34a926001792ce627e4c3f8fd23f", + "id": "fls_opnxq5kyw9jo", + "link": "inline-assembly.html#fls_opnxq5kyw9jo", + "number": "22.4:9" + }, + { + "checksum": "67eac47f7353bee4b01c2c0155a18d611ee18841d560d9d21ef05ad20fde5032", + "id": "fls_wydu9yft7a3r", + "link": "inline-assembly.html#fls_wydu9yft7a3r", + "number": "22.4:10" } ], - "title": "Abort" - } - ], - "title": "Exceptions and Errors" - }, - { - "informational": true, - "link": "changelog.html", - "sections": [], - "title": "Changelog" - }, - { - "informational": false, - "link": "entities-and-resolution.html", - "sections": [ - { - "id": "fls_gdeyap4or1db", - "informational": false, - "link": "entities-and-resolution.html", - "number": "14", - "paragraphs": [], - "title": "Entities and Resolution" + "title": "Assembly Instructions" }, { - "id": "fls_151r19d7xbgz", + "id": "fls_lv19xysy1f7e", "informational": false, - "link": "entities-and-resolution.html#entities", - "number": "14.1", + "link": "inline-assembly.html#register-parameter-modifiers", + "number": "22.4.1", "paragraphs": [ { - "checksum": "92b15a7349815675a6ada6d976e81b32a73c72307b7c303744b672b2b79f3ccb", - "id": "fls_x7j6wcigqt7u", - "link": "entities-and-resolution.html#fls_x7j6wcigqt7u", - "number": "14.1:1" - }, - { - "checksum": "3ac0c135867846c8545f48d505c7223de228d3c834e6c35369d20839f30645cc", - "id": "fls_40d2g0hvq2il", - "link": "entities-and-resolution.html#fls_40d2g0hvq2il", - "number": "14.1:2" + "checksum": "df3daccc1134fbde7da5c81da7286e4a8aee9877bdc758f10041315d7afdbc82", + "id": "fls_2xilifichdqu", + "link": "inline-assembly.html#fls_2xilifichdqu", + "number": "22.4.1:1" }, { - "checksum": "387b393db8ebbfbbaa31538cf9d1ed1fbd883a62e99cd43daae557d55d6e3778", - "id": "fls_lcca91wjwnpx", - "link": "entities-and-resolution.html#fls_lcca91wjwnpx", - "number": "14.1:3" + "checksum": "8a38fde192b47c9883e5f9c64b28fc71e84f6027fdf2b6510307bd918adb2c36", + "id": "fls_o3fx9397ib74", + "link": "inline-assembly.html#fls_o3fx9397ib74", + "number": "22.4.1:2" }, { - "checksum": "1e043bf7a4a6c40020a77d37fba4d646c5e5114a2fd0ce467c817c77746df26f", - "id": "fls_94l2d7ti0hjw", - "link": "entities-and-resolution.html#fls_94l2d7ti0hjw", - "number": "14.1:4" + "checksum": "c4b970759ed150c13e63c36cd2534ef92a591c902367bfc2ecccc00302882d45", + "id": "fls_3atrad53m22a", + "link": "inline-assembly.html#fls_3atrad53m22a", + "number": "22.4.1:3" }, { - "checksum": "cc53b299dafa70bd4b1bc538690c6759ff1aad962a25dd458050dd834792d02d", - "id": "fls_kvdqmo8gmdxi", - "link": "entities-and-resolution.html#fls_kvdqmo8gmdxi", - "number": "14.1:5" + "checksum": "2aa77d6c13b1986b1ca71de2a9193b60d5f2ad0e8326131351cbe4785d63394a", + "id": "fls_5zaqgz9jc8gy", + "link": "inline-assembly.html#fls_5zaqgz9jc8gy", + "number": "22.4.1:4" }, { - "checksum": "62877b7edbba7547c96ed213430dab1fe6be842fc13b0905bf1e0e39c1574c5b", - "id": "fls_b3cdg74utyvo", - "link": "entities-and-resolution.html#fls_b3cdg74utyvo", - "number": "14.1:6" + "checksum": "1dbdb7b1cb424770dccf1f1ca6a22190bfded4e3a864ebe0c70138dcb08aa376", + "id": "fls_erjczjotwqc3", + "link": "inline-assembly.html#fls_erjczjotwqc3", + "number": "22.4.1:5" }, { - "checksum": "df0355bf9d408621ab3501afd0c95fb72223e5ff74db7d62eaddc72e96f02fe7", - "id": "fls_njcmeqxzvfsa", - "link": "entities-and-resolution.html#fls_njcmeqxzvfsa", - "number": "14.1:7" + "checksum": "fce3a5b86ad0c82c14627febfea99be0d1adbc87a19da2a21ac7918c982c2165", + "id": "fls_z8m45i9xqkct", + "link": "inline-assembly.html#fls_z8m45i9xqkct", + "number": "22.4.1:6" }, { - "checksum": "9cd87b048aec0624e781b4917dc99e0e3956051679365ab7d33211685d93833e", - "id": "fls_63ul8sgf6dgr", - "link": "entities-and-resolution.html#fls_63ul8sgf6dgr", - "number": "14.1:8" + "checksum": "f2a6f18c824fdc7760e14c9983e4f55bb428d7bc2e926c5336d84aebcad96812", + "id": "fls_adaavz3olha3", + "link": "inline-assembly.html#fls_adaavz3olha3", + "number": "22.4.1:7" }, { - "checksum": "43a9474bc05f5cd0e56b671046a19f56d4d25db70d02d706182d94772fbd9525", - "id": "fls_6hxf0rn9j1sr", - "link": "entities-and-resolution.html#fls_6hxf0rn9j1sr", - "number": "14.1:9" + "checksum": "402be3937906f8d4fa926dd2d3368473a0c85d6086a0eaf2ced7795159268355", + "id": "fls_u5dzca5f2pfm", + "link": "inline-assembly.html#fls_u5dzca5f2pfm", + "number": "22.4.1:8" }, { - "checksum": "9db5af7bb0d5b4e2f0254774b4d7490d11b8de8e608b0b73eab3268c19fb91fb", - "id": "fls_2qitjk5ssaau", - "link": "entities-and-resolution.html#fls_2qitjk5ssaau", - "number": "14.1:10" + "checksum": "a46c8e5b6d61001549276e9ceda68d5082cde5d8c411b9fcd4322e415159f543", + "id": "fls_6542a2hi5yhc", + "link": "inline-assembly.html#fls_6542a2hi5yhc", + "number": "22.4.1:9" }, { - "checksum": "e602c23f513b2a0a4e008c3ea5961a7ed8f80dac05e98ee7a31d32e25387986d", - "id": "fls_4li2c5qc31c7", - "link": "entities-and-resolution.html#fls_4li2c5qc31c7", - "number": "14.1:11" + "checksum": "69d1797e8dae4213d66892c3b2df1eccc2ba619132a731ed8578a80930d8b125", + "id": "fls_94thqwabspw2", + "link": "inline-assembly.html#fls_94thqwabspw2", + "number": "22.4.1:10" }, { - "checksum": "f8bbcd5c0b5f9b29fea6dd2805dd0de209e9d9b443342686960740d64e79321f", - "id": "fls_nq8n7w2s3bja", - "link": "entities-and-resolution.html#fls_nq8n7w2s3bja", - "number": "14.1:12" + "checksum": "a26371ca4aa01bab4d2e1d2db822d99d09f609cc2e368e34640291ed8b08987b", + "id": "fls_xhp8xu8xvvgd", + "link": "inline-assembly.html#fls_xhp8xu8xvvgd", + "number": "22.4.1:11" }, { - "checksum": "fa112b87d033633e63c7f4216a3dac2dfa3bd0d440c7f574ab7a8375752d577d", - "id": "fls_jv7qi34flit0", - "link": "entities-and-resolution.html#fls_jv7qi34flit0", - "number": "14.1:13" + "checksum": "934b550a3ace7fdebb140b888b4c6b0bd6fc15ba51a11245c09777b143e31c74", + "id": "fls_g0p2ebuffnxb", + "link": "inline-assembly.html#fls_g0p2ebuffnxb", + "number": "22.4.1:12" }, { - "checksum": "e61e4578e8cd0a2124be00366bcc0ace261b47e5cc829bcf30274cd2b97c588f", - "id": "fls_rutlgmzh3tnz", - "link": "entities-and-resolution.html#fls_rutlgmzh3tnz", - "number": "14.1:14" + "checksum": "91e48c407ab94676b42f914c8b5e3d221426fc65422e434aee70e89bc2a2575e", + "id": "fls_d0e2weni8q87", + "link": "inline-assembly.html#fls_d0e2weni8q87", + "number": "22.4.1:13" }, { - "checksum": "79a170cb7c345638c6755e2d61f7444d2a35b53530a2cb6d81638b4f002de86e", - "id": "fls_1owx5ch7sidm", - "link": "entities-and-resolution.html#fls_1owx5ch7sidm", - "number": "14.1:15" + "checksum": "c267be0d4596f4e187e1217ff192f745f74d1ed2d2e60cffc633f57ab23a3d0f", + "id": "fls_bq3p4k42tzh8", + "link": "inline-assembly.html#fls_bq3p4k42tzh8", + "number": "22.4.1:14" }, { - "checksum": "25dffbf70cb2bb0cd22c431fd0a46235618a589b0bdbf4bf180b51ed37a9d5d6", - "id": "fls_8ldy7lec9bcd", - "link": "entities-and-resolution.html#fls_8ldy7lec9bcd", - "number": "14.1:16" + "checksum": "fbd2694649731061bfc93b035df30a2ffd8057aa228d378e6a1bbbd4d0c3b01b", + "id": "fls_l5mkfdot97fz", + "link": "inline-assembly.html#fls_l5mkfdot97fz", + "number": "22.4.1:15" }, { - "checksum": "9f6fa9d43fbf2924e1c1ecc5b6eef6db41f10e0eda261f898fa00b0db1afddce", - "id": "fls_3mt2p4ssqt0a", - "link": "entities-and-resolution.html#fls_3mt2p4ssqt0a", - "number": "14.1:17" + "checksum": "a80431476395f4d9b37789b614a02fd3ed653fbfdbe231393ee74f8ffa344d62", + "id": "fls_sarl1hoq0lco", + "link": "inline-assembly.html#fls_sarl1hoq0lco", + "number": "22.4.1:16" }, { - "checksum": "79b617624b18e886c67a3fd5303e3c3701a1bd761fbd598742fa457fc62de196", - "id": "fls_qqwu3e98lktb", - "link": "entities-and-resolution.html#fls_qqwu3e98lktb", - "number": "14.1:18" + "checksum": "e8ec5785b962e9d67a7073c3f3ed029663329777ffe7ed04740bb5bae1a2ca3d", + "id": "fls_f4q9a0jrs4ek", + "link": "inline-assembly.html#fls_f4q9a0jrs4ek", + "number": "22.4.1:17" }, { - "checksum": "3cb5bba3fd185734dbe976155c21323253786de2fe38287190058d186d71d532", - "id": "fls_fup6984lxdfy", - "link": "entities-and-resolution.html#fls_fup6984lxdfy", - "number": "14.1:19" + "checksum": "94c265f36be700f5ab1b92a26255bccb819f831e2c5eb49430450911142fd3fc", + "id": "fls_w0hn6vkpuvpc", + "link": "inline-assembly.html#fls_w0hn6vkpuvpc", + "number": "22.4.1:18" }, { - "checksum": "d3626b9e94df64ec6a68712a8efc764bafebfd1c7736ff21617a39badd0aaffb", - "id": "fls_ji9iem1c7ekq", - "link": "entities-and-resolution.html#fls_ji9iem1c7ekq", - "number": "14.1:20" + "checksum": "a9dbcb5f2bc6a82cb619b70700efb6c5fb95c9028afc2ed8d33d54d9b8cc4285", + "id": "fls_a7bvkyh5otx5", + "link": "inline-assembly.html#fls_a7bvkyh5otx5", + "number": "22.4.1:19" }, { - "checksum": "65940ab579200a562e9131adc24d67f63cf6751522972925d98e3eae88aff033", - "id": "fls_v7w8ptbyxv9w", - "link": "entities-and-resolution.html#fls_v7w8ptbyxv9w", - "number": "14.1:21" + "checksum": "210fc5aba0385a72ab09974dd17ebe68a03a97d12721b43936fe872062b67039", + "id": "fls_klthi4cczkem", + "link": "inline-assembly.html#fls_klthi4cczkem", + "number": "22.4.1:20" }, { - "checksum": "d2c4cc3aa6df1ff8c7acad8083bd97da4c7bd274a886f15fd95a0d2495bdbb3c", - "id": "fls_ig1l38gpy5gy", - "link": "entities-and-resolution.html#fls_ig1l38gpy5gy", - "number": "14.1:22" + "checksum": "99debc3917e79cbba4f44a5de1c26fe2a35a28b999da0b9e0896c74136c7f55a", + "id": "fls_pizgnxvcnj46", + "link": "inline-assembly.html#fls_pizgnxvcnj46", + "number": "22.4.1:21" }, { - "checksum": "87567b121feb41fb49a72ba0f88e7147e1ba7182f665fcd3118ee7cb4e446d2a", - "id": "fls_ed0t6u7fo3fi", - "link": "entities-and-resolution.html#fls_ed0t6u7fo3fi", - "number": "14.1:23" + "checksum": "4646e2e4cfd3b465d1c592a0f0fc10fc28890d013e8bc48635645162b72b1c25", + "id": "fls_pvzfiuxka6wg", + "link": "inline-assembly.html#fls_pvzfiuxka6wg", + "number": "22.4.1:22" }, { - "checksum": "d9efa486af937ab8018d16e3b87ca3ba0732e7ca8ee67d91e37f85f401545d65", - "id": "fls_gjps01c8l6aa", - "link": "entities-and-resolution.html#fls_gjps01c8l6aa", - "number": "14.1:24" - } - ], - "title": "Entities" - }, - { - "id": "fls_jdknpu3kf865", - "informational": false, - "link": "entities-and-resolution.html#visibility", - "number": "14.2", - "paragraphs": [ - { - "checksum": "14af7dc4459e85c32c0011005e8e1d05bae942ed8f5d4cb54aa713543425be90", - "id": "fls_7kpepal8ghuj", - "link": "entities-and-resolution.html#fls_7kpepal8ghuj", - "number": "14.2:1" + "checksum": "13837d13ce9ed671b2a8b76b9831fa0d911cd1db7ef3fd3c4dc3393776c153e8", + "id": "fls_pf8yj3m81wk4", + "link": "inline-assembly.html#fls_pf8yj3m81wk4", + "number": "22.4.1:23" }, { - "checksum": "fca421955d7d461ea14be29c223491277febdafc297c8580073d5de797c6d03d", - "id": "fls_qo0itr5il1kk", - "link": "entities-and-resolution.html#fls_qo0itr5il1kk", - "number": "14.2:2" + "checksum": "5f0c87c237f4329b033b02aa1a4e501b3ef81ac67ae21f801fc66e6f1860dbdc", + "id": "fls_2bf3ady2idq1", + "link": "inline-assembly.html#fls_2bf3ady2idq1", + "number": "22.4.1:24" }, { - "checksum": "5ca85d6ded39a41b0210496968063f04f76d7902d519e0044cb431f2888c032e", - "id": "fls_knjruq5wppv", - "link": "entities-and-resolution.html#fls_knjruq5wppv", - "number": "14.2:3" + "checksum": "eb8c9934a20fbabd92ca5744c146afd324847e0dfda556cf639c4dfb8f63fe77", + "id": "fls_x1se4r75v58o", + "link": "inline-assembly.html#fls_x1se4r75v58o", + "number": "22.4.1:25" }, { - "checksum": "f5f0a1362543a52c48a0832fff6511e7b9cdc663430d2e625d748329455133e9", - "id": "fls_t7i4n19qdgn4", - "link": "entities-and-resolution.html#fls_t7i4n19qdgn4", - "number": "14.2:4" + "checksum": "a71927d83c22a012d774779a46a3cc373fd770ac798eaa5bccaa5727d5fc7d23", + "id": "fls_rgovn5r3caif", + "link": "inline-assembly.html#fls_rgovn5r3caif", + "number": "22.4.1:26" }, { - "checksum": "66f72eec45e87d39e392824cbccd92cde109e25c1ee3731ca8e7b27a1bf1b778", - "id": "fls_aa4f3rvir9lm", - "link": "entities-and-resolution.html#fls_aa4f3rvir9lm", - "number": "14.2:5" + "checksum": "d6f5bc80d218fd2eb5f767c7df3d43a0fbb3a4f9d611ff09ec5e2db2d73b964b", + "id": "fls_faxm8xkhruvz", + "link": "inline-assembly.html#fls_faxm8xkhruvz", + "number": "22.4.1:27" }, { - "checksum": "3e16d751015c5913b6d155171e1fe22c30d873dd4c5557cff7f046161cdcea45", - "id": "fls_tnh7o3pb4e22", - "link": "entities-and-resolution.html#fls_tnh7o3pb4e22", - "number": "14.2:6" + "checksum": "6cff8dae509fe0e99e81732637131e38defb7dc8880572cd5eaab052c4f188ae", + "id": "fls_x1kkkvugpkyd", + "link": "inline-assembly.html#fls_x1kkkvugpkyd", + "number": "22.4.1:28" }, { - "checksum": "452fb25c8aa516688bcf11408b978c40b71cfd10786c03becf5b16b12856c292", - "id": "fls_yymgpyi67dty", - "link": "entities-and-resolution.html#fls_yymgpyi67dty", - "number": "14.2:7" + "checksum": "486e270c4089e2051cac9eaba641373777e921aa5c7fe474f0ca357161b85e9e", + "id": "fls_uonqmj16oqxe", + "link": "inline-assembly.html#fls_uonqmj16oqxe", + "number": "22.4.1:29" }, { - "checksum": "80ceeb14a02b69d81e041c8aa913ebc5ae529cf2ee64c36a4e3d2490d9362418", - "id": "fls_hc121mxknq03", - "link": "entities-and-resolution.html#fls_hc121mxknq03", - "number": "14.2:8" + "checksum": "ab3d0ca0fad9f39d9ea1ee3f6e6bd6a62feaf78a7c4ad89e4da7e1c9e1cd94e9", + "id": "fls_ydhwlp56vmrz", + "link": "inline-assembly.html#fls_ydhwlp56vmrz", + "number": "22.4.1:30" }, { - "checksum": "5b655efb0d1d9c1ebf6eb86a27556a8c7c7f88e3eefb54012f7a4875c62c4c65", - "id": "fls_icztzxjpm1du", - "link": "entities-and-resolution.html#fls_icztzxjpm1du", - "number": "14.2:9" + "checksum": "88ba61d8f48ccf3c27d589bf4bce3d0792b2193ee35a423cf45e9b005f6270f3", + "id": "fls_yolqzfqbfjoh", + "link": "inline-assembly.html#fls_yolqzfqbfjoh", + "number": "22.4.1:31" }, { - "checksum": "ce3d4660375205a9a7a05150a0d32054e4b084831f3597693559ff0ac6ecc11a", - "id": "fls_np8aghofjqhm", - "link": "entities-and-resolution.html#fls_np8aghofjqhm", - "number": "14.2:10" + "checksum": "4cc9da7a653b181d53c273bb4262f0da36f474bba28cccad6515df9569c34691", + "id": "fls_ojzzhoed6t9l", + "link": "inline-assembly.html#fls_ojzzhoed6t9l", + "number": "22.4.1:32" }, { - "checksum": "58a169c5015aa314a9ea29344a5fd52675e580674d1e2c168b5a1cf37a662248", - "id": "fls_quzvhzpr0124", - "link": "entities-and-resolution.html#fls_quzvhzpr0124", - "number": "14.2:11" + "checksum": "7fbd7be9fcd499335adb0893a1a8a65bec287ca73feb1889dc5982a40247b238", + "id": "fls_gw56ok8llid3", + "link": "inline-assembly.html#fls_gw56ok8llid3", + "number": "22.4.1:33" }, { - "checksum": "807c234d700c1c58154b62858df9203ffd5320127beface925513a72d43746a9", - "id": "fls_utgjx6l5zwfl", - "link": "entities-and-resolution.html#fls_utgjx6l5zwfl", - "number": "14.2:12" + "checksum": "72a5a17c379fbb6bacbacf067b17b2ceea95cab6686fa06bc8a48211b01489fa", + "id": "fls_ry7qoosmjrev", + "link": "inline-assembly.html#fls_ry7qoosmjrev", + "number": "22.4.1:34" }, { - "checksum": "651aeacef587c3880996a14a105866d5887bfa31fee311aca7513f5271e0ab59", - "id": "fls_jifg2st5bfd6", - "link": "entities-and-resolution.html#fls_jifg2st5bfd6", - "number": "14.2:13" + "checksum": "6790bb787a1e84d353705bdf02d3ecff05ea2e3e901235703945395484fba35d", + "id": "fls_i0ax45x2wskd", + "link": "inline-assembly.html#fls_i0ax45x2wskd", + "number": "22.4.1:35" }, { - "checksum": "d2248a6b249c97703150accda87c18ce1d43c45ffefa58683d93eab6824434a9", - "id": "fls_dm0xr424ine1", - "link": "entities-and-resolution.html#fls_dm0xr424ine1", - "number": "14.2:14" + "checksum": "173a7c080780f8a61d2439061e6ae4d2d179116636cebf6c3b387311b75d8cdc", + "id": "fls_op4dx1rqwhsf", + "link": "inline-assembly.html#fls_op4dx1rqwhsf", + "number": "22.4.1:36" } ], - "title": "Visibility" + "title": "Register Parameter Modifiers" }, { - "id": "fls_9i5msiuuyihf", + "id": "fls_6momhvgx4w21", "informational": false, - "link": "entities-and-resolution.html#paths", - "number": "14.3", + "link": "inline-assembly.html#directive-support", + "number": "22.4.2", "paragraphs": [ { - "checksum": "faccaa19e7b0dd16805a53e9927816227b3f4124f6860de31b266715a59bde86", - "id": "fls_klcltwcwrw6i", - "link": "entities-and-resolution.html#fls_klcltwcwrw6i", - "number": "14.3:1" + "checksum": "273042489d4acdfdbbb4c1c21595ddaf45d9567bc4f222b33e969448bfe38b69", + "id": "fls_4tfod2vgz2m6", + "link": "inline-assembly.html#fls_4tfod2vgz2m6", + "number": "22.4.2:1" + }, + { + "checksum": "c5968dc1b0f463f09aeb9cd4b2aa570644d895641c7b98e829068c6a3b7bcead", + "id": "fls_3b0ab1nlo641", + "link": "inline-assembly.html#fls_3b0ab1nlo641", + "number": "22.4.2:2" + }, + { + "checksum": "b3410bc20ea46ca83da705dd5c3d4bd7e5dbcbb0cb06be3739e98bde74815d2e", + "id": "fls_caqznttql5p8", + "link": "inline-assembly.html#fls_caqznttql5p8", + "number": "22.4.2:3" + }, + { + "checksum": "327bbebd420db49f61b331ce68c29a03205db8e1034271f35724e3bff4967225", + "id": "fls_bcheqswo7a1", + "link": "inline-assembly.html#fls_bcheqswo7a1", + "number": "22.4.2:4" }, { - "checksum": "6f1bc5b5d9ddca435a18802abb0879b3e878d2b97ae4b40649b17237e7aefb96", - "id": "fls_y1z7kougmahd", - "link": "entities-and-resolution.html#fls_y1z7kougmahd", - "number": "14.3:2" + "checksum": "322b37644fe5b701127925bfa2b7e952ba5a3875e5ea4488a332ddde869dc5fd", + "id": "fls_qxcl999rdwam", + "link": "inline-assembly.html#fls_qxcl999rdwam", + "number": "22.4.2:5" }, { - "checksum": "a598a12193d0fc3d17a4719a6654b5c3e5de057fb03a8f36364aedb05933e05d", - "id": "fls_8q8nqfpSz7Ly", - "link": "entities-and-resolution.html#fls_8q8nqfpSz7Ly", - "number": "14.3:3" + "checksum": "b253c450a0ba8f47f8d9771eac1d50f4af75e1f2c3a1287d83242bd3fe0dece5", + "id": "fls_2yi7kjnhkfme", + "link": "inline-assembly.html#fls_2yi7kjnhkfme", + "number": "22.4.2:6" }, { - "checksum": "a04567d394d65300bd24d7a6f612dfe96ab09a8e1d9d24007027cc4d257070ef", - "id": "fls_opn5n5t2mo3m", - "link": "entities-and-resolution.html#fls_opn5n5t2mo3m", - "number": "14.3:4" + "checksum": "38d62d0687777daed55c786318f571e04236edf24b0be2be66fb64bcc79a1ec0", + "id": "fls_q0jp60aj81nv", + "link": "inline-assembly.html#fls_q0jp60aj81nv", + "number": "22.4.2:7" }, { - "checksum": "5acaf482d5f5abc1db1e3f4e641bcd7d6c807d43c02ce713887758947708de2a", - "id": "fls_774uryecc2sx", - "link": "entities-and-resolution.html#fls_774uryecc2sx", - "number": "14.3:5" + "checksum": "74b6e657cec0604048f4b316ae345d1d3607cb8345b45d3c19a0762e701bb114", + "id": "fls_akny3esj88yy", + "link": "inline-assembly.html#fls_akny3esj88yy", + "number": "22.4.2:8" }, { - "checksum": "a51e77fdf7a71f0b0eb300f601b6c822e9ab150f5103aa44a1d5ca3df82f5d6e", - "id": "fls_7k88ypcgaoff", - "link": "entities-and-resolution.html#fls_7k88ypcgaoff", - "number": "14.3:6" + "checksum": "d74eb6d81576b2d4afe799e9b3c3af708fffc38c914243232b77c7da8763f198", + "id": "fls_9jajt7jn9cxk", + "link": "inline-assembly.html#fls_9jajt7jn9cxk", + "number": "22.4.2:9" }, { - "checksum": "ac1d3cb4221100b61e82b61f5b77c09f81760c8ad406f8180479d1578f69aad6", - "id": "fls_7kb6ltajgiou", - "link": "entities-and-resolution.html#fls_7kb6ltajgiou", - "number": "14.3:7" + "checksum": "bf756baa1fd54ce8e191d9722e682a9688658b4f09b5a2d2adc2be66ec71f671", + "id": "fls_i7dr87fyrei8", + "link": "inline-assembly.html#fls_i7dr87fyrei8", + "number": "22.4.2:10" }, { - "checksum": "2bbb4365668a8e9949e6ea226fc1c4ca1bb0c3059e7c2972f064baed667ad506", - "id": "fls_n77icl6idazp", - "link": "entities-and-resolution.html#fls_n77icl6idazp", - "number": "14.3:8" + "checksum": "d0ce672b39bf64337b97f071c7ba60f06f19162e6e462862b7d3ba89bbf35177", + "id": "fls_rjzgpxt8z8x", + "link": "inline-assembly.html#fls_rjzgpxt8z8x", + "number": "22.4.2:11" }, { - "checksum": "46a2d9694956884e4ad1725c0e2505ade9aa39d6718a015d2850e0cfd1d66063", - "id": "fls_YnUsdSM4x9eq", - "link": "entities-and-resolution.html#fls_YnUsdSM4x9eq", - "number": "14.3:9" + "checksum": "8f07653c7f5ffded5888d86d7b90df75357f168c0520bdf55624bb2c836d2f9b", + "id": "fls_iqrjkvgae5k", + "link": "inline-assembly.html#fls_iqrjkvgae5k", + "number": "22.4.2:12" }, { - "checksum": "804ea247d1050feb630c47dee50038329c0f8fa4a1c7bb724eb1d19b29eaaaa0", - "id": "fls_iuzvtr3oax1o", - "link": "entities-and-resolution.html#fls_iuzvtr3oax1o", - "number": "14.3:10" + "checksum": "8f0cc6ef2c23c8cb4bee94667d1ec39ea29fbe2c83738e9a0a6911180130faee", + "id": "fls_tzb5diegx3d5", + "link": "inline-assembly.html#fls_tzb5diegx3d5", + "number": "22.4.2:13" }, { - "checksum": "0810d5dde86b8556c45ed507bfbec7f51260bd25badbaf62080b21443358840c", - "id": "fls_cw006jhlboa", - "link": "entities-and-resolution.html#fls_cw006jhlboa", - "number": "14.3:11" + "checksum": "540a3815a037e2019aa295eebe637bce2924911055ffd22655e0f7105361d716", + "id": "fls_82nia9oagat", + "link": "inline-assembly.html#fls_82nia9oagat", + "number": "22.4.2:14" }, { - "checksum": "0b5983245cd5346dfd1276ced7307c04d4d81717e265035c72319454fbdfac52", - "id": "fls_kv5bpq8rf1j9", - "link": "entities-and-resolution.html#fls_kv5bpq8rf1j9", - "number": "14.3:12" + "checksum": "4c30c61a09b304482477071c1e8940277819245caa2586bd24d5f47c8f0604ea", + "id": "fls_qg6wt4plwnw6", + "link": "inline-assembly.html#fls_qg6wt4plwnw6", + "number": "22.4.2:15" }, { - "checksum": "6b80947a0c4468e18fade3520519a9b79a5d2d8115001c1eb8bfc0a4a029399f", - "id": "fls_chtj3hcfe3ap", - "link": "entities-and-resolution.html#fls_chtj3hcfe3ap", - "number": "14.3:13" + "checksum": "7b42846b370887a3f35c3a27a083518bb3dc29223630fbcb50980f0b92d4c3c5", + "id": "fls_8ci8ukk25nz3", + "link": "inline-assembly.html#fls_8ci8ukk25nz3", + "number": "22.4.2:16" }, { - "checksum": "f525bc5d1cc46a6aa7cf36eff8be2a47a90c9803c8a06fe6f8dc345c61c9b080", - "id": "fls_wm61yeclairz", - "link": "entities-and-resolution.html#fls_wm61yeclairz", - "number": "14.3:14" + "checksum": "c180e76cf8759c8f1c126845c7c4691255b1fc7300dd6d998b1ec33d2ba5193a", + "id": "fls_ysbaz052rjg4", + "link": "inline-assembly.html#fls_ysbaz052rjg4", + "number": "22.4.2:17" }, { - "checksum": "34501af716aa34b93e15d351af04e08ef899126df7109cba1f2037f7ebaf9b3e", - "id": "fls_nRgjCLYZL3iX", - "link": "entities-and-resolution.html#fls_nRgjCLYZL3iX", - "number": "14.3:15" + "checksum": "2237d36080e7a184ba2787ef43ea4309766b3fe20fcd28e287f48d8467f79652", + "id": "fls_lbazk0g9r350", + "link": "inline-assembly.html#fls_lbazk0g9r350", + "number": "22.4.2:18" }, { - "checksum": "612a8b592294352e6c67e0fd45f33a2baa3584b57d904a270e78d4339281dd9e", - "id": "fls_tvvycup09b51", - "link": "entities-and-resolution.html#fls_tvvycup09b51", - "number": "14.3:16" + "checksum": "3a38d11b37270cb84ae0f2d6aeb76dd4852810ab70083e2f74a868de0b004dcb", + "id": "fls_2dui79hn30o7", + "link": "inline-assembly.html#fls_2dui79hn30o7", + "number": "22.4.2:19" }, { - "checksum": "6ea02148b0bd9c494f779eeaac0fa983285fd00082c3df4add60a7293f1b4d54", - "id": "fls_h2zikgmazoxx", - "link": "entities-and-resolution.html#fls_h2zikgmazoxx", - "number": "14.3:17" + "checksum": "913c7a58bd440f285a8792fb9cb87bfe92b9d9e3e781b519576338a87006b568", + "id": "fls_qzwyjj6xxwc2", + "link": "inline-assembly.html#fls_qzwyjj6xxwc2", + "number": "22.4.2:20" }, { - "checksum": "2356207dea29ed2d5aa6e5a78e4994316431b1fe5a8a50b76c7d4b8cdeddede4", - "id": "fls_nj7s6xmzx55f", - "link": "entities-and-resolution.html#fls_nj7s6xmzx55f", - "number": "14.3:18" + "checksum": "b999de4283114f3434ff6b2f3d644c97f9947cdb3682846f9e5cfe9198c41071", + "id": "fls_9flwdfh5crsk", + "link": "inline-assembly.html#fls_9flwdfh5crsk", + "number": "22.4.2:21" }, { - "checksum": "8ed372f7349a77552ebc1390e264ac1254ab102736d6e6455ffb3143c4602dbe", - "id": "fls_e65q3iz50j6a", - "link": "entities-and-resolution.html#fls_e65q3iz50j6a", - "number": "14.3:19" + "checksum": "e95b22baaaaccde95e5436616a10d509d3d62c095e320c2f3c8dd4e40946315a", + "id": "fls_u1c09ssrllil", + "link": "inline-assembly.html#fls_u1c09ssrllil", + "number": "22.4.2:22" }, { - "checksum": "038e052fffa92303139c1be17bb4f7521e552611f2647cf22270e8358b9553e4", - "id": "fls_Ai1jN5a8h3Dz", - "link": "entities-and-resolution.html#fls_Ai1jN5a8h3Dz", - "number": "14.3:20" + "checksum": "5cabacdc42396ecdc4239421e1c94751f7e18bae7137ce2e7a90b826dc77137d", + "id": "fls_tx58qbvh3jz3", + "link": "inline-assembly.html#fls_tx58qbvh3jz3", + "number": "22.4.2:23" }, { - "checksum": "af34b5b94f5c4f453bbbfd85b46861f49c8a88f06df9ae12aa2283441c4c8138", - "id": "fls_ybv0tdu7dnj5", - "link": "entities-and-resolution.html#fls_ybv0tdu7dnj5", - "number": "14.3:21" + "checksum": "1b9a50adc4326cc8e3aa3e0191f596d700af6e1ee878fcd463eddc88796901c9", + "id": "fls_anwe21ypcjws", + "link": "inline-assembly.html#fls_anwe21ypcjws", + "number": "22.4.2:24" }, { - "checksum": "87aea9dbee8a48f85557797bbc2d29f8e6fdb0dac7615ebc8e45ed28500441bc", - "id": "fls_qkYF2J7GVah8", - "link": "entities-and-resolution.html#fls_qkYF2J7GVah8", - "number": "14.3:22" + "checksum": "553eef4baa71f1f46de56c071450919bf2eca3dd6cf7bd9f4fde72bb42083db8", + "id": "fls_43rchr5ffxsv", + "link": "inline-assembly.html#fls_43rchr5ffxsv", + "number": "22.4.2:25" }, { - "checksum": "94b5df1f402d9ada3134d26046016aef437a8e86533892e27c5a99f4edf52a58", - "id": "fls_QjNQbQhUcRTT", - "link": "entities-and-resolution.html#fls_QjNQbQhUcRTT", - "number": "14.3:23" + "checksum": "f79e3715f39c38e8e39df76757e19cba8ddf802007d1b76f19778e8c66455608", + "id": "fls_rscmbo3kbrsm", + "link": "inline-assembly.html#fls_rscmbo3kbrsm", + "number": "22.4.2:26" }, { - "checksum": "8902e7d1f80cd0a6e4e560838c03d8a2949169ae5cf6ffc22f241884a23bdc84", - "id": "fls_7sm3206va03c", - "link": "entities-and-resolution.html#fls_7sm3206va03c", - "number": "14.3:24" + "checksum": "24edea8823258cf1aec3574d325ce870b217dc84940d5cbaef8363f52c84ce84", + "id": "fls_hnol9houwn1f", + "link": "inline-assembly.html#fls_hnol9houwn1f", + "number": "22.4.2:27" }, { - "checksum": "272e37c850f521889e6d9d7e6813f0a87f44a727cb59102b140179debf821a56", - "id": "fls_huynsyx13gsz", - "link": "entities-and-resolution.html#fls_huynsyx13gsz", - "number": "14.3:25" + "checksum": "ef6d7d774ab1ca21f1aef251798dada30ebf5aa565f69e642e4ce2ef193ab470", + "id": "fls_es1lo6siw702", + "link": "inline-assembly.html#fls_es1lo6siw702", + "number": "22.4.2:28" }, { - "checksum": "07fbe740bf19563ae613bfea97ef80ae7a163aeeb40db28860b3e3271f30dec9", - "id": "fls_RZvIsApi4WQm", - "link": "entities-and-resolution.html#fls_RZvIsApi4WQm", - "number": "14.3:26" + "checksum": "05610e4ed44e10cfa011be9ec110980564ae67ea3f1a345629bc1726e0fbd447", + "id": "fls_xlk7kd26j2rm", + "link": "inline-assembly.html#fls_xlk7kd26j2rm", + "number": "22.4.2:29" }, { - "checksum": "3f386dfca473cbbe5216f7579aabaa0cda4f6d9bfe53ab258a43c18168692f75", - "id": "fls_f1ciozzetj5a", - "link": "entities-and-resolution.html#fls_f1ciozzetj5a", - "number": "14.3:27" + "checksum": "c2122b8e33a87c74ed1103c4a38fb30ea601a8445ed9d99df70a31536b77d278", + "id": "fls_x9kaplz9g1z9", + "link": "inline-assembly.html#fls_x9kaplz9g1z9", + "number": "22.4.2:30" }, { - "checksum": "c3776124fbef0e532209f8f067a47e7af3a3bf7032ceeab5cfb896c7651d07dd", - "id": "fls_cy7vza3flqi9", - "link": "entities-and-resolution.html#fls_cy7vza3flqi9", - "number": "14.3:28" + "checksum": "3066b6559d893177f8936aea6fc3b373621f7ad26e0b80588ac312291a2c572a", + "id": "fls_928ermlgde11", + "link": "inline-assembly.html#fls_928ermlgde11", + "number": "22.4.2:31" }, { - "checksum": "acc7d0ee1b047d062435a8ce89e6a518f16f42bf9f2c5dfd6dfc5ff8b5ced03e", - "id": "fls_cul31g1kkz5c", - "link": "entities-and-resolution.html#fls_cul31g1kkz5c", - "number": "14.3:29" + "checksum": "db73f3b0c8c275ef4f5be8518eac860264aba6acc6f606edcf905e67a01f825a", + "id": "fls_49bkqmxwl0d2", + "link": "inline-assembly.html#fls_49bkqmxwl0d2", + "number": "22.4.2:32" }, { - "checksum": "3ad2063380470f0ebbf19e527f4669a78071b833fe64f5e0f1521729680bc477", - "id": "fls_no853u27p4f3", - "link": "entities-and-resolution.html#fls_no853u27p4f3", - "number": "14.3:30" + "checksum": "dea0ddd23f309d02462555b58271dfaff6e5958548830d4a0c6891eafc50b025", + "id": "fls_xlvkpe975b58", + "link": "inline-assembly.html#fls_xlvkpe975b58", + "number": "22.4.2:33" }, { - "checksum": "177724d6510ea45d50120fed6c28c92b69dca2fc3124541b67c96e933917bc0d", - "id": "fls_28c21rzc6rsp", - "link": "entities-and-resolution.html#fls_28c21rzc6rsp", - "number": "14.3:31" + "checksum": "7c4f40ae43ddedb5dc71bae0dff059a430abcc5ef5dc36e38ce185ef2631ff54", + "id": "fls_k3sy1ph0kvy", + "link": "inline-assembly.html#fls_k3sy1ph0kvy", + "number": "22.4.2:34" + }, + { + "checksum": "d14404ffbac7fa6b204f853222509a2a09e59f7bbd912577a5a7ff4aeff0d298", + "id": "fls_ku6noqc0poxq", + "link": "inline-assembly.html#fls_ku6noqc0poxq", + "number": "22.4.2:35" }, { - "checksum": "a16e3a0e1a36090a7b32f972287f4cdedbddc5e240a9b3c08929277f4696a99a", - "id": "fls_4s2n95h4rd1q", - "link": "entities-and-resolution.html#fls_4s2n95h4rd1q", - "number": "14.3:32" + "checksum": "251021164436aa4b642498f6a3d30aa2af316742c592de939c3153c932ff6d94", + "id": "fls_hny0patop479", + "link": "inline-assembly.html#fls_hny0patop479", + "number": "22.4.2:36" }, { - "checksum": "3d7face163b14a1f83f142fc864a2664a8c9ff279c76d924be5a0d78c9df1a62", - "id": "fls_ojdntg5i79pb", - "link": "entities-and-resolution.html#fls_ojdntg5i79pb", - "number": "14.3:33" - } - ], - "title": "Paths" - }, - { - "id": "fls_izl8iuhoz9e0", - "informational": false, - "link": "entities-and-resolution.html#scopes", - "number": "14.4", - "paragraphs": [ - { - "checksum": "43517ff3b8cb455dc43e97cf0f608edaefcb7c7be9cbed87555264c018d5f507", - "id": "fls_5x5xykocwyiy", - "link": "entities-and-resolution.html#fls_5x5xykocwyiy", - "number": "14.4:1" - } - ], - "title": "Scopes" - }, - { - "id": "fls_6ozthochxz1i", - "informational": false, - "link": "entities-and-resolution.html#binding-scopes", - "number": "14.4.1", - "paragraphs": [ + "checksum": "e493e8ca70b894b223615529525c358bb19c570024d3de0d8bd36a9ffa32c238", + "id": "fls_jm61m237cww", + "link": "inline-assembly.html#fls_jm61m237cww", + "number": "22.4.2:37" + }, { - "checksum": "1c091220f546537aa0376292e1056112fdb4f6accdb5704f2b02a353e1dc7f57", - "id": "fls_ncg9etb3x7k0", - "link": "entities-and-resolution.html#fls_ncg9etb3x7k0", - "number": "14.4.1:1" + "checksum": "0830b44abd4b4c395984463fae9b127ebdbe221c966d5585ce9c6279c5e1253a", + "id": "fls_wvje5eua16xm", + "link": "inline-assembly.html#fls_wvje5eua16xm", + "number": "22.4.2:38" }, { - "checksum": "28f8cbea9a3c14d13f591f4f492b25d66fb3fef3471e02a0bce1eb50f219a2fa", - "id": "fls_u52mx4xw8zod", - "link": "entities-and-resolution.html#fls_u52mx4xw8zod", - "number": "14.4.1:2" + "checksum": "43d9d2ab6988505415d82f7db889a536048c143771389684c62cc7e58d4650fd", + "id": "fls_frxn2f6v584d", + "link": "inline-assembly.html#fls_frxn2f6v584d", + "number": "22.4.2:39" }, { - "checksum": "38c14cea741975dfcf0745579b4485aa6e7fb1819efc47af41906460216ba171", - "id": "fls_t9mk8kasobea", - "link": "entities-and-resolution.html#fls_t9mk8kasobea", - "number": "14.4.1:3" + "checksum": "abea834cf3c23b93b75de1a6678a732d14decb29a408e444d3a290397ea29134", + "id": "fls_x6azw3td92b3", + "link": "inline-assembly.html#fls_x6azw3td92b3", + "number": "22.4.2:40" }, { - "checksum": "c828a2288a376f29a23988cc6c36a6e201e5de5b00188c8dae26d122a334a4e2", - "id": "fls_h9cvs854ae34", - "link": "entities-and-resolution.html#fls_h9cvs854ae34", - "number": "14.4.1:4" + "checksum": "b5e53d8a4f2d5d92ff52782f5c95785d1d304ad5347ceb94c72d18c10a07aad1", + "id": "fls_twerrggztho5", + "link": "inline-assembly.html#fls_twerrggztho5", + "number": "22.4.2:41" }, { - "checksum": "9738803f2e1cc4b49daa374b470dec9c822944abdddc2d5553cc3a7d2ff81962", - "id": "fls_vl1qk0odouyb", - "link": "entities-and-resolution.html#fls_vl1qk0odouyb", - "number": "14.4.1:5" + "checksum": "538abae4915a252a44ef56d9fd5d1e9b54e0a5256c3493cfd1ed292abb455257", + "id": "fls_s4cbxrc4ijyp", + "link": "inline-assembly.html#fls_s4cbxrc4ijyp", + "number": "22.4.2:42" }, { - "checksum": "704d759b8e808aade84bf9d91c6547afd260d382951c9e63b34d612e46adb44a", - "id": "fls_74nk389rk075", - "link": "entities-and-resolution.html#fls_74nk389rk075", - "number": "14.4.1:6" + "checksum": "e12629bff77cc49dd0fd9697926e543b3db783726e728995ab6dcf3eb21b6637", + "id": "fls_2hdsgqko25l5", + "link": "inline-assembly.html#fls_2hdsgqko25l5", + "number": "22.4.2:43" }, { - "checksum": "01919a0675796333a57c77739f2e33e59788ca34fb915657ee4300af13e689ff", - "id": "fls_xbnki64un70v", - "link": "entities-and-resolution.html#fls_xbnki64un70v", - "number": "14.4.1:7" + "checksum": "3906f6daca8c9ecacb9ceb21d3511d2db168376916cb202a32f59cbac41bd889", + "id": "fls_5tzwwove8mgq", + "link": "inline-assembly.html#fls_5tzwwove8mgq", + "number": "22.4.2:44" }, { - "checksum": "e8ab2db877edb9f626751999aac1bbfe95531e7b09745bf82c87979427adffa5", - "id": "fls_eBacCVlDaKYK", - "link": "entities-and-resolution.html#fls_eBacCVlDaKYK", - "number": "14.4.1:8" + "checksum": "0706a9b6e7d90d7b3533057e27539a37cf1b148a53e539ae61ad9dafdd8286f0", + "id": "fls_e6nvq9xygvh", + "link": "inline-assembly.html#fls_e6nvq9xygvh", + "number": "22.4.2:45" } ], - "title": "Binding Scopes" + "title": "Directive Support" }, { - "id": "fls_ftphlagzd2te", + "id": "fls_a3joqzqp1v9d", "informational": false, - "link": "entities-and-resolution.html#generic-parameter-scope", - "number": "14.4.2", + "link": "inline-assembly.html#abi-clobbers", + "number": "22.5", "paragraphs": [ { - "checksum": "d584c25b23d784679f49bee07542de8692b945ee1f537fbbfe6de0748ac4fb71", - "id": "fls_amoh8r4gghyj", - "link": "entities-and-resolution.html#fls_amoh8r4gghyj", - "number": "14.4.2:1" + "checksum": "059896cc2c79a9ed0b43866fe3d923d7239e3a67a128c7e89e6869411edccc1c", + "id": "fls_xa11ggykg0sh", + "link": "inline-assembly.html#fls_xa11ggykg0sh", + "number": "22.5:1" }, { - "checksum": "8279dc6a8b235106f42ec6b4f7ac14f3cbc73a5387e8050967651ecbc6aba0b0", - "id": "fls_6o38qhbna46z", - "link": "entities-and-resolution.html#fls_6o38qhbna46z", - "number": "14.4.2:2" + "checksum": "25daebb16bac887b6a619f1c15055cf292b836403ab183382752976a02a4257c", + "id": "fls_e43sj9inlsym", + "link": "inline-assembly.html#fls_e43sj9inlsym", + "number": "22.5:2" }, { - "checksum": "1bf64d3433e9732df7b69f03bc39b74cb23d3c97ec46beaacab32fd3c95b7311", - "id": "fls_jqevvpndxzdz", - "link": "entities-and-resolution.html#fls_jqevvpndxzdz", - "number": "14.4.2:3" + "checksum": "3151135e30ed0eaa0046c0a2c933b3df4524b6ebd38636be511d97edf1093142", + "id": "fls_gq2khxl1hixg", + "link": "inline-assembly.html#fls_gq2khxl1hixg", + "number": "22.5:3" }, { - "checksum": "b58069d3632126cd1d327344ae6ddc3ea80724b6edb47aae53f655412afbebe2", - "id": "fls_t9ztg017itkp", - "link": "entities-and-resolution.html#fls_t9ztg017itkp", - "number": "14.4.2:4" + "checksum": "9d980c1d9a3bd3147ff52fa56ba95e975ddb9eec707b8dcc601b03993a6bfbc5", + "id": "fls_o2qn842y0vvc", + "link": "inline-assembly.html#fls_o2qn842y0vvc", + "number": "22.5:4" }, { - "checksum": "ba9481f2a0dab8367b56e1986f679ff8388f4d6fdbd58144bd8fec7d02d50597", - "id": "fls_pmo939jw9m1m", - "link": "entities-and-resolution.html#fls_pmo939jw9m1m", - "number": "14.4.2:5" + "checksum": "c9961878c937f4ee5de1788b5a9a58b77076276170d875a58a526fa90be99665", + "id": "fls_msysjt5m2941", + "link": "inline-assembly.html#fls_msysjt5m2941", + "number": "22.5:5" }, { - "checksum": "5be283d945e26887b35476d71c9983067056e07394062f8e074c437f23e48e7c", - "id": "fls_67dtv1z3arbl", - "link": "entities-and-resolution.html#fls_67dtv1z3arbl", - "number": "14.4.2:6" + "checksum": "e99e85bd28b33b69146abf6665f55cd679b81c2d26e54ed077ca48c986466401", + "id": "fls_vyhl5po6pl4x", + "link": "inline-assembly.html#fls_vyhl5po6pl4x", + "number": "22.5:6" }, { - "checksum": "b51781ef00cf909b9c6f954d1f3ad0fcde40305f987b06a26fde3bc2fce0cebd", - "id": "fls_y8j4isk9libl", - "link": "entities-and-resolution.html#fls_y8j4isk9libl", - "number": "14.4.2:7" + "checksum": "4a320730472da6e94d6e7eb0e60110ce54e50eaf2bd689ad2d9e77d9174281bb", + "id": "fls_d1be48ik4a8", + "link": "inline-assembly.html#fls_d1be48ik4a8", + "number": "22.5:7" }, { - "checksum": "d80167a0dcd4ced3ddd314d967d1072c823cab9fb8cd19ea7168bce3bda55d70", - "id": "fls_ow5ih7q3xxfx", - "link": "entities-and-resolution.html#fls_ow5ih7q3xxfx", - "number": "14.4.2:8" + "checksum": "c7a214a478ecba87863b838e3cc1dc1e0211c41d45b466650cf9e1ee4af781b1", + "id": "fls_49pus6qqmf72", + "link": "inline-assembly.html#fls_49pus6qqmf72", + "number": "22.5:8" }, { - "checksum": "8ba2db48a9fbc5cd1345c45bbc4ffcce13b8b4fd94cb3d312f53128b4459aaf9", - "id": "fls_h9rpwxpz72v0", - "link": "entities-and-resolution.html#fls_h9rpwxpz72v0", - "number": "14.4.2:9" + "checksum": "90b9e3c561e86b008b46a75c32a8381e977ed63097178f6649d726503e1e5aa6", + "id": "fls_tc727ietnawz", + "link": "inline-assembly.html#fls_tc727ietnawz", + "number": "22.5:9" }, { - "checksum": "6da6b05924e3ca7b78a52d8b6bebba58a20200ccc2beec4a64d3e81ad77da76f", - "id": "fls_3qm3vh97bvpb", - "link": "entities-and-resolution.html#fls_3qm3vh97bvpb", - "number": "14.4.2:10" + "checksum": "02ff55c63d8be9b67db3379370d75d0fc64e42c2a63af002328807ef6040df7d", + "id": "fls_6jgsmfvww667", + "link": "inline-assembly.html#fls_6jgsmfvww667", + "number": "22.5:10" }, { - "checksum": "e918fbab918fabdf9fdbc58cfc78079484a925ab64ba7147cf502d5a1ad77c81", - "id": "fls_xuxbpv5b2ym9", - "link": "entities-and-resolution.html#fls_xuxbpv5b2ym9", - "number": "14.4.2:11" + "checksum": "544d1d040690c1ef7a2b666dc2a809d33f42b0b1114f00dc05d36f203757cfd5", + "id": "fls_gvzoq5mqwjx", + "link": "inline-assembly.html#fls_gvzoq5mqwjx", + "number": "22.5:11" }, { - "checksum": "689d6488c7516efa05b316fc2b100ccd54fc41bf3bac4b72f0868c6a4f251d1d", - "id": "fls_95z5mytvfjia", - "link": "entities-and-resolution.html#fls_95z5mytvfjia", - "number": "14.4.2:12" + "checksum": "61e5e0531c0e617d3d424dfdfc8416bbf5aeb5313b8b85f8073e6fac4da59243", + "id": "fls_bnwzzpcmiero", + "link": "inline-assembly.html#fls_bnwzzpcmiero", + "number": "22.5:12" } ], - "title": "Generic Parameter Scope" + "title": "ABI Clobbers" }, { - "id": "fls_m0z7omni9hp0", + "id": "fls_ylli0ortyegk", "informational": false, - "link": "entities-and-resolution.html#item-scope", - "number": "14.4.3", + "link": "inline-assembly.html#assembly-options", + "number": "22.6", "paragraphs": [ { - "checksum": "fb831c1ad14c0df880fe0e2965047fd726fb0ed1e305c93656d65e870501e7cd", - "id": "fls_p5o243hhe1y3", - "link": "entities-and-resolution.html#fls_p5o243hhe1y3", - "number": "14.4.3:1" + "checksum": "8a3a017f2afb0587c55dfb386c7e5d180d3157b04f01968b1eb489ce80ca469f", + "id": "fls_i21l6t3vn95t", + "link": "inline-assembly.html#fls_i21l6t3vn95t", + "number": "22.6:1" }, { - "checksum": "25bc4c9ccd15441cb75deda5c2ca31e035e40a8231453a5c22e52e2646c44186", - "id": "fls_huvo0mp2i6fb", - "link": "entities-and-resolution.html#fls_huvo0mp2i6fb", - "number": "14.4.3:2" + "checksum": "bab672735ba4bd11338fe18ef154010c328d60c6904b856a7e71670295861eb9", + "id": "fls_g09kmp2a04g9", + "link": "inline-assembly.html#fls_g09kmp2a04g9", + "number": "22.6:2" }, { - "checksum": "21fe2e6b23a8c76e0b909cb3bb66d87811b819ce8cae90179b3d73eceff47c01", - "id": "fls_x8r0oppuc1t6", - "link": "entities-and-resolution.html#fls_x8r0oppuc1t6", - "number": "14.4.3:3" - } - ], - "title": "Item Scope" - }, - { - "id": "fls_769b4p8v3cwu", - "informational": false, - "link": "entities-and-resolution.html#label-scope", - "number": "14.4.4", - "paragraphs": [ - { - "checksum": "521fb43b24f85cbd6c81cae0c33b399291a86e51201554028deeb6b63c47e26a", - "id": "fls_96kczd4zhpco", - "link": "entities-and-resolution.html#fls_96kczd4zhpco", - "number": "14.4.4:1" + "checksum": "387233953cd06bc333811fa124272c964f24902926b1502b30fd6a1be40fc044", + "id": "fls_quer8ltdwnf2", + "link": "inline-assembly.html#fls_quer8ltdwnf2", + "number": "22.6:3" }, { - "checksum": "f9d012717e48391d6a169f0d2134dd95a7d39ab75ea97dbf1dff6d58431a9ba5", - "id": "fls_8sevg1sa82h4", - "link": "entities-and-resolution.html#fls_8sevg1sa82h4", - "number": "14.4.4:2" + "checksum": "36d8b0665a541f55861ac8eec58788b1300271d197914bcef79a5366d764a54e", + "id": "fls_5wpgqpcm1v40", + "link": "inline-assembly.html#fls_5wpgqpcm1v40", + "number": "22.6:4" }, { - "checksum": "688be0bee74edd58c2e2e383d5686e45abd8a325780d2d340605d0b7b3af96bc", - "id": "fls_ep5smja1rxdv", - "link": "entities-and-resolution.html#fls_ep5smja1rxdv", - "number": "14.4.4:3" - } - ], - "title": "Label Scope" - }, - { - "id": "fls_kgbi26212eof", - "informational": false, - "link": "entities-and-resolution.html#self-scope", - "number": "14.4.5", - "paragraphs": [ + "checksum": "81f05b034ad37fdb0de0e9a2242a963b2cfd4886a5fa12d422e3944d67f850a4", + "id": "fls_ejuap3kkvs57", + "link": "inline-assembly.html#fls_ejuap3kkvs57", + "number": "22.6:5" + }, { - "checksum": "46ec90b1b86c7151216c55420994d294c6454cf9a68db0d8835a9fb9b50e5984", - "id": "fls_kgt81m4f72ne", - "link": "entities-and-resolution.html#fls_kgt81m4f72ne", - "number": "14.4.5:1" + "checksum": "e2e32a90c459f6211a5915bd66ddf72c97e86792880aa7a5af68a1028b4f1c27", + "id": "fls_1nopbk5bkeqm", + "link": "inline-assembly.html#fls_1nopbk5bkeqm", + "number": "22.6:6" }, { - "checksum": "9ea7a212c6751d354e97c5eaa4a920477f382ff731fbf5eaf9539e771f658286", - "id": "fls_kxdwq4b136tl", - "link": "entities-and-resolution.html#fls_kxdwq4b136tl", - "number": "14.4.5:2" + "checksum": "7ef8c5e5436b9b3ccf1d5ddd8f024651de630ff3a775b15cfb2dbec38833c4dc", + "id": "fls_e5b1mp3byll2", + "link": "inline-assembly.html#fls_e5b1mp3byll2", + "number": "22.6:7" }, { - "checksum": "d453f4e8c7603cc4a3af26b98e29a3619d3e4151e2257de52a6d49f61e7ac8d1", - "id": "fls_nf4g82gi12ij", - "link": "entities-and-resolution.html#fls_nf4g82gi12ij", - "number": "14.4.5:3" + "checksum": "6c6abff15d559e67cfbee302183c6b6cd9e2d0b27ef17f3fc6c2d2b1b8dc247d", + "id": "fls_2gf4wemrzaae", + "link": "inline-assembly.html#fls_2gf4wemrzaae", + "number": "22.6:8" }, { - "checksum": "f92935a62e26ce498f4d124d356e3e78782eef0ef6a3740c3cf24f7b09250efe", - "id": "fls_dy4gyepebe7b", - "link": "entities-and-resolution.html#fls_dy4gyepebe7b", - "number": "14.4.5:4" + "checksum": "9a59466198ee2c628bf21cf3451d438f7b681c05bff38179d054b394ca87b055", + "id": "fls_5ebifab8dhy", + "link": "inline-assembly.html#fls_5ebifab8dhy", + "number": "22.6:9" }, { - "checksum": "3554e24b5a9b2499a683328f0576d51a86bd26bf117490738edb7ae2e7904a1c", - "id": "fls_cha4ddwfqwvj", - "link": "entities-and-resolution.html#fls_cha4ddwfqwvj", - "number": "14.4.5:5" + "checksum": "3812da6e1f78fd4f04190a7f0b03c53c355e7fd9497bdbb2cc363720db6d7f05", + "id": "fls_ae2x4ho3i0zr", + "link": "inline-assembly.html#fls_ae2x4ho3i0zr", + "number": "22.6:10" }, { - "checksum": "2e5e179e6007b149b3d35850933d604403fbaf8f3cce6fdbd380b2f719d27cd8", - "id": "fls_ql4i021ut2n8", - "link": "entities-and-resolution.html#fls_ql4i021ut2n8", - "number": "14.4.5:6" + "checksum": "9227d85264bddc464ffe9527094d6daa871bb3d8c05d00cb821528e1c241cb04", + "id": "fls_188ib65a1z36", + "link": "inline-assembly.html#fls_188ib65a1z36", + "number": "22.6:11" }, { - "checksum": "6c12e7861360d4792869765a4ef4afc10ef9b835148fd7c3b5b6a78c61e470db", - "id": "fls_mj9vlxnf44oi", - "link": "entities-and-resolution.html#fls_mj9vlxnf44oi", - "number": "14.4.5:7" - } - ], - "title": "Self Scope" - }, - { - "id": "fls_octf6sf7yso", - "informational": false, - "link": "entities-and-resolution.html#textual-macro-scope", - "number": "14.4.6", - "paragraphs": [ + "checksum": "9947ef35f98d51e42642de4b89c7ff24c303708a6fc048d6baf420acd0c2e144", + "id": "fls_ia3cg424d601", + "link": "inline-assembly.html#fls_ia3cg424d601", + "number": "22.6:12" + }, { - "checksum": "9d8d566bf0cc9c442a6f8caa9750acaad5fd97b2051978a07e83ecde9be6c8ed", - "id": "fls_xkh8cqubhxad", - "link": "entities-and-resolution.html#fls_xkh8cqubhxad", - "number": "14.4.6:1" + "checksum": "1f08df8b8d9ead71e6d2a2b97ce398fa198247177be827becf8b36abac2eec70", + "id": "fls_j09bo53i5n69", + "link": "inline-assembly.html#fls_j09bo53i5n69", + "number": "22.6:13" }, { - "checksum": "20ccfad5b0eb356d3674f0df4b46713ca8de9f5e25b83f9ec21fa2ff02dc92cc", - "id": "fls_iec3otx863yp", - "link": "entities-and-resolution.html#fls_iec3otx863yp", - "number": "14.4.6:2" + "checksum": "6156a4e0aabe75163b3c333a7cc831ca44acd71d75015372430ca8c0d1ca7495", + "id": "fls_eka6chp3hapa", + "link": "inline-assembly.html#fls_eka6chp3hapa", + "number": "22.6:14" }, { - "checksum": "fb291d2a2d9aa2c05d01d7c0697cf5633d229a13e733f5c63cf3880d0e198fe7", - "id": "fls_cbfuh9y87y6i", - "link": "entities-and-resolution.html#fls_cbfuh9y87y6i", - "number": "14.4.6:3" - } - ], - "title": "Textual Macro Scope" - }, - { - "id": "fls_lnpyb285qdiy", - "informational": false, - "link": "entities-and-resolution.html#scope-hierarchy", - "number": "14.4.7", - "paragraphs": [ + "checksum": "6e26d5ced9abcc14efda425d56724492c38c2a89dfaaf7cc53fdcff38153f695", + "id": "fls_nszx1gllufi2", + "link": "inline-assembly.html#fls_nszx1gllufi2", + "number": "22.6:15" + }, { - "checksum": "f551c6d52274528f4d39e9f7ebb33850b7a9886014adda99bd7dfdea6a937703", - "id": "fls_4o7vfo6v39l7", - "link": "entities-and-resolution.html#fls_4o7vfo6v39l7", - "number": "14.4.7:1" + "checksum": "2d7523471d7b595463ca7ca9c55972764ee91e9b48fd28620896baccbfe97aa7", + "id": "fls_d169ppna563c", + "link": "inline-assembly.html#fls_d169ppna563c", + "number": "22.6:16" }, { - "checksum": "4d1555490789b23e0b29f3539cc6b17f32c58c25a752e2dff0ab22d6d85ba927", - "id": "fls_ns4eog3od4kw", - "link": "entities-and-resolution.html#fls_ns4eog3od4kw", - "number": "14.4.7:2" + "checksum": "44e6f48245cc97766452666dcc62fa1d73508669f7a630f942edc2e6047d67ff", + "id": "fls_h8549stij7pj", + "link": "inline-assembly.html#fls_h8549stij7pj", + "number": "22.6:17" }, { - "checksum": "0c3ab46d2d4bbdfb7f8aa15e861edba999dcd3ab2c8247042b749fdfea5d82b2", - "id": "fls_kqmykyzdb1k6", - "link": "entities-and-resolution.html#fls_kqmykyzdb1k6", - "number": "14.4.7:3" + "checksum": "b7cabc7b0d3991a23cd2b6064e873e856d12f722b48c78eb4f1e5c68cb9e9fb9", + "id": "fls_2drikpht6md9", + "link": "inline-assembly.html#fls_2drikpht6md9", + "number": "22.6:18" }, { - "checksum": "a879143703b2ebbf1c26fff249edc2ee837f8d4c7b131b30fc5b2da7cb149c8b", - "id": "fls_g86d5v14sxxv", - "link": "entities-and-resolution.html#fls_g86d5v14sxxv", - "number": "14.4.7:4" + "checksum": "87fde9a65891debe011e4dabc743129a995adf7c4605b39913aa4478ab811aab", + "id": "fls_x66j1cn6zi6p", + "link": "inline-assembly.html#fls_x66j1cn6zi6p", + "number": "22.6:19" }, { - "checksum": "e3f691f921462849f2125bb6f79621259b85389c58f9db3809ce843964ba6095", - "id": "fls_ldwencd8zp9a", - "link": "entities-and-resolution.html#fls_ldwencd8zp9a", - "number": "14.4.7:5" + "checksum": "e09645a3d913b01ba7b2da0538ca2a8cf2971062f274bbb7b07eb2fcbdf50eb6", + "id": "fls_ikwbu1ho33is", + "link": "inline-assembly.html#fls_ikwbu1ho33is", + "number": "22.6:20" }, { - "checksum": "0486289257a5a1890cd132dfa6c3f59e5fddf88be745323f21a5d89198421ac0", - "id": "fls_jz7hgkvocc9r", - "link": "entities-and-resolution.html#fls_jz7hgkvocc9r", - "number": "14.4.7:6" + "checksum": "2e64f7a7609904f62a789b1086409483bd975bfc91a04b82c0b5263044395404", + "id": "fls_nf0h9crdzhfg", + "link": "inline-assembly.html#fls_nf0h9crdzhfg", + "number": "22.6:21" }, { - "checksum": "5552d190c3a9cad12620fa1208ea5d3f5618d642ab37332f58d35c9d58779495", - "id": "fls_p4g8sxba7at9", - "link": "entities-and-resolution.html#fls_p4g8sxba7at9", - "number": "14.4.7:7" + "checksum": "ec4f1cc8bf22f706c2134897ccf8d68dff4c3a033f4ca1578e692769fe6fa36f", + "id": "fls_wh0wasawjj5s", + "link": "inline-assembly.html#fls_wh0wasawjj5s", + "number": "22.6:22" }, { - "checksum": "b37222b3100e2b80b7b034edb7a76cc840d78dd0fe191e49a45846b0e6903d8d", - "id": "fls_d1cp5pt5wn0z", - "link": "entities-and-resolution.html#fls_d1cp5pt5wn0z", - "number": "14.4.7:8" + "checksum": "3a414e98a1f7d173475399458067467182305c436b62978e990ae3071bb8dd27", + "id": "fls_s0ivlbjefh1u", + "link": "inline-assembly.html#fls_s0ivlbjefh1u", + "number": "22.6:23" + } + ], + "title": "Assembly Options" + }, + { + "id": "fls_qezwyridmjob", + "informational": false, + "link": "inline-assembly.html#macros-asm-global-asm-and-naked-asm", + "number": "22.7", + "paragraphs": [ + { + "checksum": "8a46f8db9ec5bee19e7fe7d419aade6653be1d595cc0b0e03910ea2632034b58", + "id": "fls_ecteot716j8j", + "link": "inline-assembly.html#fls_ecteot716j8j", + "number": "22.7:1" }, { - "checksum": "f2348baa759c683ef36d18d117184e9e34670dd0247232e3c9765ce2bd5acf41", - "id": "fls_ibmm8y748z4", - "link": "entities-and-resolution.html#fls_ibmm8y748z4", - "number": "14.4.7:9" + "checksum": "413125c0a5130dec6c099b6fbdc521c30f5edd1eed9330ba3453c48606ab9e60", + "id": "fls_tKj18krZ1pSt", + "link": "inline-assembly.html#fls_tKj18krZ1pSt", + "number": "22.7:2" }, { - "checksum": "3b60014467b97b96f55006cc958d04dba76f07721782eae6af5f4185ef0b50ed", - "id": "fls_39011vsy2vxx", - "link": "entities-and-resolution.html#fls_39011vsy2vxx", - "number": "14.4.7:10" + "checksum": "5d11d62e1e8b0838d243aad0ca1b6d984c7ecdd5697fb0fda10f24de8da95537", + "id": "fls_nLBhw2w6uznH", + "link": "inline-assembly.html#fls_nLBhw2w6uznH", + "number": "22.7:3" }, { - "checksum": "ee8879063f069e206d0ee79c4f20ae8cdc570027eeec9cd9ba5327ad2ecc4697", - "id": "fls_m81hyd154yun", - "link": "entities-and-resolution.html#fls_m81hyd154yun", - "number": "14.4.7:11" + "checksum": "29eb3c8d783230dd65c04e22cfcd393f3f1112155a70f3cb220df3d03a0ce6cf", + "id": "fls_cTEiqjf6haEg", + "link": "inline-assembly.html#fls_cTEiqjf6haEg", + "number": "22.7:4" }, { - "checksum": "92bfa8ddd580edf9895e16b361150c8c3d0cdd1c9ccde8524f7e6451e4e7fc64", - "id": "fls_fvgzmsaox4z3", - "link": "entities-and-resolution.html#fls_fvgzmsaox4z3", - "number": "14.4.7:12" + "checksum": "95dbbdf815af777d799412933cfb1c6139d1a5a0618aca24c1ce9095e8a3bf75", + "id": "fls_1ikzov7cxic1", + "link": "inline-assembly.html#fls_1ikzov7cxic1", + "number": "22.7:5" }, { - "checksum": "be583136a634bdd26f4a80930ae3ca6dbbdd6e0358978bd6923a42e1c84b0da7", - "id": "fls_rj8uld11o1br", - "link": "entities-and-resolution.html#fls_rj8uld11o1br", - "number": "14.4.7:13" + "checksum": "8b4f18328e73d75d6d74d43fa45304ecad3929469d9cfbf55a766168dac286fe", + "id": "fls_4lb6yh12w1cv", + "link": "inline-assembly.html#fls_4lb6yh12w1cv", + "number": "22.7:6" }, { - "checksum": "628e0a86e2c9490c0f27ad412acf3bfcaebb23591129c816d0863d06c698ea97", - "id": "fls_hyp4dnpqe620", - "link": "entities-and-resolution.html#fls_hyp4dnpqe620", - "number": "14.4.7:14" + "checksum": "f1285396b9e871a470e6c99fcee2ba4fee44f7c1c1494d2d7504854d586374ff", + "id": "fls_tgzga1lanfuo", + "link": "inline-assembly.html#fls_tgzga1lanfuo", + "number": "22.7:7" }, { - "checksum": "c3092ffb4d6f8e51556d11db102ff26ff4a26fcc6d578001ab2367f0466f8681", - "id": "fls_zgied4qysk2a", - "link": "entities-and-resolution.html#fls_zgied4qysk2a", - "number": "14.4.7:15" + "checksum": "cef34131dd6ae60d425b87a2ac6d953390e2dec00e679df1e058873e45adaf2a", + "id": "fls_nfkbvs86d6kz", + "link": "inline-assembly.html#fls_nfkbvs86d6kz", + "number": "22.7:8" }, { - "checksum": "ef0cd3b9e3e74fb06c3b65260e11dad890b4f75033c75029d59385ec3657f7b4", - "id": "fls_cn6dzmrxdp1w", - "link": "entities-and-resolution.html#fls_cn6dzmrxdp1w", - "number": "14.4.7:16" + "checksum": "04fe361f7c1056d577d0cd246f23b6ed603ea7f7dc710b06089a1dc2c44649eb", + "id": "fls_PEoOGTBjuEQc", + "link": "inline-assembly.html#fls_PEoOGTBjuEQc", + "number": "22.7:9" }, { - "checksum": "b147985d5033458a477fc80cfbdbe97b50bfe9cba065f8e54ce66e21162aa93b", - "id": "fls_9n7m0tv7w2np", - "link": "entities-and-resolution.html#fls_9n7m0tv7w2np", - "number": "14.4.7:17" + "checksum": "9c4853b2d65ce161746b9b6388b8f200ee318204f2714bf9e78e14123dc45f62", + "id": "fls_vcB5xwgD6Ign", + "link": "inline-assembly.html#fls_vcB5xwgD6Ign", + "number": "22.7:10" }, { - "checksum": "f72b277b5c1c9727052cdaaee9b8c8db7db662bebc2b7e7059b6af7aed73da62", - "id": "fls_sj2ivbf2l2dp", - "link": "entities-and-resolution.html#fls_sj2ivbf2l2dp", - "number": "14.4.7:18" + "checksum": "76c9be8a2c13f41d46c1674de81af3faa54bf6a070033c81d62a9c8d437ac6ed", + "id": "fls_98vyqh9bzigx", + "link": "inline-assembly.html#fls_98vyqh9bzigx", + "number": "22.7:11" }, { - "checksum": "86db3013b3c7a67a2925b579f72ceec4124fd83d53e5dc632e4b57d7cc2eaf3b", - "id": "fls_cejfio3ddy0j", - "link": "entities-and-resolution.html#fls_cejfio3ddy0j", - "number": "14.4.7:19" + "checksum": "29bb7394c69509f75799537c989b895449d8782786410554e4b948bd0a38ada0", + "id": "fls_ppnj8bcncdp9", + "link": "inline-assembly.html#fls_ppnj8bcncdp9", + "number": "22.7:12" }, { - "checksum": "c2e9b4fad1e4fc3b81216128713d9fd685d26f561850a43261e300f242650592", - "id": "fls_j3rot386teec", - "link": "entities-and-resolution.html#fls_j3rot386teec", - "number": "14.4.7:20" + "checksum": "fb17b760d5c76e48d93d08f1722c8da1fd927224135f46c206a4d3fe54d4a55d", + "id": "fls_wmay1vd8u0da", + "link": "inline-assembly.html#fls_wmay1vd8u0da", + "number": "22.7:13" }, { - "checksum": "f953ad67423f37e5b897c8c6668894471626c602b1893f1d007a340ddedbecbd", - "id": "fls_nuobrpnymym1", - "link": "entities-and-resolution.html#fls_nuobrpnymym1", - "number": "14.4.7:21" + "checksum": "f1479feedda5b57e1e5936f05df2da01ee75eb433febd48f24327a86fb66916e", + "id": "fls_e613hpr50t9", + "link": "inline-assembly.html#fls_e613hpr50t9", + "number": "22.7:14" }, { - "checksum": "f961d6fe49562f6bd683b5e7dac9856e6b65949cdfe488452a9af791a1a20a38", - "id": "fls_r0x9sw7dwnww", - "link": "entities-and-resolution.html#fls_r0x9sw7dwnww", - "number": "14.4.7:22" - }, + "checksum": "ce7ddc33151dcfba8a36c91bc23c8438759f7fce66fff26d097a1d2838d4028e", + "id": "fls_bic6iyd1nvfm", + "link": "inline-assembly.html#fls_bic6iyd1nvfm", + "number": "22.7:15" + } + ], + "title": "Macros: asm, global_asm, and naked_asm" + }, + { + "id": "fls_mw7mth5ooeq1", + "informational": false, + "link": "inline-assembly.html#label-block", + "number": "22.8", + "paragraphs": [ { - "checksum": "0132fa4a1bb8914b9f99869812101355d04a8da7aad46c2a3fdb0838b020ca97", - "id": "fls_ve7svuy7xvh0", - "link": "entities-and-resolution.html#fls_ve7svuy7xvh0", - "number": "14.4.7:23" + "checksum": "db39466ade2055d7d33326432bffe9094fa1a564ec801823984b2b3a1812277f", + "id": "fls_ZKMMqhBRe5lJ", + "link": "inline-assembly.html#fls_ZKMMqhBRe5lJ", + "number": "22.8:1" }, { - "checksum": "ab92461a17e2778df037251723d9e8305445c141efe44b6ca98f7ddd5804cbe9", - "id": "fls_pvfqhtts3qsa", - "link": "entities-and-resolution.html#fls_pvfqhtts3qsa", - "number": "14.4.7:24" + "checksum": "4d06b8a41f8bf3e343ea5d02e9d220c061982d3bb9faefce760dcca123f89624", + "id": "fls_vJT3iAG1ZLh3", + "link": "inline-assembly.html#fls_vJT3iAG1ZLh3", + "number": "22.8:2" }, { - "checksum": "08f70ffd77b18c260a9e349a49a29aed866ef6fa09e0df2aa9f1a8228046edc8", - "id": "fls_9k9hourczbv7", - "link": "entities-and-resolution.html#fls_9k9hourczbv7", - "number": "14.4.7:25" + "checksum": "b41b66d98778917ff4bf18fd633771bd2b6b3e3a71d1d2a3717983508fc3cbe7", + "id": "fls_ER0GVQE9N7oH", + "link": "inline-assembly.html#fls_ER0GVQE9N7oH", + "number": "22.8:3" + } + ], + "title": "Label block" + } + ], + "title": "Inline Assembly" + }, + { + "informational": true, + "link": "licenses.html", + "sections": [ + { + "id": "fls_kd7fcvfrwks0", + "informational": false, + "link": "licenses.html", + "number": "A", + "paragraphs": [], + "title": "Licenses" + }, + { + "id": "fls_mwoe9jy6l7er", + "informational": false, + "link": "licenses.html#ada-reference-manual-copyright-notice", + "number": "A.1", + "paragraphs": [ + { + "checksum": "6af57950721e83dae393e90ca8d980bdcca5c9b81a188fe360ec5adfda00b6d5", + "id": "fls_9rpvtm5tjrpp", + "link": "licenses.html#fls_9rpvtm5tjrpp", + "number": "A.1:1" }, { - "checksum": "6d3036f41d092e3cd2e55476c4918788be2ab98eb9c6d0b9e69ee70d433cbce7", - "id": "fls_p6wiuhkeypzs", - "link": "entities-and-resolution.html#fls_p6wiuhkeypzs", - "number": "14.4.7:26" + "checksum": "ef760ed2bc838098d7e8efb78966c4cd75d281f71907582b05283c54343ba0ce", + "id": "fls_7uhb9t9x8r0c", + "link": "licenses.html#fls_7uhb9t9x8r0c", + "number": "A.1:2" }, { - "checksum": "4025d7292308fbfa960b1bb6777f0012c20f86641c9d40465dbeefdf5b08c521", - "id": "fls_34usianesmf6", - "link": "entities-and-resolution.html#fls_34usianesmf6", - "number": "14.4.7:27" + "checksum": "a5f9956bcdfc5ce615410d2af601a90ce92f066360a5467a27d39718da318796", + "id": "fls_ej4g7lz5eqm8", + "link": "licenses.html#fls_ej4g7lz5eqm8", + "number": "A.1:3" }, { - "checksum": "6e01c796d3afc2eeedc107698dfc4456b7446b5110ba7583416245987f3b16db", - "id": "fls_n1a41d8i0rot", - "link": "entities-and-resolution.html#fls_n1a41d8i0rot", - "number": "14.4.7:28" + "checksum": "20b03a1ea2320f89250e61fc875c4f01597a27856b189891293511d1c3c55df5", + "id": "fls_l9uu4q48v1co", + "link": "licenses.html#fls_l9uu4q48v1co", + "number": "A.1:4" }, { - "checksum": "0db116cf8a599b71212419a8bae8425a590f80dc601f73ff9e537a80f6411a9a", - "id": "fls_amhi3d9dd3i3", - "link": "entities-and-resolution.html#fls_amhi3d9dd3i3", - "number": "14.4.7:29" + "checksum": "38194322b850acd0ebe5ec95d660959c07d88f87ef2fe6c7912a773201dc4836", + "id": "fls_3j7jx4qsqucs", + "link": "licenses.html#fls_3j7jx4qsqucs", + "number": "A.1:5" + } + ], + "title": "Ada Reference Manual Copyright Notice" + }, + { + "id": "fls_w6b35kn6la40", + "informational": false, + "link": "licenses.html#rust-reference-apache-license", + "number": "A.2", + "paragraphs": [ + { + "checksum": "7659351efeafb9bcab1f7bded9384347863a49e48384d8b8f59f4aadc9c047d5", + "id": "fls_wn4kcs3skqra", + "link": "licenses.html#fls_wn4kcs3skqra", + "number": "A.2:1" }, { - "checksum": "b12e44bc28cdd8d7415305251565ee0efd311e80ca5798f02424d3d97af7abf9", - "id": "fls_nu8xj3vza55j", - "link": "entities-and-resolution.html#fls_nu8xj3vza55j", - "number": "14.4.7:30" + "checksum": "6b9064c87e131c2318a938f837e161eb0c65d3f5fb3d76f34c6a07ac89c08f73", + "id": "fls_mtcsb64efiw1", + "link": "licenses.html#fls_mtcsb64efiw1", + "number": "A.2:2" }, { - "checksum": "2831803e44ad7befd12a919e248e44a0be126600a95ea86bddaf2a682634f7ea", - "id": "fls_fiyj50u6cg2n", - "link": "entities-and-resolution.html#fls_fiyj50u6cg2n", - "number": "14.4.7:31" + "checksum": "511b49aedd83a0711011cf0a3d3ffbc8faa1c1443f13b71376c4ce8d0b1c6f6d", + "id": "fls_9tssu3wp22cz", + "link": "licenses.html#fls_9tssu3wp22cz", + "number": "A.2:3" }, { - "checksum": "be9f93509f539e90659d105d4f3312acd8a85b94e8c3aa00920639811e1eb437", - "id": "fls_azjx3y5yezoi", - "link": "entities-and-resolution.html#fls_azjx3y5yezoi", - "number": "14.4.7:32" + "checksum": "a423d8767937df97db94bc21dea4f19e65f5d2a464521f2deca8482ccd4eb2e1", + "id": "fls_4pclfuqukw5o", + "link": "licenses.html#fls_4pclfuqukw5o", + "number": "A.2:4" }, { - "checksum": "9360333f7d8597a9b299a8028b723451ce669aa9bf7571105bcffc0ed2665b48", - "id": "fls_puly43s4x360", - "link": "entities-and-resolution.html#fls_puly43s4x360", - "number": "14.4.7:33" + "checksum": "bcc06c2a274e4878336dd5b4c71758d3b854dcf325e9f4ca54c1c6501dccbcee", + "id": "fls_rgn86bsd9tl5", + "link": "licenses.html#fls_rgn86bsd9tl5", + "number": "A.2:5" }, { - "checksum": "07f7dbb2dcd0707931f55e12d3c59fd8e10ecb809691978bc03ebcae149c872a", - "id": "fls_pxtlu7ud6w2h", - "link": "entities-and-resolution.html#fls_pxtlu7ud6w2h", - "number": "14.4.7:34" + "checksum": "0c8baad676d0cb1c29fe117e5864ef351160f102c13d047d354e16c2145fc69a", + "id": "fls_n6ash4u0e838", + "link": "licenses.html#fls_n6ash4u0e838", + "number": "A.2:6" }, { - "checksum": "73d81df70d3dae226f85d4fc1d947dcb6c50a3b857102f823d92d248260ce169", - "id": "fls_ddxxt11u0yal", - "link": "entities-and-resolution.html#fls_ddxxt11u0yal", - "number": "14.4.7:35" + "checksum": "619babacc89fcc3a48bee87f41d8567998cf7926a806e7bcb31bef0bc1bf70fd", + "id": "fls_4z50m2plw8lg", + "link": "licenses.html#fls_4z50m2plw8lg", + "number": "A.2:7" }, { - "checksum": "e9128513da136c0be871747b63ccd23562fce9810967916d929dfa3e17fdd8b7", - "id": "fls_qofr9vme46wp", - "link": "entities-and-resolution.html#fls_qofr9vme46wp", - "number": "14.4.7:36" + "checksum": "d902ee93c8c40420453bff915be7d882e3390fbfb7c3d38905a094dde4369625", + "id": "fls_bmx87il86j77", + "link": "licenses.html#fls_bmx87il86j77", + "number": "A.2:8" }, { - "checksum": "0900771280a33d9ba8e1d92770357e08ea0ba60343df556c52ebd24218d890a6", - "id": "fls_gjvfty9m84a9", - "link": "entities-and-resolution.html#fls_gjvfty9m84a9", - "number": "14.4.7:37" + "checksum": "a5fd12a0acbcca3ac4cfca07cb3a66976c749dcabc7c73c69ed9abfad2067282", + "id": "fls_467b9ucekcv3", + "link": "licenses.html#fls_467b9ucekcv3", + "number": "A.2:9" }, { - "checksum": "32dbf2307aedd581270c9472e89c27976e8bafcdef78287e45a2d79c85a0e2fa", - "id": "fls_xr9wors6oa7w", - "link": "entities-and-resolution.html#fls_xr9wors6oa7w", - "number": "14.4.7:38" - } - ], - "title": "Scope Hierarchy" - }, - { - "id": "fls_dq403wq5yrs", - "informational": false, - "link": "entities-and-resolution.html#namespaces", - "number": "14.5", - "paragraphs": [ + "checksum": "aa73e595c7b2ef9149acd45a1b91fed2704f511303a5b2ae619eb01a1ffb9410", + "id": "fls_9ws2rqie234r", + "link": "licenses.html#fls_9ws2rqie234r", + "number": "A.2:10" + }, { - "checksum": "4836b7a81f95f97d95f77726c269b4aa77f371889797ad03dd5ac5df22bb36d3", - "id": "fls_1d4jm61qnt4l", - "link": "entities-and-resolution.html#fls_1d4jm61qnt4l", - "number": "14.5:1" + "checksum": "c9b7d6603bdf686dc20a9604928e83ad51a6e432ce273673e0619de6904ad3a9", + "id": "fls_j075fx75s7wg", + "link": "licenses.html#fls_j075fx75s7wg", + "number": "A.2:11" }, { - "checksum": "db9113301feaaf01f285b54f58e061b2b4b3902f5faf3d7d227eb0b315c29ed6", - "id": "fls_avsua7bho205", - "link": "entities-and-resolution.html#fls_avsua7bho205", - "number": "14.5:2" + "checksum": "67a79516239a86e9f3ac268c33cea7354013e2efe8dae4acf776dbee55bdf3cb", + "id": "fls_akjks1czkd7n", + "link": "licenses.html#fls_akjks1czkd7n", + "number": "A.2:12" }, { - "checksum": "9d7737fbed1d15d12a68fb7556505470fade9e802af7fd449a008b5b02bcfcdc", - "id": "fls_9e3xeza853wx", - "link": "entities-and-resolution.html#fls_9e3xeza853wx", - "number": "14.5:3" + "checksum": "03e71fa54f500a9dba9a17354fc29921b6a6421fbdde23db702448cfe7f3d731", + "id": "fls_6n4t2e7lxq3", + "link": "licenses.html#fls_6n4t2e7lxq3", + "number": "A.2:13" }, { - "checksum": "1ca9460a4376c7d597c332cf181c59202868d0b5d8d5fe8255a7b430c6025ed1", - "id": "fls_w625tk3ogdui", - "link": "entities-and-resolution.html#fls_w625tk3ogdui", - "number": "14.5:4" + "checksum": "d890372e8c497682bf9b4b51d8b75a7211ca6e6b4ee30436e8c59dbbb0149bf3", + "id": "fls_g0gyduo0wc55", + "link": "licenses.html#fls_g0gyduo0wc55", + "number": "A.2:14" }, { - "checksum": "a19dd69d00d64c388eab5b3d9c048c0137016955c4437b1898ac721c57e8f994", - "id": "fls_crwfafrmydr7", - "link": "entities-and-resolution.html#fls_crwfafrmydr7", - "number": "14.5:5" + "checksum": "85d7c24f5cba0d54181181872cf27f0cad45c28f1d281733ef26114ef08e1bea", + "id": "fls_w8npkgeox7u5", + "link": "licenses.html#fls_w8npkgeox7u5", + "number": "A.2:15" }, { - "checksum": "823ca05b98ccb7e81d9e549d58a3aae35ebbed2f14813b3715bc9e393835a951", - "id": "fls_t8fcpm8ldv1y", - "link": "entities-and-resolution.html#fls_t8fcpm8ldv1y", - "number": "14.5:6" + "checksum": "f0a11688e77729f1b2fa1e4efbc104a39c8fd916d1f58f90eefccd38b209c0d6", + "id": "fls_iygftblnws72", + "link": "licenses.html#fls_iygftblnws72", + "number": "A.2:16" }, { - "checksum": "b25e6462bc8422aa888e0dfcd2fce3eb82d9ad592ae8442b03f73c12fb73f326", - "id": "fls_7pkex797rkeu", - "link": "entities-and-resolution.html#fls_7pkex797rkeu", - "number": "14.5:7" + "checksum": "4a50d529d3ee650e897db5b0f8d99db215c718a75f11d527d5c560abc287de09", + "id": "fls_s32i8ovj5nqu", + "link": "licenses.html#fls_s32i8ovj5nqu", + "number": "A.2:17" }, { - "checksum": "1d855f70942b4b97e20f7a8c2376293535691ffc63e1a3c00d9ff48fbed1c016", - "id": "fls_v32f2evgqt5q", - "link": "entities-and-resolution.html#fls_v32f2evgqt5q", - "number": "14.5:8" + "checksum": "74231e6684fc04c3f1ed8c36da5619eb35f84411c9f9fbbc9354ff69182ec20e", + "id": "fls_c673i0mbecb9", + "link": "licenses.html#fls_c673i0mbecb9", + "number": "A.2:18" }, { - "checksum": "be6570673f514f4a0d3d22cd860599d6811f9cd474760042c53ed220001d5d06", - "id": "fls_f6yrzwu6yi30", - "link": "entities-and-resolution.html#fls_f6yrzwu6yi30", - "number": "14.5:9" + "checksum": "b5a621567e61cacbad30b9320aa5fa59abe37e46a15e890c42ad445536c69bd0", + "id": "fls_pwpt8rg76oj3", + "link": "licenses.html#fls_pwpt8rg76oj3", + "number": "A.2:19" }, { - "checksum": "039f5baf7e3e9350742076c571ec8701657413eb2ab8cf7c8ba7e4a9c6525e47", - "id": "fls_nk0swexy2ztm", - "link": "entities-and-resolution.html#fls_nk0swexy2ztm", - "number": "14.5:10" + "checksum": "aa50ce2c3adbcfcae86514a554462ac599522a77d99072af00fd41aee7896afc", + "id": "fls_yu3hgzo57bvm", + "link": "licenses.html#fls_yu3hgzo57bvm", + "number": "A.2:20" }, { - "checksum": "4e6c9e8735f706d3ed311192bdd242aebefbc9cd8b79ed32481799fc4e4d54ab", - "id": "fls_ckptn88o6lla", - "link": "entities-and-resolution.html#fls_ckptn88o6lla", - "number": "14.5:11" + "checksum": "ee98c5a9da1ccf20da27dc04bdfd4c6b0ee7b4dd5180bb435515d300e6aa7c1f", + "id": "fls_37aibo9w67e8", + "link": "licenses.html#fls_37aibo9w67e8", + "number": "A.2:21" }, { - "checksum": "c2df785b7f8b4c342a0cd204655bf38f1cdcdf6da21e8f7033577a0779ea7dfa", - "id": "fls_3ma5v1fop98p", - "link": "entities-and-resolution.html#fls_3ma5v1fop98p", - "number": "14.5:12" + "checksum": "870cf6f77ad5d8ef0cfde85af78b7c2a507c6e6c8db5bbe62b60548c02229cfc", + "id": "fls_py9om9jvbulw", + "link": "licenses.html#fls_py9om9jvbulw", + "number": "A.2:22" }, { - "checksum": "b626273c028df98bf83f9cc308f4b313b40fd4af9a828d4baa99d514847b7e4f", - "id": "fls_nj7sep7ht7lg", - "link": "entities-and-resolution.html#fls_nj7sep7ht7lg", - "number": "14.5:13" + "checksum": "e4c5f14a1bc396e634f99d3e9c008a29af8f84b61dc6da057710d23ebb6a1855", + "id": "fls_wkf26wyy0ndr", + "link": "licenses.html#fls_wkf26wyy0ndr", + "number": "A.2:23" }, { - "checksum": "e2e5c5f30f5fe02da3765cc72c29c109ec1284daaaacef2440cd86c7dca3ce84", - "id": "fls_g8h6t5x6yprm", - "link": "entities-and-resolution.html#fls_g8h6t5x6yprm", - "number": "14.5:14" + "checksum": "d3162aaf802254c584ffd111d733498e754b056c61bdb59144aed5024b8bc566", + "id": "fls_qsvzdiicam4f", + "link": "licenses.html#fls_qsvzdiicam4f", + "number": "A.2:24" }, { - "checksum": "6cc5106a9453f6b24aa3d1b25413993384c934c46830703cf83d6aea69144c37", - "id": "fls_2l1o7vqfr4m7", - "link": "entities-and-resolution.html#fls_2l1o7vqfr4m7", - "number": "14.5:15" + "checksum": "c502f762cf1f733336f2b49fdabec625a400d1b9137ba436de9acfb180ea3681", + "id": "fls_n4zpph83hza1", + "link": "licenses.html#fls_n4zpph83hza1", + "number": "A.2:25" }, { - "checksum": "6de1568e937c91c9d59930b38b47a437c2f0d6c186377eb0f20facbfed8ffed5", - "id": "fls_6q8rjv1jmu84", - "link": "entities-and-resolution.html#fls_6q8rjv1jmu84", - "number": "14.5:16" + "checksum": "c6ab38d458c9605260610ec31abaaf83a03d54cc4109c75ce57d5330b7e0ae43", + "id": "fls_6gvjqsyyk3sg", + "link": "licenses.html#fls_6gvjqsyyk3sg", + "number": "A.2:26" }, { - "checksum": "da1ee981a9e1da0bcc03eb523e05994e90313be4bbd2f3a91ac1cef0226944fe", - "id": "fls_lx2tx1jt8t3a", - "link": "entities-and-resolution.html#fls_lx2tx1jt8t3a", - "number": "14.5:17" + "checksum": "dbb66a9641f131e6288803d7b31289cc46c0c00d4bf70ecaade3c44dfb5acfaa", + "id": "fls_evwltjyhvhz", + "link": "licenses.html#fls_evwltjyhvhz", + "number": "A.2:27" }, { - "checksum": "1f6a93e2bd7f34e4fba8c83590318721a361b61a42c82338c30914ca994e74b0", - "id": "fls_mo00df28t7c1", - "link": "entities-and-resolution.html#fls_mo00df28t7c1", - "number": "14.5:18" + "checksum": "f6437e117dc2304d057d9a5394bea1e927d97d91a0451366fb3bdbaa35556c6c", + "id": "fls_xj0b5mrjgdbt", + "link": "licenses.html#fls_xj0b5mrjgdbt", + "number": "A.2:28" }, { - "checksum": "e64cf0d2e680eb773614e1b17642212b8071fe0e2601a1b4c95e71731c84a4e0", - "id": "fls_8o3izim4zf8t", - "link": "entities-and-resolution.html#fls_8o3izim4zf8t", - "number": "14.5:19" + "checksum": "5cb92d044eeac194b5d3a2e2fed02e650bdca56fe9e6386c4729f39717879270", + "id": "fls_ecitsmzb8l28", + "link": "licenses.html#fls_ecitsmzb8l28", + "number": "A.2:29" }, { - "checksum": "8030bef3d19c581e3a4a96c91f3fd57388b1fb2bedea44b40c010da243302e7f", - "id": "fls_fweohszgbuj4", - "link": "entities-and-resolution.html#fls_fweohszgbuj4", - "number": "14.5:20" + "checksum": "a69bf85bf1838570613037b4423858a9a047187494aea6e78ae1c91c9d58f711", + "id": "fls_wx0hv6f68i4f", + "link": "licenses.html#fls_wx0hv6f68i4f", + "number": "A.2:30" }, { - "checksum": "df9b14ad6a4ce87a81cdbfcb59cf04e07e8eb0a0c2de14823a7b87d1ee2a5700", - "id": "fls_ry02dzisxz3h", - "link": "entities-and-resolution.html#fls_ry02dzisxz3h", - "number": "14.5:21" + "checksum": "df088a3e94ccbd2e98926c4f5ca34fd02f3cd8dfc3924b7ba3749ef0eb08b68d", + "id": "fls_fgjjzdwbw1t8", + "link": "licenses.html#fls_fgjjzdwbw1t8", + "number": "A.2:31" }, { - "checksum": "5cd65fc00ce58c6713964263dae05662f477e761c5b34c5bd4f050b3608b12bc", - "id": "fls_dcz1bxjjfsq", - "link": "entities-and-resolution.html#fls_dcz1bxjjfsq", - "number": "14.5:22" + "checksum": "1a6497e3eeb694644723d0a451104bff4dd133960c27e3d4e9b6c5282cdcdf2a", + "id": "fls_hnxekf2ahnif", + "link": "licenses.html#fls_hnxekf2ahnif", + "number": "A.2:32" }, { - "checksum": "1d817e252ce6759b84635d64a683f45d0081ab7c687143959331fb141cce459a", - "id": "fls_wt9kgsi6n6ep", - "link": "entities-and-resolution.html#fls_wt9kgsi6n6ep", - "number": "14.5:23" + "checksum": "839ca0d83587df0f49d5e7af7a86cb4ed2526d3c7a4fb98ca1e7031d41e9c3bb", + "id": "fls_e0ytxo6b451", + "link": "licenses.html#fls_e0ytxo6b451", + "number": "A.2:33" }, { - "checksum": "52ce21886a1f1adcb3176e37769e7fbf75f96b161b8f1ff617e40e254a1113b6", - "id": "fls_w29t5njbe46s", - "link": "entities-and-resolution.html#fls_w29t5njbe46s", - "number": "14.5:24" + "checksum": "1c4d9cbfb808d36160fd3001c512e6887268a758682958c644bcbe07f04a32af", + "id": "fls_145e22m6bw47", + "link": "licenses.html#fls_145e22m6bw47", + "number": "A.2:34" }, { - "checksum": "26d2b1e1d098bd315370c984c209e9bcfe05242d062331290c72be6bbe298b92", - "id": "fls_u1533bngb0yv", - "link": "entities-and-resolution.html#fls_u1533bngb0yv", - "number": "14.5:25" + "checksum": "9c3bd1dd9f6780741a46dfb5af593752082db35d8ce9d3eb8ae7d663e403c665", + "id": "fls_9o0wr812ggf2", + "link": "licenses.html#fls_9o0wr812ggf2", + "number": "A.2:35" }, { - "checksum": "daa0b3886b8ff9f6ff8b555ad965ddbc2563f2cf44e1b869d632647511fde7f0", - "id": "fls_e8v4g45v5ry2", - "link": "entities-and-resolution.html#fls_e8v4g45v5ry2", - "number": "14.5:26" + "checksum": "a3d8a030eaf80e2f2d7b0b8c819a8b4a03d2468cad162b5778d7535713e6eb54", + "id": "fls_cqaian5c7mwr", + "link": "licenses.html#fls_cqaian5c7mwr", + "number": "A.2:36" }, { - "checksum": "120d3ec4ef8b8d96a2aeed9aae90b73656762d0a9cad70696c487cf430845448", - "id": "fls_pq8bzav84q3z", - "link": "entities-and-resolution.html#fls_pq8bzav84q3z", - "number": "14.5:27" + "checksum": "2caf6f88f365a1f0b8673022f566d0e34e5f487002cbb975f7a3810984aa2b33", + "id": "fls_eil4b7ffojqa", + "link": "licenses.html#fls_eil4b7ffojqa", + "number": "A.2:37" + } + ], + "title": "Rust Reference Apache License" + }, + { + "id": "fls_un9oqipiretc", + "informational": false, + "link": "licenses.html#rust-reference-mit-license", + "number": "A.3", + "paragraphs": [ + { + "checksum": "43b7d2588a00010b6644920dff56e6f4652ec966cf564d52f3e5e87748e58bbf", + "id": "fls_bkxw1o1hanmk", + "link": "licenses.html#fls_bkxw1o1hanmk", + "number": "A.3:1" }, { - "checksum": "809a2c313aa61cdd28a44cd5fcd05bcb52778a18127ddd942da86fc441d1c2de", - "id": "fls_ttr6v8ca4av0", - "link": "entities-and-resolution.html#fls_ttr6v8ca4av0", - "number": "14.5:28" + "checksum": "efda707a511b8c7e80727098049e5458105f2960dc74c0d7faa11a43f7e7d997", + "id": "fls_yc3wyw3plm1s", + "link": "licenses.html#fls_yc3wyw3plm1s", + "number": "A.3:2" + }, + { + "checksum": "dc6e47d05ec79c0cbaa15dd61b6a60ab03fd6f9d7e2f51df6828fc53f4916265", + "id": "fls_ypomxdgiswq8", + "link": "licenses.html#fls_ypomxdgiswq8", + "number": "A.3:3" }, { - "checksum": "3ff5dde150cb5c944aec2ecd5a8349fb90870bae072f2f54544f3c820bfa7fee", - "id": "fls_aivi97hhfxy2", - "link": "entities-and-resolution.html#fls_aivi97hhfxy2", - "number": "14.5:29" + "checksum": "f227391839011fc1299d7eef9855e682ceab676478b839480bb5cdea3359f70b", + "id": "fls_q6rhvbuhxa21", + "link": "licenses.html#fls_q6rhvbuhxa21", + "number": "A.3:4" + } + ], + "title": "Rust Reference MIT License" + } + ], + "title": "Licenses" + }, + { + "informational": false, + "link": "index.html", + "sections": [], + "title": "FLS" + }, + { + "informational": false, + "link": "macros.html", + "sections": [ + { + "id": "fls_83182bfa9uqb", + "informational": false, + "link": "macros.html", + "number": "20", + "paragraphs": [ + { + "checksum": "23ad7afdee8d11ed607ec24d3a0e15437d673156ba4b127587b5669f1832653b", + "id": "fls_j1jc83erljo0", + "link": "macros.html#fls_j1jc83erljo0", + "number": "20:1" }, { - "checksum": "d4015ccb1d4717c0b29f6d3f48fddfad1ac1f9f8a397e4963f0d571b37bd4a4b", - "id": "fls_pie4ltdtzkl3", - "link": "entities-and-resolution.html#fls_pie4ltdtzkl3", - "number": "14.5:30" + "checksum": "f3350d5be59f16503e8f202eed548f9142e23d0995f540a0fe8dbfa0790eb114", + "id": "fls_23eapx3ckymf", + "link": "macros.html#fls_23eapx3ckymf", + "number": "20:2" }, { - "checksum": "650724cc8ebda80bcd989f6e99e585db43a4590a015f45fedea21bb82f33dc3c", - "id": "fls_qmf7lk6h96sv", - "link": "entities-and-resolution.html#fls_qmf7lk6h96sv", - "number": "14.5:31" + "checksum": "b37827025945e3ebe09fb51af413ab507e55bef20cbd1cdc0d88bda1e5b31868", + "id": "fls_a5uemz2hnbi8", + "link": "macros.html#fls_a5uemz2hnbi8", + "number": "20:3" }, { - "checksum": "ba289fa126ce5f48c2046c9cc9e5e7cbd13217e772757f9c2d12848473872ebd", - "id": "fls_ufp3btk8pet5", - "link": "entities-and-resolution.html#fls_ufp3btk8pet5", - "number": "14.5:32" - }, + "checksum": "f947401b4192d1cb447cdbc6dfa662250896c68e1cb0477bdfdbc7dfaf9b809c", + "id": "fls_rnty1c8l5495", + "link": "macros.html#fls_rnty1c8l5495", + "number": "20:4" + } + ], + "title": "Macros" + }, + { + "id": "fls_xa7lp0zg1ol2", + "informational": false, + "link": "macros.html#declarative-macros", + "number": "20.1", + "paragraphs": [ { - "checksum": "cc447ee2f7a5dac66d5e22eb032c6a31eb21d5526ca66fda96ebc2ad415fe6e6", - "id": "fls_t3bnpkfazw4z", - "link": "entities-and-resolution.html#fls_t3bnpkfazw4z", - "number": "14.5:33" + "checksum": "8cffdd9962e92466f819713aab704c780ae0d98dac229021c576afa2742830ba", + "id": "fls_ikzjsq8heyk6", + "link": "macros.html#fls_ikzjsq8heyk6", + "number": "20.1:1" }, { - "checksum": "61fb8303812158a4adfaef901dc1452be9cb24ee1b643dfde5f612d0d0a7eacf", - "id": "fls_y0shlli54n5y", - "link": "entities-and-resolution.html#fls_y0shlli54n5y", - "number": "14.5:34" + "checksum": "4e9e7f12e6633dbb05180c17c6a222b11c2760152ee1a091050e70af0a2f4238", + "id": "fls_w44hav7mw3ao", + "link": "macros.html#fls_w44hav7mw3ao", + "number": "20.1:2" }, { - "checksum": "42dffcc4cecdc17b20167b7319ca32561b6c3bac4a546b72a18301ddd55a407a", - "id": "fls_tghgxcju5u2t", - "link": "entities-and-resolution.html#fls_tghgxcju5u2t", - "number": "14.5:35" + "checksum": "af67fc9d046886fb7c793c0a9eef775cd020529cf8ec85a93167f1e58f4122fa", + "id": "fls_dw1nq4r9ghhd", + "link": "macros.html#fls_dw1nq4r9ghhd", + "number": "20.1:3" }, { - "checksum": "40f27fcd73d87d8592e01e22030b08faea5b83cae412960f492dcc2f8a684cc2", - "id": "fls_yesesxynpq6s", - "link": "entities-and-resolution.html#fls_yesesxynpq6s", - "number": "14.5:36" + "checksum": "13beaa609262aa0b7e0b3212da54ac78e68086106c14cc11e081e7132158286b", + "id": "fls_oq4xn8guos8f", + "link": "macros.html#fls_oq4xn8guos8f", + "number": "20.1:4" }, { - "checksum": "ff385d3524d4f2788d893d77f024a5876c248a7eeee84ac5626642a07c55e0aa", - "id": "fls_40o8y6exr3df", - "link": "entities-and-resolution.html#fls_40o8y6exr3df", - "number": "14.5:37" + "checksum": "b549b18e6de60d1ec130ab38737806e6ea0cbd0d471501c476916eefe2be7228", + "id": "fls_cdaf8viwmdfe", + "link": "macros.html#fls_cdaf8viwmdfe", + "number": "20.1:5" }, { - "checksum": "a2c64e06759e68c6b0cf453b6868f12decc89182b9e88d83b02190bccc393951", - "id": "fls_y76o5ug7dtv", - "link": "entities-and-resolution.html#fls_y76o5ug7dtv", - "number": "14.5:38" + "checksum": "238647cd9313b6607667d9993c8bf87f5406351bd4a87045bd0850f5063f6b84", + "id": "fls_ljavs0w61z3j", + "link": "macros.html#fls_ljavs0w61z3j", + "number": "20.1:6" }, { - "checksum": "45f1bccebe0303da8288a05a61a1209ff3203d962d252fcfa1fb0a549f9feb28", - "id": "fls_3np518s1su4w", - "link": "entities-and-resolution.html#fls_3np518s1su4w", - "number": "14.5:39" + "checksum": "41d7cadc4529963b5fae05cea05d12721cc023eac8db3ab3013c78712f677f92", + "id": "fls_3jspk8obv7sd", + "link": "macros.html#fls_3jspk8obv7sd", + "number": "20.1:7" } ], - "title": "Namespaces" + "title": "Declarative Macros" }, { - "id": "fls_ld0ize96cm6m", + "id": "fls_8nzypdu9j3ge", "informational": false, - "link": "entities-and-resolution.html#preludes", - "number": "14.6", + "link": "macros.html#metavariables", + "number": "20.1.1", "paragraphs": [ { - "checksum": "38d62c936f206c7f1e48f785bbde2d34f1bc2865287994876e882b1c0f65d05e", - "id": "fls_po4gw6t2ptwu", - "link": "entities-and-resolution.html#fls_po4gw6t2ptwu", - "number": "14.6:1" + "checksum": "142bdc43ec20b9deed884f97e167a2a47e1cd8cab76ba4b6a81e5b3ec56b69ea", + "id": "fls_g93r3teei8wo", + "link": "macros.html#fls_g93r3teei8wo", + "number": "20.1.1:1" }, { - "checksum": "9110a045c9c8c940ad03c36e93d5155d9143317d35c55691fb325a15db4b8607", - "id": "fls_n4102qskkmz2", - "link": "entities-and-resolution.html#fls_n4102qskkmz2", - "number": "14.6:2" + "checksum": "260a8af1053ab13b37c8171151aec029a2b0049a4db4d2fa4c072391caa7c788", + "id": "fls_4zdait30exvn", + "link": "macros.html#fls_4zdait30exvn", + "number": "20.1.1:2" }, { - "checksum": "ec28e48bd9269897b4f0446e74ae493289085d680cbdc44336623ee5d261c82b", - "id": "fls_atvnwly4w8g2", - "link": "entities-and-resolution.html#fls_atvnwly4w8g2", - "number": "14.6:3" + "checksum": "0e475930b62b5e6e2c4eaa5d954c01002c3918fb1243f5ffd3b8b5959e5350f0", + "id": "fls_2HguXbL7DjKH", + "link": "macros.html#fls_2HguXbL7DjKH", + "number": "20.1.1:3" }, { - "checksum": "51511e88fc1929d2b8a82b1599001557503f199917df1a80d734152884ff7c77", - "id": "fls_pbc7ktlu0pl", - "link": "entities-and-resolution.html#fls_pbc7ktlu0pl", - "number": "14.6:4" + "checksum": "e16b11f619d14521da5d28556e2228fd055de524ae57da0397dc5fb7f244b522", + "id": "fls_8zypylq60zba", + "link": "macros.html#fls_8zypylq60zba", + "number": "20.1.1:4" }, { - "checksum": "6bc798373f758197663c5691e9bf15b398a778e6f199a05c44fa7aa932fe6c11", - "id": "fls_frjv68kqqxfh", - "link": "entities-and-resolution.html#fls_frjv68kqqxfh", - "number": "14.6:5" + "checksum": "a7305e4a6c0db284c01d3b766c9526e183f139d0d6b3417b05a0a25448deb2e1", + "id": "fls_8o9mcV2KrKac", + "link": "macros.html#fls_8o9mcV2KrKac", + "number": "20.1.1:5" }, { - "checksum": "c92c07a0326e48234f51c8b6a55bbc0ef651cc56135b5f0f5b5203095dac192e", - "id": "fls_rf6a2ae3y7vu", - "link": "entities-and-resolution.html#fls_rf6a2ae3y7vu", - "number": "14.6:6" + "checksum": "bbdb87195b02f153c61cd395d1d5db4690ef89af595efddbf90e07037c6d88fd", + "id": "fls_PxR9vNHsaFnI", + "link": "macros.html#fls_PxR9vNHsaFnI", + "number": "20.1.1:6" }, { - "checksum": "4346e39808389249844816dc52643e4e1c2e04e1990eddc603e94ad0e797c809", - "id": "fls_sxnnkzmuvexa", - "link": "entities-and-resolution.html#fls_sxnnkzmuvexa", - "number": "14.6:7" + "checksum": "db57f38586128d0f797f87a3bec37a62e9bc1ffe1a0b5b3634c97bbbd3c3a923", + "id": "fls_ePyoTeJJ11N0", + "link": "macros.html#fls_ePyoTeJJ11N0", + "number": "20.1.1:7" }, { - "checksum": "27cab7337ca595be01ad78b78429ef14d328fa6a449fea686acad8144ea79c34", - "id": "fls_qsyorqjkdh2t", - "link": "entities-and-resolution.html#fls_qsyorqjkdh2t", - "number": "14.6:8" + "checksum": "5ab36e5b8b233c50fbb634f2ddb85db4a65676e58884c4e545002ad8ae792716", + "id": "fls_0j7VOV4ewfeY", + "link": "macros.html#fls_0j7VOV4ewfeY", + "number": "20.1.1:8" }, { - "checksum": "77446948cc77c5f2d38ee156686b5607c4f0331f371b47e54e835ad173f80ba9", - "id": "fls_aolj6abvp9sa", - "link": "entities-and-resolution.html#fls_aolj6abvp9sa", - "number": "14.6:9" + "checksum": "7e4c3dc1c34fcadf4bb70df2a2ca55cb16019fe78a19c5f625a3407c7f23de0c", + "id": "fls_80cOMpIMU2gx", + "link": "macros.html#fls_80cOMpIMU2gx", + "number": "20.1.1:9" }, { - "checksum": "9a4a1df8c10224f0c968a22de8bdb0bef1914575cfb19ea0eabb39bce468dd62", - "id": "fls_of4n3vv15l5z", - "link": "entities-and-resolution.html#fls_of4n3vv15l5z", - "number": "14.6:10" - } - ], - "title": "Preludes" - }, - { - "id": "fls_9gprp17h6t1q", - "informational": false, - "link": "entities-and-resolution.html#use-imports", - "number": "14.7", - "paragraphs": [ + "checksum": "ae189445a188e528fc320b0716648ea260e44624143f233a7a3d6594c319fc71", + "id": "fls_DFMRwsWI8e5z", + "link": "macros.html#fls_DFMRwsWI8e5z", + "number": "20.1.1:10" + }, { - "checksum": "813cc34b517b6e85c804618d58441cbaec0e01369396ee4e16225d853a8ea8f3", - "id": "fls_lyw4t098sxrj", - "link": "entities-and-resolution.html#fls_lyw4t098sxrj", - "number": "14.7:1" + "checksum": "3a94c395e86b915f016782c8e832229ac77ff7d26c4468f604f4d1346d410385", + "id": "fls_BoIGgrFdyhwH", + "link": "macros.html#fls_BoIGgrFdyhwH", + "number": "20.1.1:11" }, { - "checksum": "60cd8ef0b9e4e5913161b5314d0d298edd34ff20cb398eb5e69afcb53c884a65", - "id": "fls_sxo1jb25pl8a", - "link": "entities-and-resolution.html#fls_sxo1jb25pl8a", - "number": "14.7:2" + "checksum": "10fac2b91182e4a802d41382e138eeea01e0169f14fd98576dec0541757d3d52", + "id": "fls_NBbygZwUxjFp", + "link": "macros.html#fls_NBbygZwUxjFp", + "number": "20.1.1:12" }, { - "checksum": "eb328b409e1e5b1f5bfe01c443065950ff895ff862c758235178db6ea7812a7b", - "id": "fls_WAA4WmohGu6T", - "link": "entities-and-resolution.html#fls_WAA4WmohGu6T", - "number": "14.7:3" + "checksum": "a397be8475f298d03d342d1d7922a7addc7c4e01708dbc748271e89ff6e4e91a", + "id": "fls_lZ8F1zUJju33", + "link": "macros.html#fls_lZ8F1zUJju33", + "number": "20.1.1:13" }, { - "checksum": "1b245fbcb87f36b4177fc03be141f12a61d6353063182660fab4e0cdc454c0da", - "id": "fls_IPYvldMqduf4", - "link": "entities-and-resolution.html#fls_IPYvldMqduf4", - "number": "14.7:4" + "checksum": "6d1e723dc52e63b247ff2a9f36fd2a2448c78a8a6c0785d9bc962cbf164a68ff", + "id": "fls_ephlmLsGTMgw", + "link": "macros.html#fls_ephlmLsGTMgw", + "number": "20.1.1:14" + } + ], + "title": "Metavariables" + }, + { + "id": "fls_k01lsksqtq1r", + "informational": false, + "link": "macros.html#repetition", + "number": "20.1.2", + "paragraphs": [ + { + "checksum": "9be00a29faba4cce56b85af749378910166a176caaf783feb7f21917b113f4fc", + "id": "fls_4ps4x4513xau", + "link": "macros.html#fls_4ps4x4513xau", + "number": "20.1.2:1" }, { - "checksum": "e517d4937f6b8b583887c5043676af52be726cafd61bbc680f13f5656221172d", - "id": "fls_MOXId37fcNPY", - "link": "entities-and-resolution.html#fls_MOXId37fcNPY", - "number": "14.7:5" + "checksum": "0afa98bdb2bc52d07887d1af628eba5846e5110da9bf813ba1906139eba01c98", + "id": "fls_8byjmlgum2f3", + "link": "macros.html#fls_8byjmlgum2f3", + "number": "20.1.2:2" }, { - "checksum": "6bf1152c602ed8ba48c73c7fa1915bfad897fb93875da88dcb8ba9ffbffd7dca", - "id": "fls_2UyFcB6Our1v", - "link": "entities-and-resolution.html#fls_2UyFcB6Our1v", - "number": "14.7:6" + "checksum": "43358f7409e5fcf8e712e18e535f05897041a0c7ee20dc9c07eaae0a6a5b463c", + "id": "fls_ltdp3zs60dzr", + "link": "macros.html#fls_ltdp3zs60dzr", + "number": "20.1.2:3" }, { - "checksum": "79e5b1e6a7d289558aff645ca9eef17ce05e04fedcea56361c3c5c8e0ee8da88", - "id": "fls_irdKqoYzBM0M", - "link": "entities-and-resolution.html#fls_irdKqoYzBM0M", - "number": "14.7:7" + "checksum": "fb90822182dbef1ad0b6848a7d2b4784a72127a44df216ec0a2fc1e4f586298c", + "id": "fls_V1WRuzZUWUGj", + "link": "macros.html#fls_V1WRuzZUWUGj", + "number": "20.1.2:4" }, { - "checksum": "f9a3c4f788393bf20c3f6e2f1532ebe181eabd6d14c64bc76bbde37f384cc86b", - "id": "fls_gAWsqibl4GLq", - "link": "entities-and-resolution.html#fls_gAWsqibl4GLq", - "number": "14.7:8" + "checksum": "cc21d8251493e001ac2bf6dc3b6d9869a4525b75ff8bdd4a6b96569f649bd5a7", + "id": "fls_u86j0zm2jshf", + "link": "macros.html#fls_u86j0zm2jshf", + "number": "20.1.2:5" }, { - "checksum": "5b6924c18d30552bc9b8351dcc65a6bd1e5d98680cbcb35bd254e872513eb96e", - "id": "fls_2bkcn83smy2y", - "link": "entities-and-resolution.html#fls_2bkcn83smy2y", - "number": "14.7:9" + "checksum": "decb59aaa3220c66f8fa18342f46b9dbbd8d79ae738262d7cc8d3276f0d6f871", + "id": "fls_h5f8x4jdnvbu", + "link": "macros.html#fls_h5f8x4jdnvbu", + "number": "20.1.2:6" }, { - "checksum": "7fbdb92123ed136d12204172f71af7bdf831c5cca0d09fb88a8446f8a976e9cd", - "id": "fls_v3a6y2ze44v2", - "link": "entities-and-resolution.html#fls_v3a6y2ze44v2", - "number": "14.7:10" + "checksum": "8cb8cbde345ddbe65efa4ff3d8e5aa2db38eaa6626cdd39220f0a691c264783d", + "id": "fls_hf4gj5pfl437", + "link": "macros.html#fls_hf4gj5pfl437", + "number": "20.1.2:7" }, { - "checksum": "7645908217267b4bb43c4e7b5562d641d9bd121bc47c13f2301e854c768d2dab", - "id": "fls_JHU0ersYB6eL", - "link": "entities-and-resolution.html#fls_JHU0ersYB6eL", - "number": "14.7:11" + "checksum": "e94a58c9be4a3e503fe157cfb77e90b5e89fe37b134ffb1f6f08c47bd52ea5f9", + "id": "fls_tm0w0680wf4x", + "link": "macros.html#fls_tm0w0680wf4x", + "number": "20.1.2:8" }, { - "checksum": "8e0c213bf3df7e68bb3d5a37c5d28f9a2951831f6a212231d780248a26766802", - "id": "fls_jlNKxkuhsvX4", - "link": "entities-and-resolution.html#fls_jlNKxkuhsvX4", - "number": "14.7:12" + "checksum": "3cdc79fac2c5194ed7084ae18321fb32b4db88a78ed80cc4f4432b4894648505", + "id": "fls_10lsg5212ffb", + "link": "macros.html#fls_10lsg5212ffb", + "number": "20.1.2:9" }, { - "checksum": "9cec53e98886d395fc8df4d58bb00bfcb91617b34acaf616395d8e52652246e5", - "id": "fls_q0KFfNALMslq", - "link": "entities-and-resolution.html#fls_q0KFfNALMslq", - "number": "14.7:13" + "checksum": "44e5e09eadfa0ef32f2aa42f5d86aefc29a08264af8ea9678ec830ac5d0d919c", + "id": "fls_UnfvR9NB1Nze", + "link": "macros.html#fls_UnfvR9NB1Nze", + "number": "20.1.2:10" }, { - "checksum": "3bfcbf98de455cd97691285c1acf7f4818c2d6a62889ebc042f7444e9a36d98b", - "id": "fls_Hy17LzOFGfOp", - "link": "entities-and-resolution.html#fls_Hy17LzOFGfOp", - "number": "14.7:14" + "checksum": "ada08a290b60070c6aae793052fba60f6404e5668f5da364e16a178044452d6a", + "id": "fls_Sm4qVsHKYLY2", + "link": "macros.html#fls_Sm4qVsHKYLY2", + "number": "20.1.2:11" }, { - "checksum": "64222f860a3b80479b545a9e1668b362fc096bd84fd135b83848cab59f535464", - "id": "fls_90hQvSh7Bfyg", - "link": "entities-and-resolution.html#fls_90hQvSh7Bfyg", - "number": "14.7:15" + "checksum": "ea5e5013c9fbf8d7b889cd0fe21718fb395b30a9d7cd9f10b911a150dc32decd", + "id": "fls_Rdvs8Dz6OUU7", + "link": "macros.html#fls_Rdvs8Dz6OUU7", + "number": "20.1.2:12" }, { - "checksum": "0e4fdf65d5d96641f5f69fd73231dc0d8b53d0a8ee40c16c7e62fc70ee53f047", - "id": "fls_wRmvtgQkFA6w", - "link": "entities-and-resolution.html#fls_wRmvtgQkFA6w", - "number": "14.7:16" + "checksum": "e9e82608e910aea5e4b9b881e3bbc4f832130dd93a971ad1d979ae6b3be81ba8", + "id": "fls_UIlj6Csow81w", + "link": "macros.html#fls_UIlj6Csow81w", + "number": "20.1.2:13" }, { - "checksum": "b5a17251d386bd737a55045cc108b41a18740d77abb50130269d9ebe6303ec72", - "id": "fls_kz2Gij5wHXnl", - "link": "entities-and-resolution.html#fls_kz2Gij5wHXnl", - "number": "14.7:17" + "checksum": "31f297fdd3706a0010aae6400f9a4d0d7368160c1aebd8e60699f9e9fbdf2639", + "id": "fls_yp2XxDv4DzEi", + "link": "macros.html#fls_yp2XxDv4DzEi", + "number": "20.1.2:14" }, { - "checksum": "2649f9cf8cb9cc5a2907d411e6134e4e0997b0ab85f19a07509de9ffc29575b4", - "id": "fls_yY58pFpkig9o", - "link": "entities-and-resolution.html#fls_yY58pFpkig9o", - "number": "14.7:18" - }, + "checksum": "e542a9b407077073772387ef8c3ca43d8afb0cf939f1285a5c47b7d07ad1979d", + "id": "fls_n5TkJKWiDhCD", + "link": "macros.html#fls_n5TkJKWiDhCD", + "number": "20.1.2:15" + } + ], + "title": "Repetition" + }, + { + "id": "fls_wn1i6hzg2ff7", + "informational": false, + "link": "macros.html#procedural-macros", + "number": "20.2", + "paragraphs": [ { - "checksum": "41745bc2f1843a06a54a672f54f86ac947143da2a368e20e62f8df7a9d67ebeb", - "id": "fls_ar03D5rxjzy0", - "link": "entities-and-resolution.html#fls_ar03D5rxjzy0", - "number": "14.7:19" + "checksum": "40ae367af875e89e010e4d22a7cfe13aa5dbaed9fc46011de6b52d138577092f", + "id": "fls_ejbddhggstd2", + "link": "macros.html#fls_ejbddhggstd2", + "number": "20.2:1" }, { - "checksum": "00e9aea779512a6b5660ecaf0702c0d3ac4c3c845a967ca739fd4cac1be0b551", - "id": "fls_ce73bg0BqV1X", - "link": "entities-and-resolution.html#fls_ce73bg0BqV1X", - "number": "14.7:20" + "checksum": "a154977cec887d19447bb9d7566684f5f07b10108feaad22add360f52cade411", + "id": "fls_pcce9gmjpxba", + "link": "macros.html#fls_pcce9gmjpxba", + "number": "20.2:2" }, { - "checksum": "43af72288bd22a2547f54bfac05285b72065c3c2cb06c8de5b5ce6ba9efb055b", - "id": "fls_FILuR3pfwjw3", - "link": "entities-and-resolution.html#fls_FILuR3pfwjw3", - "number": "14.7:21" + "checksum": "0f0739a1ed671f541972aa194b63778a35eef1f8a258625357bad1dfb79126ec", + "id": "fls_vtzuplb1p3s", + "link": "macros.html#fls_vtzuplb1p3s", + "number": "20.2:3" }, { - "checksum": "2ddb4b51369e74bddbb00fcd04f1bb773bfba5a9e5dfc796d8df300f808159ff", - "id": "fls_iQOgxNihUEr7", - "link": "entities-and-resolution.html#fls_iQOgxNihUEr7", - "number": "14.7:22" - }, + "checksum": "b0d421770e39ab6631e15b540881bf6453ecda3a0bbc8c78183bba9be46e6c77", + "id": "fls_mewfehvgm16r", + "link": "macros.html#fls_mewfehvgm16r", + "number": "20.2:4" + } + ], + "title": "Procedural Macros" + }, + { + "id": "fls_2d6bqnpy6tvs", + "informational": false, + "link": "macros.html#function-like-macros", + "number": "20.2.1", + "paragraphs": [ { - "checksum": "fa0a3046e6eb99fb80810bb3178f02f5e79ed989591df2df0d2770e7a189838e", - "id": "fls_ldr7tsuqw34s", - "link": "entities-and-resolution.html#fls_ldr7tsuqw34s", - "number": "14.7:23" + "checksum": "35211e047d31106ec03bc1b7ccba14cee77f4c950b50ddedbd47d9cbe9dc876f", + "id": "fls_utd3zqczix", + "link": "macros.html#fls_utd3zqczix", + "number": "20.2.1:1" }, { - "checksum": "8cdb7a1fd01eee9c13a292e349acea9eb9ac949a2fa9265baef3c16015ddb27e", - "id": "fls_iNUBX5fJAI1N", - "link": "entities-and-resolution.html#fls_iNUBX5fJAI1N", - "number": "14.7:24" + "checksum": "195f49e7c5486c12cd274b338a19d3eaa1a19f99570f7ee0b52a3b1eeb90251e", + "id": "fls_ojr30lf6jfx0", + "link": "macros.html#fls_ojr30lf6jfx0", + "number": "20.2.1:2" }, { - "checksum": "12921489bca7899128033af3093a7ba86cffca5d616df55a4805a78bec860c60", - "id": "fls_RUiFQ17bmRLt", - "link": "entities-and-resolution.html#fls_RUiFQ17bmRLt", - "number": "14.7:25" + "checksum": "1d126893f14def3d9f5d85d0190f84dc53a460ce5e6da350afffa20da8714d0b", + "id": "fls_ljkjmegynhiy", + "link": "macros.html#fls_ljkjmegynhiy", + "number": "20.2.1:3" }, { - "checksum": "923e223c194e910992be0f11cedf10480449de62681514efabd485fd092a163f", - "id": "fls_hv3xT2CjZuxc", - "link": "entities-and-resolution.html#fls_hv3xT2CjZuxc", - "number": "14.7:26" + "checksum": "1a1931bdd5b2a64999e14fa18b646001ec64e8e677c906ce614ba221a5dbf2c6", + "id": "fls_8a8qhzjw5hax", + "link": "macros.html#fls_8a8qhzjw5hax", + "number": "20.2.1:4" }, { - "checksum": "7b947c9844d14caa57b996745fe9aba3027e04b17242edb5d6ba5c93d9247024", - "id": "fls_Pxc0Ts8Y7pfW", - "link": "entities-and-resolution.html#fls_Pxc0Ts8Y7pfW", - "number": "14.7:27" + "checksum": "589f04afe8c234ee2c179f3f0931eaded30292da86287719098525edb953002c", + "id": "fls_ofzql79i9if", + "link": "macros.html#fls_ofzql79i9if", + "number": "20.2.1:5" }, { - "checksum": "17b551577a4582157ce3c9a4dd5f1f653ac4f0fcca4d9b35db57fcb2edc04dda", - "id": "fls_wB3fVglLOqbZ", - "link": "entities-and-resolution.html#fls_wB3fVglLOqbZ", - "number": "14.7:28" + "checksum": "67268d716ea7849d7125350436f4e4a6d5d38effc37ae5216408b2690ab8d033", + "id": "fls_j1wsyzip2qb3", + "link": "macros.html#fls_j1wsyzip2qb3", + "number": "20.2.1:6" }, { - "checksum": "96787ea2ff0300ae0e9ddc0818a3d47b177e6ce4c85c2c850a2784a954fa5c27", - "id": "fls_zmYSBW995kSN", - "link": "entities-and-resolution.html#fls_zmYSBW995kSN", - "number": "14.7:29" + "checksum": "ba0099799988b2f6d17799a6f6f9c33b477183b8eeec921be942c1c46fe6e668", + "id": "fls_etyo9bmzxby6", + "link": "macros.html#fls_etyo9bmzxby6", + "number": "20.2.1:7" }, { - "checksum": "74a4e0482ac24acf7980f76673590ee8dd4391bf49277003e96558de9179fbfe", - "id": "fls_5dlnffim6fso", - "link": "entities-and-resolution.html#fls_5dlnffim6fso", - "number": "14.7:30" + "checksum": "7de42cd159846bf2a84aba1bcaf2c1f39e0a6092c7db776d8abb5854419ee2c1", + "id": "fls_mkl9b38m0sf1", + "link": "macros.html#fls_mkl9b38m0sf1", + "number": "20.2.1:8" }, { - "checksum": "6a1fe59e480a0b1ad4b849816298fff68ba63985da52c560be04b860192302b0", - "id": "fls_9rhflreuubhq", - "link": "entities-and-resolution.html#fls_9rhflreuubhq", - "number": "14.7:31" + "checksum": "6e54b9a8b956ed9a206672b6145a346a9d87f0734de85341ef04814529bcee31", + "id": "fls_lfmb22bfnrye", + "link": "macros.html#fls_lfmb22bfnrye", + "number": "20.2.1:9" }, { - "checksum": "450d1f7e6a218347a2975757a64915986a919f11db07da137f1b613c0651d553", - "id": "fls_s86dgrdpl1w4", - "link": "entities-and-resolution.html#fls_s86dgrdpl1w4", - "number": "14.7:32" + "checksum": "78e85689e473377a80352ef6fe022993d405de27b2c6c0393cc70edc6d40e0e8", + "id": "fls_fbgal48cgj44", + "link": "macros.html#fls_fbgal48cgj44", + "number": "20.2.1:10" } ], - "title": "Use Imports" + "title": "Function-like Macros" }, { - "id": "fls_ydmnb7qnmzzq", + "id": "fls_o8s3r7m90q59", "informational": false, - "link": "entities-and-resolution.html#shadowing", - "number": "14.8", + "link": "macros.html#derive-macros", + "number": "20.2.2", "paragraphs": [ { - "checksum": "eb93934c961caf16b01776dfdbf9b28bd8d888368b6ad7e96ed926a29f22bbf4", - "id": "fls_ob0riinmitkl", - "link": "entities-and-resolution.html#fls_ob0riinmitkl", - "number": "14.8:1" + "checksum": "aeee3f491d3c18c862f68c8f41eed353a109270ae1f717f9d5620bd9e311fbc6", + "id": "fls_e5x92q2rq8a0", + "link": "macros.html#fls_e5x92q2rq8a0", + "number": "20.2.2:1" }, { - "checksum": "6e3390d623ef6fb40f9e1687c6a8225b9eba4a6e72844ff3d2c96319a35975fc", - "id": "fls_fslg89a70e3n", - "link": "entities-and-resolution.html#fls_fslg89a70e3n", - "number": "14.8:2" + "checksum": "5242e5a075162a366a5adf0a32e20e73a4ed4c93a3b63541a8201de95af0cbfe", + "id": "fls_ldw75sy5uj7p", + "link": "macros.html#fls_ldw75sy5uj7p", + "number": "20.2.2:2" }, { - "checksum": "db3d661af677fa5cf3df3f945d076f9f7636833ffe1dda2753c0bd7295c67912", - "id": "fls_hp3f4r3399kt", - "link": "entities-and-resolution.html#fls_hp3f4r3399kt", - "number": "14.8:3" + "checksum": "77dd4bd0709e93df0c9294bd185bd0cb487a9e1991042f0cb0ecffb5df13bc60", + "id": "fls_7gcnui9beky", + "link": "macros.html#fls_7gcnui9beky", + "number": "20.2.2:3" }, { - "checksum": "418ed55705f495765526f09bf71b0e5b2725b130e6f0fce6aef783c6ebca4258", - "id": "fls_z8qjpskt13yq", - "link": "entities-and-resolution.html#fls_z8qjpskt13yq", - "number": "14.8:4" + "checksum": "7fa2a69e91d4ee885cc21d70cb2d326b60305f03bba8a17a36209a53457749bb", + "id": "fls_ef30ropg7dhx", + "link": "macros.html#fls_ef30ropg7dhx", + "number": "20.2.2:4" }, { - "checksum": "7bb5774d32bb848596abc22f98835526687c7290c92e9a0c480006e2ff78fcee", - "id": "fls_i0gp1y38lr73", - "link": "entities-and-resolution.html#fls_i0gp1y38lr73", - "number": "14.8:5" + "checksum": "700ee188839152335fdb7422422a37ae9cb40c8bb9bc9940787cd260d61e7276", + "id": "fls_mo00vqm9xfqc", + "link": "macros.html#fls_mo00vqm9xfqc", + "number": "20.2.2:5" }, { - "checksum": "83eb26105dc85d77872ef84d2a9ee034d52e70b80e1aebf1653955eb3298caad", - "id": "fls_7pif12rt4s4s", - "link": "entities-and-resolution.html#fls_7pif12rt4s4s", - "number": "14.8:6" + "checksum": "d8d72a951c13aa5fe8714a16539462fbbaf7ead2c1ad8ecd97cf3071f5db6ab8", + "id": "fls_gr9wugeqyb3b", + "link": "macros.html#fls_gr9wugeqyb3b", + "number": "20.2.2:6" }, { - "checksum": "2b077c03fd28171c667fe2cf4b197a2f463d319e9fc967f4718fc7013e240522", - "id": "fls_are9qz67p7b6", - "link": "entities-and-resolution.html#fls_are9qz67p7b6", - "number": "14.8:7" + "checksum": "6a638cc0c5ef1383ed2612ba3ab85735c70a35cbabcde5839d61b426d3411406", + "id": "fls_npnze2cg8ae", + "link": "macros.html#fls_npnze2cg8ae", + "number": "20.2.2:7" }, { - "checksum": "c67d0f504519dd854e9530bae15b45de6b718e9b8c41f7cbf3e5178c96e8c04b", - "id": "fls_4tis5syofyg0", - "link": "entities-and-resolution.html#fls_4tis5syofyg0", - "number": "14.8:8" + "checksum": "704edbd352cfa964eac3571f6d328a271b59ec3801b0d4deb1b3ff167d9edcbf", + "id": "fls_w2h4lk6bmht", + "link": "macros.html#fls_w2h4lk6bmht", + "number": "20.2.2:8" }, { - "checksum": "77ce43e27ab83c36305d9a185a5ca04f7f8829673d2b5697f77fd89d032f6cbe", - "id": "fls_u0tsnkhacr06", - "link": "entities-and-resolution.html#fls_u0tsnkhacr06", - "number": "14.8:9" + "checksum": "f217c6bc6e853a2b8556dbc0b41314003eaa542eca75e8a17d1573264d4e2151", + "id": "fls_x96a0xzcyrko", + "link": "macros.html#fls_x96a0xzcyrko", + "number": "20.2.2:9" }, { - "checksum": "28721531162ef5d6094a8ccb50a419eb494630777d043b260cc2e84dd9e65180", - "id": "fls_iaklf84guczc", - "link": "entities-and-resolution.html#fls_iaklf84guczc", - "number": "14.8:10" + "checksum": "a284d52790bdad317fdaac6d6bb30541a4dba804471aa74bf317849a3eba4ca2", + "id": "fls_caa16usjxryg", + "link": "macros.html#fls_caa16usjxryg", + "number": "20.2.2:10" }, { - "checksum": "849224ffd513f258a847928a943322879b28ee64055165e1ea8d3bac7ae0e624", - "id": "fls_a0zovslu2v4u", - "link": "entities-and-resolution.html#fls_a0zovslu2v4u", - "number": "14.8:11" + "checksum": "81811154b26e520b237186a9289476a52adca1dac91598bea29f6d2f8ad5c0af", + "id": "fls_H5ipqqlH3pJh", + "link": "macros.html#fls_H5ipqqlH3pJh", + "number": "20.2.2:11" + }, + { + "checksum": "99a2dde0b9166346d32d075006f6fff461527d79389470e64b255fb7a9f63733", + "id": "fls_mobky5ck1mi", + "link": "macros.html#fls_mobky5ck1mi", + "number": "20.2.2:12" } ], - "title": "Shadowing" + "title": "Derive Macros" }, { - "id": "fls_40xoego2thsp", + "id": "fls_4vjbkm4ceymk", "informational": false, - "link": "entities-and-resolution.html#resolution", - "number": "14.9", + "link": "macros.html#attribute-macros", + "number": "20.2.3", "paragraphs": [ { - "checksum": "9dc68fa012d432e6ce69d9945c40a38ec3fa96e38601d3106e03acfe663fdc95", - "id": "fls_ho4kem1slcxg", - "link": "entities-and-resolution.html#fls_ho4kem1slcxg", - "number": "14.9:1" + "checksum": "a507e2ee0a755f9e9bef9483a14b746466426c98185a2634629bff10acf93070", + "id": "fls_l3epi1dqpi8o", + "link": "macros.html#fls_l3epi1dqpi8o", + "number": "20.2.3:1" }, { - "checksum": "66ebea6978cb1daf3e00dfeebbba4e819b907b02a40349e360a6b094881ff7ff", - "id": "fls_7le2vcdbtxbq", - "link": "entities-and-resolution.html#fls_7le2vcdbtxbq", - "number": "14.9:2" + "checksum": "ad536eef612c80a23662ccb5976e09fc7a7cd564b3099362de958cb078de4636", + "id": "fls_3sublbi9bz7k", + "link": "macros.html#fls_3sublbi9bz7k", + "number": "20.2.3:2" }, { - "checksum": "f1ca8332615aeed822d795872f0809ed29a4e1d2160faa1ee7da8b3e1f6e6346", - "id": "fls_x3alg07yd7hx", - "link": "entities-and-resolution.html#fls_x3alg07yd7hx", - "number": "14.9:3" + "checksum": "0e6bc1537645755a80173fb2e360056fe473f07b84fa83e420a2119802b5688e", + "id": "fls_eb8jxl70wmeh", + "link": "macros.html#fls_eb8jxl70wmeh", + "number": "20.2.3:3" }, { - "checksum": "1697af6564bdffc62e51d3c846e72831c1c12e73fe2790f11c7a21ebfaa07a71", - "id": "fls_4hulwazdu20i", - "link": "entities-and-resolution.html#fls_4hulwazdu20i", - "number": "14.9:4" + "checksum": "65f907abc0b4741ed3d04dfb54bf57f7733475bd00f7eb52a681cf2df6dd26cf", + "id": "fls_7ugtmobgb2t9", + "link": "macros.html#fls_7ugtmobgb2t9", + "number": "20.2.3:4" }, { - "checksum": "f79f3efcbf1195ffe54c0a60acbf4bf5874f53ef8c0b36f0851ce886a40598d3", - "id": "fls_ptocwx5p25lj", - "link": "entities-and-resolution.html#fls_ptocwx5p25lj", - "number": "14.9:5" + "checksum": "ef5cb13d4e805e4e81816f26647b316792ceb249829578bfaa57070acb415879", + "id": "fls_y700oif45wum", + "link": "macros.html#fls_y700oif45wum", + "number": "20.2.3:5" }, { - "checksum": "a131476f47296126bd7d484e19183efe874d331eb45abb3ea465295d1c0645ca", - "id": "fls_ygam5nisv98c", - "link": "entities-and-resolution.html#fls_ygam5nisv98c", - "number": "14.9:6" - } - ], - "title": "Resolution" - }, - { - "id": "fls_xcwfotmq2e5d", - "informational": false, - "link": "entities-and-resolution.html#field-resolution", - "number": "14.9.1", - "paragraphs": [ + "checksum": "a67726cd1b3c0fa718910126228b74a584cd8d14685cff5a51c66aecafa25a2e", + "id": "fls_hhsf1a9p6o55", + "link": "macros.html#fls_hhsf1a9p6o55", + "number": "20.2.3:6" + }, { - "checksum": "aaf0db1f36f8f15b96618b5a70cec4a15e5d625748a8590acdfb9e046f222dd1", - "id": "fls_1nxknwjdp0am", - "link": "entities-and-resolution.html#fls_1nxknwjdp0am", - "number": "14.9.1:1" + "checksum": "85bf012ed3c7f1fe9e724f38a2d891b1697ec17ecde0a3277e037fe496069d6a", + "id": "fls_4g932k8ueyqp", + "link": "macros.html#fls_4g932k8ueyqp", + "number": "20.2.3:7" }, { - "checksum": "66cc3c2c1a5ec6dd71396911c8316e01e72ca51ce4bc489387e1e7b0b3371827", - "id": "fls_j1bip4w30q8", - "link": "entities-and-resolution.html#fls_j1bip4w30q8", - "number": "14.9.1:2" + "checksum": "301c590c9b06bc5965288c7611ce54546237f2ab270ad8d08ada06606d3129bf", + "id": "fls_f5qy1pnlbpng", + "link": "macros.html#fls_f5qy1pnlbpng", + "number": "20.2.3:8" }, { - "checksum": "19df557d0cd32382f3a39cabc59987441fef33fd672cfe083367c1432ed20de2", - "id": "fls_jrk3gzqvqr8e", - "link": "entities-and-resolution.html#fls_jrk3gzqvqr8e", - "number": "14.9.1:3" + "checksum": "db387e1df17fe1b34f0785313cbd032471da2d489046fbbd57783da324ddd87b", + "id": "fls_rzn48xylk4yj", + "link": "macros.html#fls_rzn48xylk4yj", + "number": "20.2.3:9" }, { - "checksum": "8a8e13fcffd5809ce5f90c8ea9cefec9fa5ca58b1bea8566b16d1fc9a6040f1d", - "id": "fls_asn20qx16sr6", - "link": "entities-and-resolution.html#fls_asn20qx16sr6", - "number": "14.9.1:4" + "checksum": "ae2d00ba71cdeeebcbc5ba13db842b607d9367375f608225d84eeb55ce0afb90", + "id": "fls_78400zh02sdq", + "link": "macros.html#fls_78400zh02sdq", + "number": "20.2.3:10" }, { - "checksum": "f7516801073f4902b37f8c6a2860b920595b14089f145b432e64e340de0fca99", - "id": "fls_jzoon4x89zp7", - "link": "entities-and-resolution.html#fls_jzoon4x89zp7", - "number": "14.9.1:5" + "checksum": "130f778e0740bb2eac48f0b045d7c7d7854a7f114684fd426c548a5289d530b9", + "id": "fls_eyesmvuwpjn1", + "link": "macros.html#fls_eyesmvuwpjn1", + "number": "20.2.3:11" }, { - "checksum": "7a2ade6ee042d2fa1254f9e3a047fe526e3618d2f949b87f34a08121a165eb4f", - "id": "fls_r80pixfoe5hk", - "link": "entities-and-resolution.html#fls_r80pixfoe5hk", - "number": "14.9.1:6" + "checksum": "a9469713d62e5ab294be5b5463896a680482fe532ef2db60c9a17dd040147917", + "id": "fls_fku5beu3mr4c", + "link": "macros.html#fls_fku5beu3mr4c", + "number": "20.2.3:12" }, { - "checksum": "3c9f984a5a8cf97fecfc89b84a15e8ae272e62c460b0988610134cebefe43a63", - "id": "fls_40oa0j6aiop3", - "link": "entities-and-resolution.html#fls_40oa0j6aiop3", - "number": "14.9.1:7" + "checksum": "1f2493e72099adeeabdbdb5af0e9a9021fe478c82c788215781e06312b59c4e4", + "id": "fls_knjsslplv5ri", + "link": "macros.html#fls_knjsslplv5ri", + "number": "20.2.3:13" + } + ], + "title": "Attribute Macros" + }, + { + "id": "fls_vnvt40pa48n8", + "informational": false, + "link": "macros.html#macro-invocation", + "number": "20.3", + "paragraphs": [ + { + "checksum": "2d88086f369da9c00a710d6478ff80775749dcb789b8f28b671fa96d22b5c01e", + "id": "fls_wushtmw9qt3y", + "link": "macros.html#fls_wushtmw9qt3y", + "number": "20.3:1" }, { - "checksum": "9f3f6ee5e9400959e3a4445be267664e3ccdec6bf0887f243d3696b27b6c2ac2", - "id": "fls_2bp1zs7qaz7o", - "link": "entities-and-resolution.html#fls_2bp1zs7qaz7o", - "number": "14.9.1:8" + "checksum": "974d06554ff28647c4f903fa86768f1e175bc518e3c538c7d26f09ca32580f3a", + "id": "fls_snpxxcqhtjfv", + "link": "macros.html#fls_snpxxcqhtjfv", + "number": "20.3:2" }, { - "checksum": "1245cb0d297861a8c0266d9628d353be5f835d30038c5d29c99d6ebd0af2a540", - "id": "fls_s14fegwhwnc8", - "link": "entities-and-resolution.html#fls_s14fegwhwnc8", - "number": "14.9.1:9" + "checksum": "9331eac9772bb3030cfa592afd0542c15f135b1445df9e764e483111e6e799e5", + "id": "fls_6v06zvi1ctub", + "link": "macros.html#fls_6v06zvi1ctub", + "number": "20.3:3" }, { - "checksum": "1fa259887a9405988fa7b3381ca5a486089f705f216bc750d058d170e8641a6d", - "id": "fls_tfjm27ydiake", - "link": "entities-and-resolution.html#fls_tfjm27ydiake", - "number": "14.9.1:10" + "checksum": "7b9182c06cf00fd455b1965ab8a30dda3df6d618c935ed284f6ca291707e0599", + "id": "fls_338rmbazl67o", + "link": "macros.html#fls_338rmbazl67o", + "number": "20.3:4" }, { - "checksum": "0ac084f03e5a81020440616fa419fc5d5b6861d4a201ef74c1be61813fdeca03", - "id": "fls_p6hgoqo0kcx", - "link": "entities-and-resolution.html#fls_p6hgoqo0kcx", - "number": "14.9.1:11" + "checksum": "f0d9a1f0b0eab822233723ad23f52e94ac8af7a075116203900e841cce37b8ed", + "id": "fls_lrr7gg8tian", + "link": "macros.html#fls_lrr7gg8tian", + "number": "20.3:5" }, { - "checksum": "4e24dc92aa06d5b4ad0e1df6508c329176170f30e1a94c6692df3e2e12215796", - "id": "fls_e7sj392ohvbd", - "link": "entities-and-resolution.html#fls_e7sj392ohvbd", - "number": "14.9.1:12" + "checksum": "2f1f05665a7725eb786fccfba3d43fc78ce77ef15cfcf47af07e6cb0c9b8b6ff", + "id": "fls_8qxwwf4trnl", + "link": "macros.html#fls_8qxwwf4trnl", + "number": "20.3:6" }, { - "checksum": "07f1cdb38f9bd8c127759860f4a51b104e71f573c2880fceec9bb97d393d32aa", - "id": "fls_z6qt9obbhhcg", - "link": "entities-and-resolution.html#fls_z6qt9obbhhcg", - "number": "14.9.1:13" + "checksum": "c0738db650ac62715e9597df801371e725fa1a1faf26794dba7b764fe0e1c70b", + "id": "fls_8z1sgtvchhhw", + "link": "macros.html#fls_8z1sgtvchhhw", + "number": "20.3:7" }, { - "checksum": "c7857117b11bfd871d7e3b709cd608d66f29d41ebccc028b2b47eb0a71ae7fd5", - "id": "fls_ljnjxex3u5o", - "link": "entities-and-resolution.html#fls_ljnjxex3u5o", - "number": "14.9.1:14" + "checksum": "27e64c85ad3e00def820e0904a4aa2d4beaac06c9fb0feccce519d3b3d81492d", + "id": "fls_d9w3dn2yn7mo", + "link": "macros.html#fls_d9w3dn2yn7mo", + "number": "20.3:8" }, { - "checksum": "bb0494028f9d19ce98975cf71392baf6f4b01e861bd44b13c4eec3780403a9fa", - "id": "fls_nm06mru40tyg", - "link": "entities-and-resolution.html#fls_nm06mru40tyg", - "number": "14.9.1:15" + "checksum": "a5ebe407976f7eb1733d25250fff1be138a66bf342fac15d3226e09e6ffbcb31", + "id": "fls_1tftbd91yfpd", + "link": "macros.html#fls_1tftbd91yfpd", + "number": "20.3:9" } ], - "title": "Field Resolution" + "title": "Macro Invocation" }, { - "id": "fls_wqazkzle0ix9", + "id": "fls_wjldgtio5o75", "informational": false, - "link": "entities-and-resolution.html#method-resolution", - "number": "14.9.2", + "link": "macros.html#macro-expansion", + "number": "20.4", "paragraphs": [ { - "checksum": "506663a2566f85e068c82d0103f1ca78bd821cb68b77fbdbcd52c1072331ad09", - "id": "fls_e5a5z5yht26l", - "link": "entities-and-resolution.html#fls_e5a5z5yht26l", - "number": "14.9.2:1" + "checksum": "8026222297277fbad4ed1549d49f0c3e75d7e5157adbb94d90360ceb716f2a3d", + "id": "fls_xscdaxvs4wx4", + "link": "macros.html#fls_xscdaxvs4wx4", + "number": "20.4:1" }, { - "checksum": "29e3e853007709641b0c2182f3e9bc70020808d3869acd0dcfa131141a8c00a0", - "id": "fls_mbdS0xiNlj92", - "link": "entities-and-resolution.html#fls_mbdS0xiNlj92", - "number": "14.9.2:2" + "checksum": "81bdfdb7218e324273181015cf2a0058601e53a6f07875e9c55f6b9ccd110600", + "id": "fls_nz5stwcc41gk", + "link": "macros.html#fls_nz5stwcc41gk", + "number": "20.4:2" }, { - "checksum": "c89b01c453ba585643631dc3b2963abc8b1e4c53933b3721b3dfc7101393d2d2", - "id": "fls_z80ylmlu1f3q", - "link": "entities-and-resolution.html#fls_z80ylmlu1f3q", - "number": "14.9.2:3" + "checksum": "bf49814afca1afc4a1ea61da7acaf9bb0ac3da4e8a3d40a8a0ad770017aaf4e2", + "id": "fls_40xq8Ri1OMZZ", + "link": "macros.html#fls_40xq8Ri1OMZZ", + "number": "20.4:3" }, { - "checksum": "0cf16c00febccc266cbb1905aa697021947ef02980576e67df40efe5cf32c0a4", - "id": "fls_e1029pvq706h", - "link": "entities-and-resolution.html#fls_e1029pvq706h", - "number": "14.9.2:4" + "checksum": "f2e3b68bee58b99bd43a9238014702fed0da098493aea6cd5ee8c35d689b2cfb", + "id": "fls_76prdp6k1fga", + "link": "macros.html#fls_76prdp6k1fga", + "number": "20.4:4" }, { - "checksum": "7d99ef0d1b75056141e26deea64ad1a7c9653284138e25b5fdad0f5c30a465b0", - "id": "fls_w3ik83d43fr1", - "link": "entities-and-resolution.html#fls_w3ik83d43fr1", - "number": "14.9.2:5" + "checksum": "74b6ccc123fd68357e50a1ba7a24fcfb694a68271bd2c7156e558945c62bb744", + "id": "fls_76u274l4kew8", + "link": "macros.html#fls_76u274l4kew8", + "number": "20.4:5" }, { - "checksum": "8e912acb711b0d365e2b30de2a8c67f34bfc9132756c0ce068ee0cccafb9d8d4", - "id": "fls_pybv4krsvktv", - "link": "entities-and-resolution.html#fls_pybv4krsvktv", - "number": "14.9.2:6" + "checksum": "41396541fe1131f8e3a05e47eb4dc795aca82ca7744db28c5808acbbcec8c548", + "id": "fls_lakpily1zwfl", + "link": "macros.html#fls_lakpily1zwfl", + "number": "20.4:6" }, { - "checksum": "2f5e3e2a6ede7f8249c7456439a30d94da8d1baf1c53135ae9fbdde73ae46077", - "id": "fls_m2njj6no0p1i", - "link": "entities-and-resolution.html#fls_m2njj6no0p1i", - "number": "14.9.2:7" + "checksum": "145096281559894c5fdd9c34f4c59020e8018a542a295c4718a2158eb1c24db6", + "id": "fls_y20pmwo3v3uu", + "link": "macros.html#fls_y20pmwo3v3uu", + "number": "20.4:7" }, { - "checksum": "72843d941971110fefb9a9b6995ef90cd5b564d6639651630b631e41ee4e491e", - "id": "fls_16l2q1wpcnbp", - "link": "entities-and-resolution.html#fls_16l2q1wpcnbp", - "number": "14.9.2:8" + "checksum": "73e7c3d42509b26c6836a37c592ce6b5f8b4b8d51bdd13b1e69c781dbdb074ac", + "id": "fls_nsh2vwx8oiw", + "link": "macros.html#fls_nsh2vwx8oiw", + "number": "20.4:8" }, { - "checksum": "6d27f6b19310c77c713cea4e5c7bb344db8cbb3fdf479386c7233e155236809f", - "id": "fls_fcnahkqxomuo", - "link": "entities-and-resolution.html#fls_fcnahkqxomuo", - "number": "14.9.2:9" + "checksum": "2e54f1caa1c75bb8c551cb037444610dab84c77e38bccd44a64d81885462bfb0", + "id": "fls_tu6kmwm4v9nj", + "link": "macros.html#fls_tu6kmwm4v9nj", + "number": "20.4:9" }, { - "checksum": "cb39263ec9ad7cd027df0b037770018592ea1b93c52e02c1e911048464995f86", - "id": "fls_ii0fdpekn1qt", - "link": "entities-and-resolution.html#fls_ii0fdpekn1qt", - "number": "14.9.2:10" + "checksum": "3b01afcc0d4c9f2696ea110f2556ea0c37503ac92544dd77a5853e2bc4cc735c", + "id": "fls_3zn4dz19nyvq", + "link": "macros.html#fls_3zn4dz19nyvq", + "number": "20.4:10" }, { - "checksum": "e7745cebb4a1cfb9d08a5ec1b5827d5cb745348158eb1ab0b3677d950118e4c7", - "id": "fls_ohjmxhbw3nx3", - "link": "entities-and-resolution.html#fls_ohjmxhbw3nx3", - "number": "14.9.2:11" + "checksum": "39601be41eaf38c2fe68a562e68628c4c897e8c99f95ec75e583dbefab3af157", + "id": "fls_t89sw6az99z7", + "link": "macros.html#fls_t89sw6az99z7", + "number": "20.4:11" }, { - "checksum": "d9a23a581045be380483ef64273fb0970eb3a080cffb32f83f6ff8e0922acd5c", - "id": "fls_lgpdicxxwq13", - "link": "entities-and-resolution.html#fls_lgpdicxxwq13", - "number": "14.9.2:12" + "checksum": "48dbce0bae72db846dd146bad2ff656a10cf5dd99c403720ad8a65ff71bf965e", + "id": "fls_417hvhvj2554", + "link": "macros.html#fls_417hvhvj2554", + "number": "20.4:12" }, { - "checksum": "d684bf9d23507a618c9721f7ea2ec85c2736c06dbee658fa864d22a5e32c5d44", - "id": "fls_ugl3x4y3lli2", - "link": "entities-and-resolution.html#fls_ugl3x4y3lli2", - "number": "14.9.2:13" + "checksum": "10c68086f5de7156157c189ebd2864036d21287d9219fdce6f5e9df7658f3e2a", + "id": "fls_nNrs4EC3ff5T", + "link": "macros.html#fls_nNrs4EC3ff5T", + "number": "20.4:13" }, { - "checksum": "8e4b764f8f8a09f136954a2bfc947ddc4eda420f1148fad92fc5fa7a24c12c13", - "id": "fls_bb4cbmvui8fk", - "link": "entities-and-resolution.html#fls_bb4cbmvui8fk", - "number": "14.9.2:14" + "checksum": "303da91f4606ee986ceddcb3a6dd2990ea1a5d6f4d8ff9eba1411b05f9cfa29c", + "id": "fls_srtqkdceaz5t", + "link": "macros.html#fls_srtqkdceaz5t", + "number": "20.4:14" }, { - "checksum": "66ebe678e1ae9102e4d8521706cef63daeae10b848e263ab3766e4aa6cc9a1d9", - "id": "fls_5wny1yxbyuz0", - "link": "entities-and-resolution.html#fls_5wny1yxbyuz0", - "number": "14.9.2:15" + "checksum": "57347ca4d51ad58880a851280bfb8d8e529e1b9951a52ca4929e47b94f19be4e", + "id": "fls_mi92etjtpamu", + "link": "macros.html#fls_mi92etjtpamu", + "number": "20.4:15" }, { - "checksum": "17c39504c2086447190664d68d7a3eae124510357ebc7f144f3d1b25f7026518", - "id": "fls_gsc8pt4tlsqv", - "link": "entities-and-resolution.html#fls_gsc8pt4tlsqv", - "number": "14.9.2:16" + "checksum": "9a60784d14b99e43a10674a97c9f853c1fda603311694eaa435886353f7102ab", + "id": "fls_n8beqlt54rhy", + "link": "macros.html#fls_n8beqlt54rhy", + "number": "20.4:16" }, { - "checksum": "9aed381332445df712568775e61af445c35e6c942e4722e7a4d38140ff9b3fa3", - "id": "fls_tfglce1wuq5q", - "link": "entities-and-resolution.html#fls_tfglce1wuq5q", - "number": "14.9.2:17" + "checksum": "04d7c36c59b8db5f9cd9d437097d1287b1462b3d1a28b1af866979139758cf22", + "id": "fls_vd3dzvr6re19", + "link": "macros.html#fls_vd3dzvr6re19", + "number": "20.4:17" }, { - "checksum": "04832d6c14ff2380c1a19024f980142d165adb58520a40c0c8af80ffe1fb55c8", - "id": "fls_64bfcn9okeve", - "link": "entities-and-resolution.html#fls_64bfcn9okeve", - "number": "14.9.2:18" + "checksum": "8738a7304a24b50b3140d449d1bfca2c9baf6da9f0d078a51401795554fd4474", + "id": "fls_l8j2jiuuao4f", + "link": "macros.html#fls_l8j2jiuuao4f", + "number": "20.4:18" }, { - "checksum": "3700640ef18118308cf59e04636cc5b6f1c574e9b6d802e29ef33f0d5814bf71", - "id": "fls_om90v9re8b2l", - "link": "entities-and-resolution.html#fls_om90v9re8b2l", - "number": "14.9.2:19" + "checksum": "d26201e9ec1b8ce4e8d0ce918281c597b4207dd5c717ab9d2f690dc2d3fa69f6", + "id": "fls_xvemyqj5gc6g", + "link": "macros.html#fls_xvemyqj5gc6g", + "number": "20.4:19" }, { - "checksum": "f01b3809e31f3e711b3d853cd5dc8d1acdc431c60f9d0c04452abeccb6190de6", - "id": "fls_bsf4hy9x7c2e", - "link": "entities-and-resolution.html#fls_bsf4hy9x7c2e", - "number": "14.9.2:20" + "checksum": "9cd6b3fdd12de5819ef5f034cd512142d9ea7275dd351bb3364534aede42230d", + "id": "fls_stseor6tln22", + "link": "macros.html#fls_stseor6tln22", + "number": "20.4:20" }, { - "checksum": "e610b83f08f3c1ffeb1693c97a13b45472e77067e9caff1501681e6e4ff7633c", - "id": "fls_cnn5hkf1z5q4", - "link": "entities-and-resolution.html#fls_cnn5hkf1z5q4", - "number": "14.9.2:21" + "checksum": "785c20cd4ee512f170f8821ae84ac3e0164356afb744c5eb7766d1b428f43f95", + "id": "fls_u11o90szy68s", + "link": "macros.html#fls_u11o90szy68s", + "number": "20.4:21" }, { - "checksum": "4380ca472e236c4d884dc55c2ab8da71766dbea2d3cc0895b010c2c48c9df55b", - "id": "fls_j9ho6xc2fj0w", - "link": "entities-and-resolution.html#fls_j9ho6xc2fj0w", - "number": "14.9.2:22" + "checksum": "42ff8c1ac6cc0dad7129ac746debad5fa96d773994a30fdf5b71496e0fe894be", + "id": "fls_qi5kyvj1e8th", + "link": "macros.html#fls_qi5kyvj1e8th", + "number": "20.4:22" }, { - "checksum": "2cd76e3de2de59d6830b6ea7fef86225bf36e7cfcecb6ebec75030fcfb2efc73", - "id": "fls_1y94elgpg0uk", - "link": "entities-and-resolution.html#fls_1y94elgpg0uk", - "number": "14.9.2:23" + "checksum": "75411eced63ec88091cc61ad48545c56af50d13cdfbb58da468cfcd471b1d753", + "id": "fls_vqIZaEl4EKu5", + "link": "macros.html#fls_vqIZaEl4EKu5", + "number": "20.4:23" + }, + { + "checksum": "8336e9a3dc539d94ebc4c3f4d1db0a002daac4a11ed04f66f0c8944f7c59dadc", + "id": "fls_grtiwf7q8jah", + "link": "macros.html#fls_grtiwf7q8jah", + "number": "20.4:24" + }, + { + "checksum": "862660c9a7927486f4ab97a5219efca956468b25a372e7f0b8778d938fea614f", + "id": "fls_tbe2qq7whq10", + "link": "macros.html#fls_tbe2qq7whq10", + "number": "20.4:25" + }, + { + "checksum": "56c2e17cefa41716653ddebd7376d5e765866b6b48613007cbd6fa266ec588a1", + "id": "fls_my93neopj9x0", + "link": "macros.html#fls_my93neopj9x0", + "number": "20.4:26" }, { - "checksum": "4b79cb54112a7172b9965ddcc0ee752f7f3490dd18a445e79bd25bddb06908c0", - "id": "fls_npsdxrtcslcf", - "link": "entities-and-resolution.html#fls_npsdxrtcslcf", - "number": "14.9.2:24" + "checksum": "6f0481d9039a52034b6e9c95142a26b01f4af587546d9d2ca76ccdcdce2cf14c", + "id": "fls_zat7kwi5vc5c", + "link": "macros.html#fls_zat7kwi5vc5c", + "number": "20.4:27" }, { - "checksum": "5f2072da8fa1e8d184be3da512c28aed044a859e8fcd3ffe93c952419a84e822", - "id": "fls_yv5l823lwdsv", - "link": "entities-and-resolution.html#fls_yv5l823lwdsv", - "number": "14.9.2:25" + "checksum": "4ed573b9e2ba6991e0c8cd77151c8f7fb439090236dff6dcba9b8dd9d06d2604", + "id": "fls_tjn92evtlflq", + "link": "macros.html#fls_tjn92evtlflq", + "number": "20.4:28" }, { - "checksum": "c3218b553ed9d95d1235f5fc0fd5281890f0bf12ace5c8649ae55530225d53e1", - "id": "fls_ckdoyvbaybe0", - "link": "entities-and-resolution.html#fls_ckdoyvbaybe0", - "number": "14.9.2:26" + "checksum": "9e9dd4998f98b6a17410d0f5050a5640b8a7d12b16d5ccc1fbf5ddfc4fa2c910", + "id": "fls_AJmPrhHfZo6J", + "link": "macros.html#fls_AJmPrhHfZo6J", + "number": "20.4:29" }, { - "checksum": "64374639950e73d231a40ccb24e159d9cd1d7dd8e03cfab109bd17a57ea06805", - "id": "fls_1azkiu20r0e4", - "link": "entities-and-resolution.html#fls_1azkiu20r0e4", - "number": "14.9.2:27" + "checksum": "d54d6148b8f96f7ae615b20d921e9536dec2d40191387304db5912c07457039c", + "id": "fls_mpgh22bi8caz", + "link": "macros.html#fls_mpgh22bi8caz", + "number": "20.4:30" }, { - "checksum": "63fa9c1342877193573e6c29b715d5deac6bfb9eb8ea49fa19c104c6063e6b4c", - "id": "fls_ose5m4bhkg57", - "link": "entities-and-resolution.html#fls_ose5m4bhkg57", - "number": "14.9.2:28" + "checksum": "ecc18764aaf923e05323ff7d8dbc3196f9c2a677d000895c4350a34de447d370", + "id": "fls_ul7nhfyvyzh", + "link": "macros.html#fls_ul7nhfyvyzh", + "number": "20.4:31" }, { - "checksum": "9977dc1614c53e251615c07e9e08e617d47505e3cdbc9b1f313d120c80533919", - "id": "fls_jw2yv23cduu4", - "link": "entities-and-resolution.html#fls_jw2yv23cduu4", - "number": "14.9.2:29" + "checksum": "61b9b13308b8c7583de5aab70fe1644dee9867664a812351e4a713132df8f246", + "id": "fls_z6xfhf71w10a", + "link": "macros.html#fls_z6xfhf71w10a", + "number": "20.4:32" } ], - "title": "Method Resolution" + "title": "Macro Expansion" }, { - "id": "fls_telbknkodx3d", + "id": "fls_4apk1exafxii", "informational": false, - "link": "entities-and-resolution.html#call-resolution", - "number": "14.9.3", + "link": "macros.html#macro-matching", + "number": "20.4.1", "paragraphs": [ { - "checksum": "e00de8ccb48972edb964b7efe2cdb1840c249fb526d14c3331b293a3cf8cf79d", - "id": "fls_ZjJ7y9r6QQMW", - "link": "entities-and-resolution.html#fls_ZjJ7y9r6QQMW", - "number": "14.9.3:1" - }, + "checksum": "6b8396da1325228a967d0d28720f37811bfec9f46d17c5b5d20ab8838236c8d1", + "id": "fls_ZmQZ8HQWv77L", + "link": "macros.html#fls_ZmQZ8HQWv77L", + "number": "20.4.1:1" + } + ], + "title": "Macro Matching" + }, + { + "id": "fls_n3ktmjqf87qb", + "informational": false, + "link": "macros.html#rule-matching", + "number": "20.4.1.1", + "paragraphs": [ { - "checksum": "18e91497757f3ccef6d3e37f0ab6fa99650ef04419b74aa5cb0faaf8d646c8c1", - "id": "fls_zBSloU2Gjv7x", - "link": "entities-and-resolution.html#fls_zBSloU2Gjv7x", - "number": "14.9.3:2" + "checksum": "86e93126d0e6c567afa83d3532a9dda63596c2a3a1b8fa60469481ff4891e786", + "id": "fls_77ucvwu6idms", + "link": "macros.html#fls_77ucvwu6idms", + "number": "20.4.1.1:1" }, { - "checksum": "d569ecf4ccc75ebed4ac9a1fd2482c4db16dcb459e321909013b8a42be1b4bf4", - "id": "fls_XBakDylF7aOG", - "link": "entities-and-resolution.html#fls_XBakDylF7aOG", - "number": "14.9.3:3" + "checksum": "4c41fb4fb8823d556cfb539659a3c4703c73a1c321684dd511b7f178f2ee5439", + "id": "fls_6h1jqhxzku5v", + "link": "macros.html#fls_6h1jqhxzku5v", + "number": "20.4.1.1:2" }, { - "checksum": "de3fc414790cfa7bddbc44846eff2f3816840e6b0249dd0e743b4ee447d3a909", - "id": "fls_kzovwqIw7ghc", - "link": "entities-and-resolution.html#fls_kzovwqIw7ghc", - "number": "14.9.3:4" + "checksum": "6ff3d3159841229f8392ab25c21c4749d84f44f9448851671427adf3fa2c97f8", + "id": "fls_r6i1ykrhb49j", + "link": "macros.html#fls_r6i1ykrhb49j", + "number": "20.4.1.1:3" }, { - "checksum": "f9822b671141f854469f2aa328c18a596471e264a51e4f3c1630f47b3b828d76", - "id": "fls_4iBdtE0Ji4Tf", - "link": "entities-and-resolution.html#fls_4iBdtE0Ji4Tf", - "number": "14.9.3:5" + "checksum": "d6cdc5aaa72a1fe038b82e1c7963c4446c2bc7668ed2fae5540f1dd6416a865f", + "id": "fls_3qzes4lr8yuv", + "link": "macros.html#fls_3qzes4lr8yuv", + "number": "20.4.1.1:4" }, { - "checksum": "c7df7b2b6d7b1fbd91b08418341411ea2e0a68ee04bf8ab36bcf50f60d63f1b8", - "id": "fls_TWR5GIUxPcaJ", - "link": "entities-and-resolution.html#fls_TWR5GIUxPcaJ", - "number": "14.9.3:6" + "checksum": "b69c21d1eb5931b6f4848c2e230a9199999a3b94ade1ff93a4c66ba685343958", + "id": "fls_r878ysvsy4jb", + "link": "macros.html#fls_r878ysvsy4jb", + "number": "20.4.1.1:5" } ], - "title": "Call Resolution" + "title": "Rule Matching" }, { - "id": "fls_i6qzga6dyaee", + "id": "fls_qpx6lgapce57", "informational": false, - "link": "entities-and-resolution.html#path-resolution", - "number": "14.9.4", + "link": "macros.html#token-matching", + "number": "20.4.1.2", "paragraphs": [ { - "checksum": "55361e7d41c5c24510d526001510c8bad4aef48dbd51f81ef0d1fe0e7aa4128c", - "id": "fls_8slvisr3jfja", - "link": "entities-and-resolution.html#fls_8slvisr3jfja", - "number": "14.9.4:1" + "checksum": "939454db28503818aeec06820dacf2ab2a82bb11e9ebd50d2b097213c7223ae4", + "id": "fls_k6a24sbon5v9", + "link": "macros.html#fls_k6a24sbon5v9", + "number": "20.4.1.2:1" }, { - "checksum": "8cd576cd86befd359dfe276c5f05be757f54ff4b4b44768f9a4b71ec18287156", - "id": "fls_nmev0tnzgw35", - "link": "entities-and-resolution.html#fls_nmev0tnzgw35", - "number": "14.9.4:2" + "checksum": "69d0900d72789c738bb16a2cd4d7cd0938784373e5835a4c87c5655968e72f06", + "id": "fls_6uuxv91xgmfz", + "link": "macros.html#fls_6uuxv91xgmfz", + "number": "20.4.1.2:2" }, { - "checksum": "a8a99061757e28c4776e91bccebf019763e6c7876ea81a92d9a1c528165aaf18", - "id": "fls_p23q1ob2qitz", - "link": "entities-and-resolution.html#fls_p23q1ob2qitz", - "number": "14.9.4:3" + "checksum": "9676823aa002fa0b34ed9ceb6cdf528e036dd054cc9680fd769fbfbabbdf2f33", + "id": "fls_g1rml9tavh8v", + "link": "macros.html#fls_g1rml9tavh8v", + "number": "20.4.1.2:3" }, { - "checksum": "537b298a9d54dde06ac687138d86b76d85a21e75ce6b341493c8b394b5239199", - "id": "fls_e9rv8dfa0arl", - "link": "entities-and-resolution.html#fls_e9rv8dfa0arl", - "number": "14.9.4:4" + "checksum": "485dc2a03abcfc575a18d12f44392c25e70e3d6c8c7defe90be8b259721e9cf2", + "id": "fls_h7x3tc208zpk", + "link": "macros.html#fls_h7x3tc208zpk", + "number": "20.4.1.2:4" }, { - "checksum": "48938e27d06bad943ff87be97fcc21540da3b77868b16a71eb117137d09d23fc", - "id": "fls_yule33qm1ok", - "link": "entities-and-resolution.html#fls_yule33qm1ok", - "number": "14.9.4:5" + "checksum": "4e3b1a4397f68e72da8d33b18764d344b2cdc18f83b97ddefee8dbdef401dc87", + "id": "fls_p9eqa17d3dx", + "link": "macros.html#fls_p9eqa17d3dx", + "number": "20.4.1.2:5" }, { - "checksum": "725b600fb8ecdc9eddfb8e2568ae2102893884fcb7c6aa4e1963c948b242dabe", - "id": "fls_7xmhm2lf2h8f", - "link": "entities-and-resolution.html#fls_7xmhm2lf2h8f", - "number": "14.9.4:6" + "checksum": "d54b6dc3d7995358cb1bb0e6672591cf1d8416a887f73cf8f3f2d2d7347d499b", + "id": "fls_k00bck2k8tde", + "link": "macros.html#fls_k00bck2k8tde", + "number": "20.4.1.2:6" }, { - "checksum": "d67675a77fe3ac8c7c6a93c703b80fb47b1307005a8bca11ae42fe53388b0e18", - "id": "fls_ec4wo8odusqp", - "link": "entities-and-resolution.html#fls_ec4wo8odusqp", - "number": "14.9.4:7" + "checksum": "9beee8340868a57b1052f0fc0b375a60221fffc91fe52f2b2a049ecea579e62e", + "id": "fls_pf0qrz5nadl2", + "link": "macros.html#fls_pf0qrz5nadl2", + "number": "20.4.1.2:7" }, { - "checksum": "ed06a87167d12f3592d3f1f3fe6b1b8dee1445d3084186ca2eb0c64e38d0a195", - "id": "fls_9tedg9lpewqa", - "link": "entities-and-resolution.html#fls_9tedg9lpewqa", - "number": "14.9.4:8" + "checksum": "944fc29dfd11666e10802285b2515966d675e8dd3dba1e06c0e50e2db56199cf", + "id": "fls_9fioah171ojx", + "link": "macros.html#fls_9fioah171ojx", + "number": "20.4.1.2:8" }, { - "checksum": "836b27b41fb978acddad30353606e589d3d7a0f959fd869e0363f5f4da38a1e2", - "id": "fls_mvymlhp7192e", - "link": "entities-and-resolution.html#fls_mvymlhp7192e", - "number": "14.9.4:9" + "checksum": "3a4366c2f44243bd2284234dca5b270e4dc4a86d1f32a617a269adb290fb8985", + "id": "fls_j2o0f52zyvyb", + "link": "macros.html#fls_j2o0f52zyvyb", + "number": "20.4.1.2:9" }, { - "checksum": "8bc4d6164a3454b1f92bc7c3b987ebd35d035f0b570af2120160837269eb6880", - "id": "fls_cs485plo4z49", - "link": "entities-and-resolution.html#fls_cs485plo4z49", - "number": "14.9.4:10" + "checksum": "e4f9ae7e642ed779024a8e69cea6afc9bd400fd2105ce496a574bcc9be0bf3d7", + "id": "fls_w5dzv3z4zd5a", + "link": "macros.html#fls_w5dzv3z4zd5a", + "number": "20.4.1.2:10" }, { - "checksum": "28e96aa4ab999e28d472ac597a459b4f3cf5355e1f3d4e36729f80527e38a9a3", - "id": "fls_yrpem8vhxpr5", - "link": "entities-and-resolution.html#fls_yrpem8vhxpr5", - "number": "14.9.4:11" + "checksum": "a6923eaff6739eb4d6492f970c783cfdabc71c275843e5ef6405fff5af5b0432", + "id": "fls_wtol98rrqka5", + "link": "macros.html#fls_wtol98rrqka5", + "number": "20.4.1.2:11" }, { - "checksum": "12d01e64bed3fb1d93c2baaba1c990b876b01143f65e13549b16e2f86722c1f8", - "id": "fls_ri50nc2dg7c4", - "link": "entities-and-resolution.html#fls_ri50nc2dg7c4", - "number": "14.9.4:12" + "checksum": "80a04645ad370bbb071324afb5d55d19b18f915e9fadfab568496d6a3a18fa5f", + "id": "fls_iorqt9q4ie9j", + "link": "macros.html#fls_iorqt9q4ie9j", + "number": "20.4.1.2:12" }, { - "checksum": "2f1892ee92de8c871fc2ae0a0108195b7e93d76975e2ed4557897be68ac335ea", - "id": "fls_to52oma1bvx3", - "link": "entities-and-resolution.html#fls_to52oma1bvx3", - "number": "14.9.4:13" + "checksum": "b45c38305f3c3ffa6bf27034b89cc6b8baac7a4e72914a0cfdf7eeee7da69266", + "id": "fls_2zjed913qpvi", + "link": "macros.html#fls_2zjed913qpvi", + "number": "20.4.1.2:13" }, { - "checksum": "e2260bf8d1cd6ed1f8f560bde5a04eda03749ba3b9698be0e9e830379cc526ac", - "id": "fls_kpn2y7xb3s8q", - "link": "entities-and-resolution.html#fls_kpn2y7xb3s8q", - "number": "14.9.4:14" + "checksum": "d58dc97395dae9ccb1d8fe2a21911f6287c2676be72fea50a94301b4b85614fb", + "id": "fls_3zdts0fsa36u", + "link": "macros.html#fls_3zdts0fsa36u", + "number": "20.4.1.2:14" + }, + { + "checksum": "0f22177e56930189a013cc165f27159e88950b6309aec5aa8f36dad4fc00070b", + "id": "fls_mb3yr1j7npv5", + "link": "macros.html#fls_mb3yr1j7npv5", + "number": "20.4.1.2:15" + }, + { + "checksum": "8b44cfe64ffb38c055354fd03755c38a67d369899996d279000749b6938b4130", + "id": "fls_xbuixjt9pum6", + "link": "macros.html#fls_xbuixjt9pum6", + "number": "20.4.1.2:16" + }, + { + "checksum": "2868c75bd1038a056153d19675c38e727498801f62dcf998f97e79d1feb762aa", + "id": "fls_6annifhk6cd8", + "link": "macros.html#fls_6annifhk6cd8", + "number": "20.4.1.2:17" }, { - "checksum": "e869f661950b3fb36bb49810ed4fa5897bab056d9061a5614f1f983004a68702", - "id": "fls_z71op1vdnazq", - "link": "entities-and-resolution.html#fls_z71op1vdnazq", - "number": "14.9.4:15" + "checksum": "8a369cdda5e38c9d0c9235244570dc6350c8f7513c2a5dc0ce5b97503f92b7ef", + "id": "fls_2zu22efr6ncy", + "link": "macros.html#fls_2zu22efr6ncy", + "number": "20.4.1.2:18" }, { - "checksum": "f1bf72c3463050e387b72d2514a1d90f3ce47bceb98da6b23877d0cf3a67ad9a", - "id": "fls_2km29ekj9464", - "link": "entities-and-resolution.html#fls_2km29ekj9464", - "number": "14.9.4:16" + "checksum": "235bfe3d83907a5ee61a001a2b2acac3b0e021e603831aed451c1cc984b6a784", + "id": "fls_dqroklsaayzb", + "link": "macros.html#fls_dqroklsaayzb", + "number": "20.4.1.2:19" }, { - "checksum": "8048713e7609c52f8f92a383762dac9c9e3c99f7aa1e987e0d6e2fe1af9c998a", - "id": "fls_l2y464skbuta", - "link": "entities-and-resolution.html#fls_l2y464skbuta", - "number": "14.9.4:17" + "checksum": "273716f2c264d37ea340ce796a784d1e2b3cb3a16578a954da0dcab1900b6ca9", + "id": "fls_ghqjk6xj85ng", + "link": "macros.html#fls_ghqjk6xj85ng", + "number": "20.4.1.2:20" }, { - "checksum": "1bf727a1dcaab6f5e8df8590d6bb1ba357978668aa0b347d821b88b023b5731b", - "id": "fls_n2x13sg5szbl", - "link": "entities-and-resolution.html#fls_n2x13sg5szbl", - "number": "14.9.4:18" + "checksum": "1ff9a6a6d1f6eb7b4c668c7940b8c2e1490c37f93b536e4b0f4468a49095216f", + "id": "fls_lzwl4en5wcw0", + "link": "macros.html#fls_lzwl4en5wcw0", + "number": "20.4.1.2:21" }, { - "checksum": "288a6623bd127a3853dec6c8c81b675f11276f49d7085cdc4252566638ef0cad", - "id": "fls_53kd7eb1qzuz", - "link": "entities-and-resolution.html#fls_53kd7eb1qzuz", - "number": "14.9.4:19" + "checksum": "622a0124d322fc811ce341d23c250c1d22df2be7065199c523a053e04f93655b", + "id": "fls_cz44evkjzv29", + "link": "macros.html#fls_cz44evkjzv29", + "number": "20.4.1.2:22" }, { - "checksum": "8d75724256d8acc0107bb9928323bf6eebea21f3cb90023f550e681bd0f4c068", - "id": "fls_3spnlz9tqnhj", - "link": "entities-and-resolution.html#fls_3spnlz9tqnhj", - "number": "14.9.4:20" + "checksum": "61ca7e8337e433a1f27f8ca82eda551971c4361a34cf11fb7c23f53368ee2593", + "id": "fls_o2exsai4m0gy", + "link": "macros.html#fls_o2exsai4m0gy", + "number": "20.4.1.2:23" }, { - "checksum": "b3276319834228734d7f152a0a6ee88d4760ced117b6e497f7775f79453e1247", - "id": "fls_Mt6xKI08viYw", - "link": "entities-and-resolution.html#fls_Mt6xKI08viYw", - "number": "14.9.4:21" + "checksum": "2b361f50a42eadd00c2c0ca748063e0d34342f34e39bae540bb8802a83f14882", + "id": "fls_1ch299zp8h7", + "link": "macros.html#fls_1ch299zp8h7", + "number": "20.4.1.2:24" }, { - "checksum": "bba6464e5850bc807bb84138c417abccf4d8e863b7cc927a7dc6c4f574742d9f", - "id": "fls_7hK059djudGN", - "link": "entities-and-resolution.html#fls_7hK059djudGN", - "number": "14.9.4:22" + "checksum": "225d33e4ff2e8e37f77d0d4ba4811088ee088d027a6ae4a1a4882ca9b039fa98", + "id": "fls_55ptfjlvoo8o", + "link": "macros.html#fls_55ptfjlvoo8o", + "number": "20.4.1.2:25" }, { - "checksum": "d976b884515464965ab2b569d6e256a24cbdd97a74803de060faefb3dcf17a9a", - "id": "fls_lxa7uhmdoy9d", - "link": "entities-and-resolution.html#fls_lxa7uhmdoy9d", - "number": "14.9.4:23" + "checksum": "17b373ea759d5c509466cee1cd0f9941a470f136a1109fb7f745dd87e096bcf9", + "id": "fls_finzfb5ljkf8", + "link": "macros.html#fls_finzfb5ljkf8", + "number": "20.4.1.2:26" }, { - "checksum": "3ceffa06884bb95ce527ddb9ed4cc31c620d57a422f0938a8257f78e4c4f844a", - "id": "fls_EcaZauRXEGcs", - "link": "entities-and-resolution.html#fls_EcaZauRXEGcs", - "number": "14.9.4:24" + "checksum": "34a0742ef58a8b7fe215596c5252975ec70b4b86687272048bf5ccbee1c21eda", + "id": "fls_s1ccs6jocsgr", + "link": "macros.html#fls_s1ccs6jocsgr", + "number": "20.4.1.2:27" }, { - "checksum": "10ebf431ca0e173e09d0e95cc99d83fa560ba88808dfbb3c22cb34e450e43483", - "id": "fls_xujlscsir05f", - "link": "entities-and-resolution.html#fls_xujlscsir05f", - "number": "14.9.4:25" + "checksum": "408711d561d0aa646b34d91388c4abb3e0e292eecb966dc171c8368efaca46c5", + "id": "fls_wpi2i6hoj3li", + "link": "macros.html#fls_wpi2i6hoj3li", + "number": "20.4.1.2:28" }, { - "checksum": "d4f7baf7b1415e0ddd6d71a215ed14fec816b15d9e58dcc2454d7f27e69004bd", - "id": "fls_wypnvfklnmc1", - "link": "entities-and-resolution.html#fls_wypnvfklnmc1", - "number": "14.9.4:26" + "checksum": "c8977ccaa62d4c406898dba6432a60322e5b401d6bd0f7cda07b4b0979bb29f6", + "id": "fls_uuey421a8n96", + "link": "macros.html#fls_uuey421a8n96", + "number": "20.4.1.2:29" }, { - "checksum": "cd2421eeddd4a05db76bdfc69b504e9b4de55915c2ef18bfaa6e927a3e72ce16", - "id": "fls_zi46lmwsn4rg", - "link": "entities-and-resolution.html#fls_zi46lmwsn4rg", - "number": "14.9.4:27" + "checksum": "4538f5121ec15ee6fbd7d3cb0bb690cc955c3d8655feceab5333045461750c15", + "id": "fls_b5u47tuu136r", + "link": "macros.html#fls_b5u47tuu136r", + "number": "20.4.1.2:30" }, { - "checksum": "5090abee767f23b1aea068bdf5cf40ac88c35f6bab4a3fe0c8da122c592d1093", - "id": "fls_bATFGtxjKq0B", - "link": "entities-and-resolution.html#fls_bATFGtxjKq0B", - "number": "14.9.4:28" + "checksum": "38cb0f7a2daa5611bc22dac0b50c555f9d3ffb1127900a2f86ba5a79ff08498a", + "id": "fls_rb1tu4e7dpma", + "link": "macros.html#fls_rb1tu4e7dpma", + "number": "20.4.1.2:31" + }, + { + "checksum": "3f33b5ba4b848845ef3c885d9b0b6d3309d5d903bf48ba507cc368dc25cad1ce", + "id": "fls_c76sdvos5xeo", + "link": "macros.html#fls_c76sdvos5xeo", + "number": "20.4.1.2:32" } ], - "title": "Path Resolution" + "title": "Token Matching" }, { - "id": "fls_bbso3c45kr9z", + "id": "fls_ym00b6ewf4n3", "informational": false, - "link": "entities-and-resolution.html#simple-path-resolution", - "number": "14.9.4.1", + "link": "macros.html#macro-transcription", + "number": "20.4.2", "paragraphs": [ { - "checksum": "c1ebb5064acb14aea9a28d26b01046f531294fbbd5ca6410cce82bf98b8d7261", - "id": "fls_uml24jw5jo7a", - "link": "entities-and-resolution.html#fls_uml24jw5jo7a", - "number": "14.9.4.1:1" - }, - { - "checksum": "253940a0312803793da657e8bb0a7e478cbc89857c2a237d03548618de1ec256", - "id": "fls_59wd7loxst43", - "link": "entities-and-resolution.html#fls_59wd7loxst43", - "number": "14.9.4.1:2" - }, - { - "checksum": "8459bc1ffb9ad72f9f9ce23cfd6b93c218419c6681b8e4a7686dae5b8ac9e309", - "id": "fls_mk0ufkeggot6", - "link": "entities-and-resolution.html#fls_mk0ufkeggot6", - "number": "14.9.4.1:3" + "checksum": "0625ef0bcc1537bbd7e48bbb52e650fac001e4ce41c99c213d6d9c46b45e74e0", + "id": "fls_y21i8062mft0", + "link": "macros.html#fls_y21i8062mft0", + "number": "20.4.2:1" }, { - "checksum": "b3db0521d2ccad1a7ff3c83e38d7bfada206a50d8fced514e5605a753bcacab8", - "id": "fls_ayv8okec9fwb", - "link": "entities-and-resolution.html#fls_ayv8okec9fwb", - "number": "14.9.4.1:4" + "checksum": "3a923fd62e7cb8e223448548b2aa38404e63cc3e8b41254c70fa1d13076ad338", + "id": "fls_n2dx4ug5nd5w", + "link": "macros.html#fls_n2dx4ug5nd5w", + "number": "20.4.2:2" }, { - "checksum": "1650a4f513fab2754fe189d6a8993c9c7855378082856282662768a8528dc575", - "id": "fls_ppoc6wcplab6", - "link": "entities-and-resolution.html#fls_ppoc6wcplab6", - "number": "14.9.4.1:5" + "checksum": "3a3dab26920d694cfc94f0c1b2f51523cd3b42dbae5b61643d7c56834c7220cf", + "id": "fls_iw7322ycvhkc", + "link": "macros.html#fls_iw7322ycvhkc", + "number": "20.4.2:3" }, { - "checksum": "f036f3d708d6307e0393c1e54c59023aa992026100787bff26b010ac6a22a467", - "id": "fls_dc0yv4306p82", - "link": "entities-and-resolution.html#fls_dc0yv4306p82", - "number": "14.9.4.1:6" + "checksum": "0138c8400ce4f71c65b6aa2722efe3060cbd1d41df1c72e0d5f3b34a20f300b5", + "id": "fls_jgitbqmyixem", + "link": "macros.html#fls_jgitbqmyixem", + "number": "20.4.2:4" }, { - "checksum": "5ac0651992734c49cdd19081a8d8fe0dc4836c3ac46e8efe50194b2f7e10729f", - "id": "fls_jhivcca0xcqj", - "link": "entities-and-resolution.html#fls_jhivcca0xcqj", - "number": "14.9.4.1:7" + "checksum": "5c84ed6a518944136878abb4c88f566d332adfa28adc88939272040b8b6376d9", + "id": "fls_ihcwl6taptas", + "link": "macros.html#fls_ihcwl6taptas", + "number": "20.4.2:5" }, { - "checksum": "07a8a2eb540fa895244a934632b5bf01b28d65714143604e47094193df81fafb", - "id": "fls_tfsgutcpube2", - "link": "entities-and-resolution.html#fls_tfsgutcpube2", - "number": "14.9.4.1:8" - } - ], - "title": "Simple Path Resolution" - }, - { - "id": "fls_o9u2h5m17kpz", - "informational": false, - "link": "entities-and-resolution.html#path-expression-resolution", - "number": "14.9.4.2", - "paragraphs": [ - { - "checksum": "89af848f7138b85e851a6fce2956808b14f21021ce84337948db5477059f0233", - "id": "fls_akjlqm3a2lb1", - "link": "entities-and-resolution.html#fls_akjlqm3a2lb1", - "number": "14.9.4.2:1" + "checksum": "ccd3909779e2ae52e078ac9ec0171a31569a1a5f27a764f1116aa514d4ab1e76", + "id": "fls_g3dtpw4rtgdr", + "link": "macros.html#fls_g3dtpw4rtgdr", + "number": "20.4.2:6" }, { - "checksum": "36e698199bc9da0c6a50931da2b24831e8a0c0750e8ce52aeac1278944de27a5", - "id": "fls_xyzdajtf4u2t", - "link": "entities-and-resolution.html#fls_xyzdajtf4u2t", - "number": "14.9.4.2:2" + "checksum": "cfa58e13c7d71f34271957f476e066a784af6a8ed0072bb215252e5c076b03c3", + "id": "fls_pvp6dxykuv66", + "link": "macros.html#fls_pvp6dxykuv66", + "number": "20.4.2:7" }, { - "checksum": "947403e0d43bb6cfb3077d98850a6352967094b8f5cb8b7f5c87720092f9c7ce", - "id": "fls_d45vu3iazi3", - "link": "entities-and-resolution.html#fls_d45vu3iazi3", - "number": "14.9.4.2:3" + "checksum": "c46a8e07cda751526560f7fc97ae3c1b78414ebc17c1ce3cd210620619ebbf25", + "id": "fls_bd673n5awwbz", + "link": "macros.html#fls_bd673n5awwbz", + "number": "20.4.2:8" }, { - "checksum": "d10ea4290290cedc3b93488c82b41a90c8b0f81d39b7086609ec5f0800847605", - "id": "fls_9pjhok9rctty", - "link": "entities-and-resolution.html#fls_9pjhok9rctty", - "number": "14.9.4.2:4" + "checksum": "89ea10945ee36c0a8d05b165f2dab20d4778edf60bc0f8b3696a46d9199743b8", + "id": "fls_zbtwrtcy7pzf", + "link": "macros.html#fls_zbtwrtcy7pzf", + "number": "20.4.2:9" }, { - "checksum": "b61a1df0de63c17349e99968166779c0689667f68065641b1e2150f748f65308", - "id": "fls_2wbpr9lvz5yq", - "link": "entities-and-resolution.html#fls_2wbpr9lvz5yq", - "number": "14.9.4.2:5" + "checksum": "928fa72878c7bba4b1bce0f51d05314cb2480974eb15a3baa42edf4802016c4b", + "id": "fls_eacyb6jap9ru", + "link": "macros.html#fls_eacyb6jap9ru", + "number": "20.4.2:10" }, { - "checksum": "aab266bf1130186940d08a2295560fbf7ce71f175fd96c37a30e54fc2c971f1e", - "id": "fls_u5nyv6ii1g2l", - "link": "entities-and-resolution.html#fls_u5nyv6ii1g2l", - "number": "14.9.4.2:6" + "checksum": "923b9e5ad7c41a86ba16ba5fc020547055484598d353ee7d4600ac28f7464960", + "id": "fls_y4podc7ee8lf", + "link": "macros.html#fls_y4podc7ee8lf", + "number": "20.4.2:11" }, { - "checksum": "f48db4f42fe8c1ac1316b5e2c8512944ed025def1e5c0e37911a3eca62f9d2d0", - "id": "fls_bscg48os5otx", - "link": "entities-and-resolution.html#fls_bscg48os5otx", - "number": "14.9.4.2:7" + "checksum": "b20e0ffba294f223744cb483c484961c3e57930764952f5beab1e3448b7f8cde", + "id": "fls_wbys0m4a1omg", + "link": "macros.html#fls_wbys0m4a1omg", + "number": "20.4.2:12" }, { - "checksum": "82ccbb720ea64a252b72a8aec77019bb5127c99487c3f35d9e6dadc715a2490d", - "id": "fls_qbmp0blpoxx9", - "link": "entities-and-resolution.html#fls_qbmp0blpoxx9", - "number": "14.9.4.2:8" + "checksum": "ffbe98be6393f55c5b73a7849f41caadf322b1ac4e018fa42c59e7305db89820", + "id": "fls_g445ovedgo4q", + "link": "macros.html#fls_g445ovedgo4q", + "number": "20.4.2:13" }, { - "checksum": "bbeb20abca91e618378dccd524e3d3a000f6f9e7f3141dc46ca1370b9efb0aeb", - "id": "fls_ydni5laqv6gp", - "link": "entities-and-resolution.html#fls_ydni5laqv6gp", - "number": "14.9.4.2:9" + "checksum": "4f0f8d199b56a8e97493384b9448af0cf2c03e0ee0924725a9973869747db80f", + "id": "fls_ctzthi6keit2", + "link": "macros.html#fls_ctzthi6keit2", + "number": "20.4.2:14" }, { - "checksum": "447a699862b4725e7b22ef7e24b1ab0783fec4db871aa6703ba6a7001eab753e", - "id": "fls_lsxbl6ep3150", - "link": "entities-and-resolution.html#fls_lsxbl6ep3150", - "number": "14.9.4.2:10" + "checksum": "924ac328fa7b1c896d1efd435201562db8738164a53715c1956c4c2da5a4012c", + "id": "fls_9n46ugmcqmix", + "link": "macros.html#fls_9n46ugmcqmix", + "number": "20.4.2:15" }, { - "checksum": "82acf15a92dae67f822e95bc38c6423ae258c77c41fe7312c0a6d9fbb981207e", - "id": "fls_x1n7w8w6lwm", - "link": "entities-and-resolution.html#fls_x1n7w8w6lwm", - "number": "14.9.4.2:11" + "checksum": "7c1fe9eccd483e7a420e8b7c1d1c1164588a91adea0ff813953d9cf07171ea38", + "id": "fls_JinrPA0pMZCr", + "link": "macros.html#fls_JinrPA0pMZCr", + "number": "20.4.2:16" }, { - "checksum": "15004350a8a539c4ce31613578db742e17945883ac27c5d3bf184e757faed1c6", - "id": "fls_v1h4frnbqruu", - "link": "entities-and-resolution.html#fls_v1h4frnbqruu", - "number": "14.9.4.2:12" + "checksum": "7b97106af69b3585b467ae757b209987fcfa719d9d0056adfca9fbc914a2d73b", + "id": "fls_95rn4cvgznmd", + "link": "macros.html#fls_95rn4cvgznmd", + "number": "20.4.2:17" }, { - "checksum": "9ac0b1cdc63a7e767baf563d906d9f2d9c9f560ba05a020309fdea70f031bf47", - "id": "fls_utfpnwlo0v99", - "link": "entities-and-resolution.html#fls_utfpnwlo0v99", - "number": "14.9.4.2:13" + "checksum": "a990d3628235419098482e04aad2d699f6205b88761be1c1cea45b6dad882cd1", + "id": "fls_yg4c9x7049y4", + "link": "macros.html#fls_yg4c9x7049y4", + "number": "20.4.2:18" }, { - "checksum": "39b97104cbe16fdf9cdc1acf3f079ade2fd32c3ac33e7858ae07e79a0bbc7907", - "id": "fls_1p8ocf1w5bp4", - "link": "entities-and-resolution.html#fls_1p8ocf1w5bp4", - "number": "14.9.4.2:14" - }, + "checksum": "30c1a84b2185098caed8510493324650b62ebcbc98651ffdef52e7656b859109", + "id": "fls_o9rwz9z0a2h4", + "link": "macros.html#fls_o9rwz9z0a2h4", + "number": "20.4.2:19" + } + ], + "title": "Macro Transcription" + }, + { + "id": "fls_xlfo7di0gsqz", + "informational": false, + "link": "macros.html#hygiene", + "number": "20.5", + "paragraphs": [ { - "checksum": "57570b48e6c6c789ee1bcbc956a0f24f79597a3ef49766fcff1d1600dad9b834", - "id": "fls_qb5yo7j5gnvf", - "link": "entities-and-resolution.html#fls_qb5yo7j5gnvf", - "number": "14.9.4.2:15" + "checksum": "dd8bbb6eef9dde4a235a404d36082ffd706c870db1421a4681408cf45d6a6bcc", + "id": "fls_7ezc7ncs678f", + "link": "macros.html#fls_7ezc7ncs678f", + "number": "20.5:1" }, { - "checksum": "f5d76df40ae4cadd8e2d9663fbe3c5b810519b7c72d0c10a68e00aa5f027118b", - "id": "fls_o1g0forw6xw", - "link": "entities-and-resolution.html#fls_o1g0forw6xw", - "number": "14.9.4.2:16" + "checksum": "950f5e9f05dfb90a7771b7b56a2c154f09546fdb6ee7b4947d3598357e680977", + "id": "fls_3axjf28xb1nt", + "link": "macros.html#fls_3axjf28xb1nt", + "number": "20.5:2" }, { - "checksum": "ce270746d595abe4f9d7b06360f034018301bd6709bc75fe5c1fde0bc1629b78", - "id": "fls_bcqe13q696zg", - "link": "entities-and-resolution.html#fls_bcqe13q696zg", - "number": "14.9.4.2:17" + "checksum": "341839374d80c8018873df9d4ddc4bc416d5bc2dac1e9b09bc7a56b94374252a", + "id": "fls_dz2mvodl818d", + "link": "macros.html#fls_dz2mvodl818d", + "number": "20.5:3" }, { - "checksum": "91a7d385f03fde69b74c5358096b89c7db65599637389124b36104c6f45d2b53", - "id": "fls_3sceutaqpqha", - "link": "entities-and-resolution.html#fls_3sceutaqpqha", - "number": "14.9.4.2:18" + "checksum": "e4746196f1fb7989f818766502b19a237f5b3740241baf2dfd8cb4211c05f424", + "id": "fls_puqhytfzfsg6", + "link": "macros.html#fls_puqhytfzfsg6", + "number": "20.5:4" }, { - "checksum": "6737364b72e672ce8f63321ee00914ec76f3c688ef520853fa9e96db1550f2e5", - "id": "fls_6q9cwqlvxmd1", - "link": "entities-and-resolution.html#fls_6q9cwqlvxmd1", - "number": "14.9.4.2:19" + "checksum": "93e969fde4270a39d3cee8c4a0b19185228c33e8b24df9d0ef270765e9824d81", + "id": "fls_uyvnq88y9gk3", + "link": "macros.html#fls_uyvnq88y9gk3", + "number": "20.5:5" }, { - "checksum": "a6ecb99abfccb4f9199fc7b11b6264c36f5e5074d9a587814517d58bb28b0397", - "id": "fls_qeym3vbi36iv", - "link": "entities-and-resolution.html#fls_qeym3vbi36iv", - "number": "14.9.4.2:20" + "checksum": "20c309825d7777388bd6af5804c0c7762b4fe76a90f7d2eb118d92ba8f547c1c", + "id": "fls_yxqcr19dig18", + "link": "macros.html#fls_yxqcr19dig18", + "number": "20.5:6" }, { - "checksum": "b5e402b7df0a8601f86f4a9fe385165d3998a9a9d0eda91d847641c00cac3469", - "id": "fls_8x0pqwpm80sj", - "link": "entities-and-resolution.html#fls_8x0pqwpm80sj", - "number": "14.9.4.2:21" + "checksum": "64745f1bbadaf610e425de55287699402147860a4554484ba0071db41bc7ed34", + "id": "fls_kx25olky1jov", + "link": "macros.html#fls_kx25olky1jov", + "number": "20.5:7" }, { - "checksum": "e6032bec4cb34591f1ba4d23d2ce789f2b98a66582dd5c3aa70faf8101bd90f7", - "id": "fls_pp09gmrnasjp", - "link": "entities-and-resolution.html#fls_pp09gmrnasjp", - "number": "14.9.4.2:22" + "checksum": "143bbe1668afc34dcafc68eddd7634b71e3847e8f9416dc8dedcee0d2d5198f5", + "id": "fls_v46v0t2vh6x4", + "link": "macros.html#fls_v46v0t2vh6x4", + "number": "20.5:8" }, { - "checksum": "b1754db3d3ab0eb3fe598859fff3a8179aa499eb840ad97eba99417063259cb9", - "id": "fls_q0jt6n2j1hsx", - "link": "entities-and-resolution.html#fls_q0jt6n2j1hsx", - "number": "14.9.4.2:23" + "checksum": "82fe4be1744d0478a0bcbed88a26bf676ea9c239bd2e996210ce59ad724a104b", + "id": "fls_7eqqk2cj0clr", + "link": "macros.html#fls_7eqqk2cj0clr", + "number": "20.5:9" } ], - "title": "Path Expression Resolution" - }, + "title": "Hygiene" + } + ], + "title": "Macros" + }, + { + "informational": false, + "link": "values.html", + "sections": [ { - "id": "fls_1h0olpc7vbui", + "id": "fls_94a8v54bufn8", "informational": false, - "link": "entities-and-resolution.html#type-path-resolution", - "number": "14.9.4.3", + "link": "values.html", + "number": "7", "paragraphs": [ { - "checksum": "148fc3beb5377b51b5edacfbb4ace5e23c7411944c711b49cd4964375b6fc241", - "id": "fls_2zuncql8ir5k", - "link": "entities-and-resolution.html#fls_2zuncql8ir5k", - "number": "14.9.4.3:1" + "checksum": "1a30c4fd875bd4b58309644a55091eee8d947eb825edf66ca6ed1b979a89b878", + "id": "fls_buyaqara7am4", + "link": "values.html#fls_buyaqara7am4", + "number": "7:1" }, { - "checksum": "fbaeca87efc96f46effc5215cbf8bc344900a3226501a59b1a547bb863112ec9", - "id": "fls_bv5cj918dqqe", - "link": "entities-and-resolution.html#fls_bv5cj918dqqe", - "number": "14.9.4.3:2" + "checksum": "0902cf1dae20d01533ef8ea3dade455b4f9f42cb77e7dde502c69b6ac4643be0", + "id": "fls_CUJyMj0Sj8NS", + "link": "values.html#fls_CUJyMj0Sj8NS", + "number": "7:2" }, { - "checksum": "752f0ff05ebaea022db0ecba55adfcb69b6f6c23c86019b367b3543af220a26c", - "id": "fls_bsakzuteuh5s", - "link": "entities-and-resolution.html#fls_bsakzuteuh5s", - "number": "14.9.4.3:3" + "checksum": "0c88ea1a19e3e040264b950c22acf3f967df13518dee02b93354389d91fc6a8a", + "id": "fls_kaomYy0Ml4Nh", + "link": "values.html#fls_kaomYy0Ml4Nh", + "number": "7:3" }, { - "checksum": "a2d543d3938e4eaf6c88335ab06f4b99d478110d46ea60d86ee04356b323bcd9", - "id": "fls_j1ewjisx0mc2", - "link": "entities-and-resolution.html#fls_j1ewjisx0mc2", - "number": "14.9.4.3:4" + "checksum": "a6432f34a97d6a2e9a35a5c164ee5140344db36cb4b42ca5aef5fbd0a9f8c741", + "id": "fls_B5cmkWfD5GNt", + "link": "values.html#fls_B5cmkWfD5GNt", + "number": "7:4" }, { - "checksum": "12dd80327fd8a5c538a2841bd1c0e0122e4159042245c5ffce28e72f8be6691d", - "id": "fls_o4snu1him277", - "link": "entities-and-resolution.html#fls_o4snu1him277", - "number": "14.9.4.3:5" + "checksum": "73bb0dbe94c2f521e89fae4c897fc338813c9175ff4600fed13e41c0b9878e30", + "id": "fls_rixdyyc525xp", + "link": "values.html#fls_rixdyyc525xp", + "number": "7:5" }, { - "checksum": "6689718fd77318ab2f4a94867ff442dd6bfa733fdede8a658be7cdb6eabb0281", - "id": "fls_goe8q52toik2", - "link": "entities-and-resolution.html#fls_goe8q52toik2", - "number": "14.9.4.3:6" + "checksum": "501fd5dc12d09844a612f32f00e4d5a2dedfdf7a1659cf1e4b1e7aa5ab84d7bb", + "id": "fls_m6ctqq70vcxr", + "link": "values.html#fls_m6ctqq70vcxr", + "number": "7:6" }, { - "checksum": "38657ebf4baa69cb441b05dfa17a0c65b100642cb9c9c766425c0db3fd888577", - "id": "fls_4rs35f6ydckj", - "link": "entities-and-resolution.html#fls_4rs35f6ydckj", - "number": "14.9.4.3:7" + "checksum": "d4a5c1906b3bbe1c9d809c9d1156e75673a511153eedc4950f47fb135b6a59a8", + "id": "fls_s231d18x5eay", + "link": "values.html#fls_s231d18x5eay", + "number": "7:7" }, { - "checksum": "b41f690747dc7316182fec64bdba0c0beb06900a8bbaba23f018fcec1330a29d", - "id": "fls_jh4db1p7or0x", - "link": "entities-and-resolution.html#fls_jh4db1p7or0x", - "number": "14.9.4.3:8" + "checksum": "01045132eff6e0617d4cca46c1cc3f5d88d56776cf683971fc1530459b7f48fe", + "id": "fls_dfr4yqo93fsn", + "link": "values.html#fls_dfr4yqo93fsn", + "number": "7:8" + }, + { + "checksum": "e4226c8e3ff3c227a9d0de5f8e42ad468c5a93f6fb87c981f7f126d3d5399060", + "id": "fls_eoak5mdl6ma", + "link": "values.html#fls_eoak5mdl6ma", + "number": "7:9" + }, + { + "checksum": "2935b280567bdb6d4a9792e339cac22702fe95059923d9a738c60a7a8548dab5", + "id": "fls_6lg0oaaopc26", + "link": "values.html#fls_6lg0oaaopc26", + "number": "7:10" + }, + { + "checksum": "9fe88066ade0f4cbf2e51e42c37d28efcd55edf9ac6496bf9612d853c6d3c866", + "id": "fls_oqhQ62mDLckN", + "link": "values.html#fls_oqhQ62mDLckN", + "number": "7:11" + }, + { + "checksum": "fda28ff83dfbce718ca81156aaddbd799783c6778b78393fd26b1d31b8eff46f", + "id": "fls_uhwpuv6cx4ip", + "link": "values.html#fls_uhwpuv6cx4ip", + "number": "7:12" + }, + { + "checksum": "470b62e28b525feb1985f8a6eb97b0b0ad3158a5c22541e4e07c02e9b1352f1a", + "id": "fls_xuuFKmm181bs", + "link": "values.html#fls_xuuFKmm181bs", + "number": "7:13" } ], - "title": "Type Path Resolution" - } - ], - "title": "Entities and Resolution" - }, - { - "informational": false, - "link": "associated-items.html", - "sections": [ + "title": "Values" + }, { - "id": "fls_l21tjqjkkaa0", + "id": "fls_ixjc5jaamx84", "informational": false, - "link": "associated-items.html", - "number": "10", + "link": "values.html#constants", + "number": "7.1", "paragraphs": [ { - "checksum": "c48bd96544b685d7b7a7e8a2e676eb1c8ed2066a485c20a48faa1c660ef8bdb7", - "id": "fls_ckzd25qd213t", - "link": "associated-items.html#fls_ckzd25qd213t", - "number": "10:1" + "checksum": "459c0a6560892364338ac1907aa3d368c9f21cf8310c9cc782a84f08d45f1ad4", + "id": "fls_5o5iu4j8in4l", + "link": "values.html#fls_5o5iu4j8in4l", + "number": "7.1:1" }, { - "checksum": "fd7bf7d0a40ad75d0809a98d1bd273b94708f4a26b1a3be16f7e8dbffbe638ec", - "id": "fls_5y6ae0xqux57", - "link": "associated-items.html#fls_5y6ae0xqux57", - "number": "10:2" + "checksum": "7c5a50f4fdf5e813f75ac692c03fc8f4b3ef0e7f8d0ee6e1de095293de8bfffb", + "id": "fls_3mhj0kkupwuz", + "link": "values.html#fls_3mhj0kkupwuz", + "number": "7.1:2" }, { - "checksum": "fda609cdbb71ae52c820019783b3b6858d633fdd6de601cfae585217aeb034aa", - "id": "fls_lj7492aq7fzo", - "link": "associated-items.html#fls_lj7492aq7fzo", - "number": "10:3" + "checksum": "4716f007f8ab2d62c80effe03cf31b1970b0da6143b16e60aed00dceda659994", + "id": "fls_ka4y2yd100dx", + "link": "values.html#fls_ka4y2yd100dx", + "number": "7.1:3" }, { - "checksum": "d0a97d55dc69fb14de940c4597c8ec6cc4d1bdea2efef1f262ca235d4380a078", - "id": "fls_8cz4rdrklaj4", - "link": "associated-items.html#fls_8cz4rdrklaj4", - "number": "10:4" + "checksum": "ff5ba133064323870b5d011bd88ce9eeacc9f115fdaa6244b44b18ee640a1431", + "id": "fls_vt9tlkd676ql", + "link": "values.html#fls_vt9tlkd676ql", + "number": "7.1:4" }, { - "checksum": "a21d4472dc9edff52ba6084426735486e96547a9916ebbf6041810119a4ca7aa", - "id": "fls_w8nu8suy7t5", - "link": "associated-items.html#fls_w8nu8suy7t5", - "number": "10:5" + "checksum": "ca441fbadf454d310fef0a445ecd27e40458052ffd2566f775fd1f5d49ab6961", + "id": "fls_ndmfqxjpvsqy", + "link": "values.html#fls_ndmfqxjpvsqy", + "number": "7.1:5" }, { - "checksum": "a5074f92b70f64ed6d002c08f65bfa91a814cf5b80b19137743ca62a12bda2b6", - "id": "fls_wasocqdnuzd1", - "link": "associated-items.html#fls_wasocqdnuzd1", - "number": "10:6" + "checksum": "195d2e43a2cb6b0474fb0ebaa8190f17c133efecdb317ae5ad83e396aaf05e0b", + "id": "fls_6rxwbbhf5tc5", + "link": "values.html#fls_6rxwbbhf5tc5", + "number": "7.1:6" }, { - "checksum": "61134011658677b475c3f8e65ac36ab5b479e0746afd064ac6d0084c0441fbb6", - "id": "fls_PeD0DzjK57be", - "link": "associated-items.html#fls_PeD0DzjK57be", - "number": "10:7" + "checksum": "ea8a4c823672702b1fff3e67be0d3834bfde988e46a4ab82202acfcc2d7b9898", + "id": "fls_vnc3ttnid1qr", + "link": "values.html#fls_vnc3ttnid1qr", + "number": "7.1:7" }, { - "checksum": "0dd68e90f7962b7f3ab0d020e22ee6db5fde4efa6ae628af968af3fe3d76baa8", - "id": "fls_3foYUch29ZtF", - "link": "associated-items.html#fls_3foYUch29ZtF", - "number": "10:8" + "checksum": "9590b37a5fb341bc26220cabf636eb0b9430c183eefc8266362a284afabdcf54", + "id": "fls_deuo1pn8cjd6", + "link": "values.html#fls_deuo1pn8cjd6", + "number": "7.1:8" }, { - "checksum": "6f1791ecd8b47dfbf50561704cc838d8b0e544cf7c69b576dd48120715a47166", - "id": "fls_SnQc0zZS57Cz", - "link": "associated-items.html#fls_SnQc0zZS57Cz", - "number": "10:9" + "checksum": "916f9839884f7b71825ecd5be8ae92f6b7ab789a916c47ba1a5f366bd8ac4382", + "id": "fls_xezt9hl069h4", + "link": "values.html#fls_xezt9hl069h4", + "number": "7.1:9" }, { - "checksum": "85d03d9e5ceb6390c1024e3f0079563e480155c17b687f8118d280be74b27d92", - "id": "fls_6Z05BK2JSzpP", - "link": "associated-items.html#fls_6Z05BK2JSzpP", - "number": "10:10" + "checksum": "18ea98ed7ee862889d2d483387c3b1e28a42e39e9d60b7bbc3e65d9e4deff0a5", + "id": "fls_ndobth7s92if", + "link": "values.html#fls_ndobth7s92if", + "number": "7.1:10" + } + ], + "title": "Constants" + }, + { + "id": "fls_xdvdl2ssnhlo", + "informational": false, + "link": "values.html#statics", + "number": "7.2", + "paragraphs": [ + { + "checksum": "1fc950d02b5b3c40129159c65daf7d001e7f01521a71c3d8b8a2058dbe50c167", + "id": "fls_ibrmiwfypldh", + "link": "values.html#fls_ibrmiwfypldh", + "number": "7.2:1" }, { - "checksum": "1a672e4e07310314b3ed72804595330ecf46d9177808d67beee1497db38edc76", - "id": "fls_AtItgS1UvwiX", - "link": "associated-items.html#fls_AtItgS1UvwiX", - "number": "10:11" + "checksum": "8c4334c679018baf9392151eb71d12964b85d09d0477dbea3910ea98adb3612f", + "id": "fls_mt94jvoot9dx", + "link": "values.html#fls_mt94jvoot9dx", + "number": "7.2:2" }, { - "checksum": "f83c7a08c44f0dc091a19c1014f202ab5538474a3d818f1583bd138daef33ecf", - "id": "fls_l3iwn56n1uz8", - "link": "associated-items.html#fls_l3iwn56n1uz8", - "number": "10:12" + "checksum": "5c1b55de587710b67a5131939b84300663f0c0699041210a7ce0779e6c1a0f65", + "id": "fls_k0r2c6uq29tu", + "link": "values.html#fls_k0r2c6uq29tu", + "number": "7.2:3" }, { - "checksum": "41db8d1e89a48299f2506f2fd5b398d24cc65ebba4aced317857a727822d1c4e", - "id": "fls_4ftfefcotb4g", - "link": "associated-items.html#fls_4ftfefcotb4g", - "number": "10:13" + "checksum": "db2fb6ad99eb58cdd6f4bdb9e2edeff34b4c9b422e25781877ba2d6267194842", + "id": "fls_b6ods85htuyn", + "link": "values.html#fls_b6ods85htuyn", + "number": "7.2:4" }, { - "checksum": "66b5d7677a5542109bcd8f36408c3ed7ec1aca313c4c747fc28557fb2dd3c6c0", - "id": "fls_qb5qpfe0uwk", - "link": "associated-items.html#fls_qb5qpfe0uwk", - "number": "10:14" + "checksum": "1fc020e39e08ce4f2038eaa3162a6a3594444e03098c3fe4d3adc9059d7ac4e4", + "id": "fls_WRpcVF1fLEpr", + "link": "values.html#fls_WRpcVF1fLEpr", + "number": "7.2:5" }, { - "checksum": "96a142348655f7c0d311daed418fc24259286e2108efc574894af02cf2471c36", - "id": "fls_1zlkeb6fz10j", - "link": "associated-items.html#fls_1zlkeb6fz10j", - "number": "10:15" + "checksum": "61e4575688b23d6d7db99392e1b544003c08b7a838ac8fc1fdad3b1e44ad8ce6", + "id": "fls_doi4z6u55bi7", + "link": "values.html#fls_doi4z6u55bi7", + "number": "7.2:6" }, { - "checksum": "b5024c3cf8ba9c9ff0e7a6bd05ae96c8f7c64599e2eaec349fae2ca6767f4bab", - "id": "fls_tw8u0cc5867l", - "link": "associated-items.html#fls_tw8u0cc5867l", - "number": "10:16" + "checksum": "436a9f15d2686b36fb1f179b13f38d0cce872399fc6c8d997896182219ca9b12", + "id": "fls_74hp208pto22", + "link": "values.html#fls_74hp208pto22", + "number": "7.2:7" }, { - "checksum": "a13634951f7946c3028793e284d6d7899f718840e70e5b30992ad30aa0b28a82", - "id": "fls_bx7931x4155h", - "link": "associated-items.html#fls_bx7931x4155h", - "number": "10:17" + "checksum": "7594d6fedfa7f00a180909cf5235b4243c8be61026a444da258f79f104bdd93f", + "id": "fls_jfde2vg6mtww", + "link": "values.html#fls_jfde2vg6mtww", + "number": "7.2:8" }, { - "checksum": "8fb89c0b3f9ad0065b86652933686cc127a11676e7b59759588bf35bbe56a204", - "id": "fls_bnTcCbDvdp94", - "link": "associated-items.html#fls_bnTcCbDvdp94", - "number": "10:18" + "checksum": "ac1f8731f3004fb8442f085915588c449be8d285a8cae244ac37519247a147c9", + "id": "fls_k4tyqb1j6zjo", + "link": "values.html#fls_k4tyqb1j6zjo", + "number": "7.2:9" }, { - "checksum": "22be2f18e00bb9e898293564455031809a4ffbdba44f030bff8af6c23ada5048", - "id": "fls_N3cdn4lCZ2Bf", - "link": "associated-items.html#fls_N3cdn4lCZ2Bf", - "number": "10:19" + "checksum": "ae7960becaff9d4e85d3aa35ebbfdf4c482ac55566c5938d255ec72bb2cfe41e", + "id": "fls_t17h5h6a6v4c", + "link": "values.html#fls_t17h5h6a6v4c", + "number": "7.2:10" }, { - "checksum": "9647e6f192f7a123a465887f651e66f32b17c903edd98aa87c48510b16acd40f", - "id": "fls_x564isbhobym", - "link": "associated-items.html#fls_x564isbhobym", - "number": "10:20" + "checksum": "74a8e2029bbf9068f3842d85c2c37aa40cf499e68bbff4aaad87bbcca3cddcff", + "id": "fls_yq0hpy4jx2qb", + "link": "values.html#fls_yq0hpy4jx2qb", + "number": "7.2:11" }, { - "checksum": "d7e3897e8d4d866fe24ed708a542d480dd49b7432f4e1ab2b6e9f83e66beb79c", - "id": "fls_b6nns7oqvdpm", - "link": "associated-items.html#fls_b6nns7oqvdpm", - "number": "10:21" + "checksum": "d07bf654cb1e5a9429203f2ebf243ae97b0345768f8f570784f2e53ba60d1a81", + "id": "fls_vgidvfwzm4ks", + "link": "values.html#fls_vgidvfwzm4ks", + "number": "7.2:12" }, { - "checksum": "f6efbcd47f77fb69c490956047b8fa92ddd13a1a56c2c82483cd10efd9f4abc8", - "id": "fls_2TRwCz38kuRz", - "link": "associated-items.html#fls_2TRwCz38kuRz", - "number": "10:22" + "checksum": "14ed04b36fe95b827d15d5985c20be8554b5d34cabcbdcff30e24ab1af96c712", + "id": "fls_8dcldbvu7lav", + "link": "values.html#fls_8dcldbvu7lav", + "number": "7.2:13" }, { - "checksum": "ef01e780fac2b6784fa9e17636be1c17d599f8831949c44ad35251b17052c1a3", - "id": "fls_WnsVATJvUdza", - "link": "associated-items.html#fls_WnsVATJvUdza", - "number": "10:23" + "checksum": "0bef3205d769452568c2d9b7420b359b46261b2fd90918adcdf762a9b9d97651", + "id": "fls_w0nb0mphho7b", + "link": "values.html#fls_w0nb0mphho7b", + "number": "7.2:14" }, { - "checksum": "3b7142bcde18f322ffa41b2c57c18446ab40aa8a6612d601b48ec17273ad3d94", - "id": "fls_yyhebj4qyk34", - "link": "associated-items.html#fls_yyhebj4qyk34", - "number": "10:24" + "checksum": "c16b08cc3a5ca2e215cad78f4c6f8d9d9c03d11b6737b12ca2cf558d58a77b3e", + "id": "fls_eeocxst9vafn", + "link": "values.html#fls_eeocxst9vafn", + "number": "7.2:15" }, { - "checksum": "53bc991f032b9bfaab3cb72189a8ae6d8772250cc7fbaefa3b80bbd25679141f", - "id": "fls_kl9p3ycl5mzf", - "link": "associated-items.html#fls_kl9p3ycl5mzf", - "number": "10:25" + "checksum": "8aa9153a03b15bd583210b61b2721907a729d0f7febdc4e7a31ff91f096d0e47", + "id": "fls_47khd5ljsxeq", + "link": "values.html#fls_47khd5ljsxeq", + "number": "7.2:16" }, { - "checksum": "6e869fbccfae9516ee3421a1571bd880ea5ec5dc5aedb65e32f5a6d2977b7955", - "id": "fls_a5prbmuruma4", - "link": "associated-items.html#fls_a5prbmuruma4", - "number": "10:26" + "checksum": "5a0bbb9142d384d7c9551f4e242a13e3d9a43d1496d68ca6b3f409ad6d7e8e7c", + "id": "fls_dowxbphqvk3n", + "link": "values.html#fls_dowxbphqvk3n", + "number": "7.2:17" }, { - "checksum": "915d230d994ce8e745b1083bb75cb58b37ac8537af7fd5486a86d1314cb353d0", - "id": "fls_vp2ov6ykueue", - "link": "associated-items.html#fls_vp2ov6ykueue", - "number": "10:27" + "checksum": "ae3be52255b3602f2d17694a7c90103ed859c959d8b5e8b02d731dd9ee8ac70f", + "id": "fls_b5wsmii7vz3v", + "link": "values.html#fls_b5wsmii7vz3v", + "number": "7.2:18" + } + ], + "title": "Statics" + }, + { + "id": "fls_cleoffpn5ew6", + "informational": false, + "link": "values.html#temporaries", + "number": "7.3", + "paragraphs": [ + { + "checksum": "c643b7ee2f09d4d661349de57526a0d8f6ba3fa1f1d3cf25ce2abb1ca7ad5487", + "id": "fls_awpw61yofckz", + "link": "values.html#fls_awpw61yofckz", + "number": "7.3:1" + } + ], + "title": "Temporaries" + }, + { + "id": "fls_gho955gmob73", + "informational": false, + "link": "values.html#variables", + "number": "7.4", + "paragraphs": [ + { + "checksum": "e459e293d2e06ba989b65a863e33bf3d1559d14ba80224385e6471d4187db194", + "id": "fls_hl5tnd9yy252", + "link": "values.html#fls_hl5tnd9yy252", + "number": "7.4:1" }, { - "checksum": "338988e157f9ff9c04c5a8163cf72e90c0400a0e6ccb6b45e6f105a1b75ac855", - "id": "fls_5uf74nvdm64o", - "link": "associated-items.html#fls_5uf74nvdm64o", - "number": "10:28" + "checksum": "a56061a790ee2cc114f7f43c891827153cacfdd9d5bbdee36e4db521b6d5fc2f", + "id": "fls_vgi0gh5zmoiu", + "link": "values.html#fls_vgi0gh5zmoiu", + "number": "7.4:2" }, { - "checksum": "4e9d22ccfb80acfdb452f21eaa41d99e8b68d03dbf8ffcaca69016ad6b224e0c", - "id": "fls_amWtS80fPtza", - "link": "associated-items.html#fls_amWtS80fPtza", - "number": "10:29" + "checksum": "317f39071a5788d82b4865c063b86490996160f886858c0f19fdba967e4204e8", + "id": "fls_81dlbula47nu", + "link": "values.html#fls_81dlbula47nu", + "number": "7.4:3" }, { - "checksum": "6b3108c40f22ac87f1e375b4907dc7e2565fc0fdb570d5e8e87d35f36da764fb", - "id": "fls_Cu8FWrisrqz1", - "link": "associated-items.html#fls_Cu8FWrisrqz1", - "number": "10:30" + "checksum": "08b9626baa3159ce1dfdeca36fdc64f24594ba26b6b57c9810b67ee7c84c26cb", + "id": "fls_3p0sb9ppmg3w", + "link": "values.html#fls_3p0sb9ppmg3w", + "number": "7.4:4" }, { - "checksum": "c67bd6dc496b87f14e94eac06fb1dd53a785b33748660d90a3455b63b8f15dea", - "id": "fls_oy92gzxgc273", - "link": "associated-items.html#fls_oy92gzxgc273", - "number": "10:31" + "checksum": "13806969584a605aded5db6ac700acea4334fa75cce1409689f2cdc10b30c2c8", + "id": "fls_r9km9f969bu8", + "link": "values.html#fls_r9km9f969bu8", + "number": "7.4:5" }, { - "checksum": "1d3e0acf8e8b25427b6a4179ef6f36b9c9be2060806f8a4532c85d38970a854f", - "id": "fls_WXnCWfJGoQx3", - "link": "associated-items.html#fls_WXnCWfJGoQx3", - "number": "10:32" - }, + "checksum": "4de69432bc744f7df3566698f62d36feec0eb8432fae9503c73491871c1858d3", + "id": "fls_g8etd5lsgn9j", + "link": "values.html#fls_g8etd5lsgn9j", + "number": "7.4:6" + } + ], + "title": "Variables" + }, + { + "id": "fls_wttihxen35as", + "informational": false, + "link": "values.html#constant-promotion", + "number": "7.4.1", + "paragraphs": [ { - "checksum": "f1864701261d89610bc085bf20379aabb9a7f867190c51deee3b5ab4e0731ab4", - "id": "fls_OaszUw4IFobz", - "link": "associated-items.html#fls_OaszUw4IFobz", - "number": "10:33" + "checksum": "2ab496292105588acffc417faa7257c109d7d2599887fea194b6276c78d7d9f9", + "id": "fls_udn9lyf3m0z6", + "link": "values.html#fls_udn9lyf3m0z6", + "number": "7.4.1:1" }, { - "checksum": "a56737ff1c2a54b21dd0325776e037cb33b634928cad055fc83c168e8513e490", - "id": "fls_Wd2FZRomB5yn", - "link": "associated-items.html#fls_Wd2FZRomB5yn", - "number": "10:34" + "checksum": "0802dcedbdb411093a11433793ec2d81046430a75caa08c88a0652e21e3f2b48", + "id": "fls_yvkdcs4pmxjf", + "link": "values.html#fls_yvkdcs4pmxjf", + "number": "7.4.1:2" }, { - "checksum": "8c0ecdddfa88a992b09009b5167f4dc68b5f53f241a872c2a1e85f8eb610f1e5", - "id": "fls_lcEyToYIlcmf", - "link": "associated-items.html#fls_lcEyToYIlcmf", - "number": "10:35" + "checksum": "7495b43c987ba177e81ed96d19821da97b72ed887800433fa2c3b5d3899da923", + "id": "fls_n570za6a9nqd", + "link": "values.html#fls_n570za6a9nqd", + "number": "7.4.1:3" }, { - "checksum": "2313193d9ba1cea774bf3d4a520130dc60870cdf9f64464a5745c228f3b647fa", - "id": "fls_IKSPR7ZQMErU", - "link": "associated-items.html#fls_IKSPR7ZQMErU", - "number": "10:36" + "checksum": "c27b3a87d8e551f6925166f12702de6c3b84e10e0560e1124e14b34b75af3571", + "id": "fls_tms5r9f5ogcb", + "link": "values.html#fls_tms5r9f5ogcb", + "number": "7.4.1:4" }, { - "checksum": "a9ddbf6bc0c06113d12f75ca1fe4401527ac5ff2a0384f375f53b2b7eedc084d", - "id": "fls_oHxzyaiT7Qm6", - "link": "associated-items.html#fls_oHxzyaiT7Qm6", - "number": "10:37" + "checksum": "4b1f204406987d2425c7aa65db5e4321ca60c66f8abe6f59d4b9c954b56369fc", + "id": "fls_bysv5r7iuf5j", + "link": "values.html#fls_bysv5r7iuf5j", + "number": "7.4.1:5" }, { - "checksum": "17b593b8d0cb4dacc6eb9d143a7e49048a6708d07c811e2595e8cfbb89483827", - "id": "fls_znfADVeOvXHD", - "link": "associated-items.html#fls_znfADVeOvXHD", - "number": "10:38" - } - ], - "title": "Associated Items" - } - ], - "title": "Associated Items" - }, - { - "informational": true, - "link": "background.html", - "sections": [ - { - "id": "fls_mlo6b3ewf50j", - "informational": false, - "link": "background.html", - "number": "E", - "paragraphs": [ - { - "checksum": "badbea10d9e5d4d6735483d5537945203ca32a824fe13ae5b80a7355f54d8a1c", - "id": "fls_p7Jiyppmg0FU", - "link": "background.html#fls_p7Jiyppmg0FU", - "number": "E:1" + "checksum": "26b8f032dee42ea2be31ad066b7ea126c0a123b1038d4a62f29eb01d10f33637", + "id": "fls_3h5vr7xk2rrt", + "link": "values.html#fls_3h5vr7xk2rrt", + "number": "7.4.1:6" }, { - "checksum": "0a1f874197f6ef38b9e620caf7600f0f4706708cc4b997fc79096eab9e0dc405", - "id": "fls_Uvf5tHsKSV19", - "link": "background.html#fls_Uvf5tHsKSV19", - "number": "E:2" + "checksum": "75d27ef13729037fd83b4022475756231b8ae7d7944805b47de72178a03c8b1a", + "id": "fls_3BGncWvMumEt", + "link": "values.html#fls_3BGncWvMumEt", + "number": "7.4.1:7" }, { - "checksum": "c8c5400f5c46557eb1d3753398731ab78073710aa598c36e1ca00db60fe5d4e7", - "id": "fls_J7ZI7mBXffzZ", - "link": "background.html#fls_J7ZI7mBXffzZ", - "number": "E:3" + "checksum": "d4d542c7748a3c35fbf6025eac3cfaadc015af1b45fc042ee9cdfe0b8b09c0db", + "id": "fls_m690b8qg9d9r", + "link": "values.html#fls_m690b8qg9d9r", + "number": "7.4.1:8" }, { - "checksum": "bfe08f4cf50f227c85776d3d0c57a39f9e9157bd89f18d0a2d992fc499c52a77", - "id": "fls_ffv8XSbBOMkR", - "link": "background.html#fls_ffv8XSbBOMkR", - "number": "E:4" - } - ], - "title": "FLS Background" - }, - { - "id": "fls_umsvnulqzd99", - "informational": false, - "link": "background.html#acknowledging-ferrous-systems", - "number": "E.1", - "paragraphs": [ + "checksum": "8f845fa5e99e7c007d619bef33689d1b260b69d4631f9648c454ffebb40c4360", + "id": "fls_uf0sg25awre6", + "link": "values.html#fls_uf0sg25awre6", + "number": "7.4.1:9" + }, { - "checksum": "cdcc8b0a0f82ccc5266f4b961c6cda964f76470688e198bd6714fc14e1de17dc", - "id": "fls_lmlLUAdtfCo5", - "link": "background.html#fls_lmlLUAdtfCo5", - "number": "E.1:1" + "checksum": "b6c301614db85e9f9a3aa268b8e6ec7397cfd563b3d321bedae74884809dc6ab", + "id": "fls_o7cqfdnr253y", + "link": "values.html#fls_o7cqfdnr253y", + "number": "7.4.1:10" }, { - "checksum": "d38387d8b265d10e97153f7ab7bd680dbe5cbbb2af4db501ff7a00f57c4a27b7", - "id": "fls_FZRrMT5AYsAQ", - "link": "background.html#fls_FZRrMT5AYsAQ", - "number": "E.1:2" + "checksum": "3bb8d78cd408d9dd893e63b174a91171c065d65c7708b50a9d029c3bff7c32b1", + "id": "fls_ap85svxyuhvg", + "link": "values.html#fls_ap85svxyuhvg", + "number": "7.4.1:11" } ], - "title": "Acknowledging Ferrous Systems" + "title": "Constant Promotion" } ], - "title": "FLS Background" + "title": "Values" }, { "informational": false, - "link": "concurrency.html", + "link": "statements.html", "sections": [ { - "id": "fls_3v733mnewssy", - "informational": false, - "link": "concurrency.html", - "number": "17", - "paragraphs": [ - { - "checksum": "58e23287dc1c3ffbe6b2c4672501d222916a129910befc8fadc364de5171c351", - "id": "fls_opt7v0mopxc8", - "link": "concurrency.html#fls_opt7v0mopxc8", - "number": "17:1" - }, - { - "checksum": "60786d66db9926333e9768b0cc8aa490ff764f600fdbdb389912f9e85add0678", - "id": "fls_tx4b8r6i93n4", - "link": "concurrency.html#fls_tx4b8r6i93n4", - "number": "17:2" - }, - { - "checksum": "5a06090908c3a726a0df6203b3e9f7c460c35609c758b6db09cf2d5c5c753c8e", - "id": "fls_isypweqewe78", - "link": "concurrency.html#fls_isypweqewe78", - "number": "17:3" - } - ], - "title": "Concurrency" - }, - { - "id": "fls_eiw4by8z75di", + "id": "fls_wdicg3sqa98e", "informational": false, - "link": "concurrency.html#send-and-sync", - "number": "17.1", + "link": "statements.html", + "number": "8", "paragraphs": [ { - "checksum": "18cfbb89a43fe476f2c0e5f3299b552fe8abafac2b759f081654338c7597f688", - "id": "fls_n5l17mlglq11", - "link": "concurrency.html#fls_n5l17mlglq11", - "number": "17.1:1" - }, - { - "checksum": "2c6d410f6594c15bcbb289be0b5a44ae8b19c478583342e5405aef3abbdb8951", - "id": "fls_2jujsujpjp3w", - "link": "concurrency.html#fls_2jujsujpjp3w", - "number": "17.1:2" - }, - { - "checksum": "11baae79e2b2bec1ba1dccff05f08bedf2b603f0b2748f345dafa59243aab72f", - "id": "fls_cax6fe4em23k", - "link": "concurrency.html#fls_cax6fe4em23k", - "number": "17.1:3" + "checksum": "03f55779646bda5f8b813a0bda5a8000aaa7a3f7df2147c58d37a6a19d213825", + "id": "fls_7zh6ziglo5iy", + "link": "statements.html#fls_7zh6ziglo5iy", + "number": "8:1" }, { - "checksum": "14b867fe8baaef726910084c46d92291fffbfe3b462586c4a7ecd134f8152d96", - "id": "fls_4ypqdehn7b0v", - "link": "concurrency.html#fls_4ypqdehn7b0v", - "number": "17.1:4" + "checksum": "857eb6f6081f30de98150db89a25a25467c0e180b1c3eb19f2786475a5a6e424", + "id": "fls_kdxe1ukmgl1", + "link": "statements.html#fls_kdxe1ukmgl1", + "number": "8:2" }, { - "checksum": "3e1f608593a27c63b70db641f71e71a331b116739b77b1bc9ce207481f0dd8be", - "id": "fls_dekskhk4g895", - "link": "concurrency.html#fls_dekskhk4g895", - "number": "17.1:5" + "checksum": "1ea74f2f77df136dd43cf45552f12fb2f6c366bcf10505917c84e5c9f9fd5fd8", + "id": "fls_fftdnwe22xrb", + "link": "statements.html#fls_fftdnwe22xrb", + "number": "8:3" }, { - "checksum": "37c5ee82be489b2f1bd2eb9efdd02a9e9734ac2867c6bfc04f37f9877a9ab760", - "id": "fls_y0iqr5ibnbfe", - "link": "concurrency.html#fls_y0iqr5ibnbfe", - "number": "17.1:6" + "checksum": "36cd13cda4653f00db550d04d91838c3dc10bfcbbf15d67f8ba19c0d5ddc297b", + "id": "fls_or125cqtxg9j", + "link": "statements.html#fls_or125cqtxg9j", + "number": "8:4" }, { - "checksum": "a206190ea6bc9a6672886ed000599cd972f4abec22f754db4c4d9acbc6754a4c", - "id": "fls_zgemofbs5q2x", - "link": "concurrency.html#fls_zgemofbs5q2x", - "number": "17.1:7" + "checksum": "82751916e76e8a528dd974fcf8f394553a002d42480c43ef8ca7bd23dec0d51a", + "id": "fls_estqu395zxgk", + "link": "statements.html#fls_estqu395zxgk", + "number": "8:5" + }, + { + "checksum": "085c0b250339d5488037ef839a9f6dd3672026ab75232f7a43c422bca728726c", + "id": "fls_dl763ssb54q1", + "link": "statements.html#fls_dl763ssb54q1", + "number": "8:6" } ], - "title": "Send and Sync" + "title": "Statements" }, { - "id": "fls_vyc9vcuamlph", + "id": "fls_yivm43r5wnp1", "informational": false, - "link": "concurrency.html#atomics", - "number": "17.2", + "link": "statements.html#let-statements", + "number": "8.1", "paragraphs": [ { - "checksum": "aca31f1758bf227f88cdd68e64370874768f51dd10e9211ec344537b1d7ba6cd", - "id": "fls_3pjla9s93mhd", - "link": "concurrency.html#fls_3pjla9s93mhd", - "number": "17.2:1" + "checksum": "fa270e107510c6e1cbbd96841a51e304e617eb94f030aaa20c17913ff260d725", + "id": "fls_ct7pp7jnfr86", + "link": "statements.html#fls_ct7pp7jnfr86", + "number": "8.1:1" }, { - "checksum": "ce72ccfc2f3e59228fae42edd3521e8333bddb6951896be2347ec2387355262a", - "id": "fls_wn4ynaio8u47", - "link": "concurrency.html#fls_wn4ynaio8u47", - "number": "17.2:2" + "checksum": "d10976aa00e76b2bd9ae4e6118e2bb91d70d4093d2416f3c38179f5e2786f112", + "id": "fls_SR3dIgR5K0Kq", + "link": "statements.html#fls_SR3dIgR5K0Kq", + "number": "8.1:2" }, { - "checksum": "f04e33b2cedb200500a0733c23e46638bb219c9422ea4796905e57f57c0bc457", - "id": "fls_q7mn6pdd8bix", - "link": "concurrency.html#fls_q7mn6pdd8bix", - "number": "17.2:3" + "checksum": "be1f15f55c52d38164795fc8324e4f7a5b3ca997e5de0de3c0c4ebe283e2d68e", + "id": "fls_iqar7vvtw22c", + "link": "statements.html#fls_iqar7vvtw22c", + "number": "8.1:3" }, { - "checksum": "fa9d632297e07c71321455d7ac82181279f4349b2878ed60e0088fb76453fa21", - "id": "fls_jx0784jzxy00", - "link": "concurrency.html#fls_jx0784jzxy00", - "number": "17.2:4" + "checksum": "9e23e5768ee96bddd83192456f161ce9e1ff1c043dfeb8c0a8d59713108c1f50", + "id": "fls_1s1UikGU5YQb", + "link": "statements.html#fls_1s1UikGU5YQb", + "number": "8.1:4" }, { - "checksum": "17b8684e84e891ce119a119dcdf3b5cc1df0bd9132fc39002ef445333a7386cf", - "id": "fls_vzuwnpx7mt08", - "link": "concurrency.html#fls_vzuwnpx7mt08", - "number": "17.2:5" + "checksum": "925e4f38b1fdb1037706134fa36069b0e1bf99946535bcf9436afefb7f125751", + "id": "fls_iB25BeFys0j8", + "link": "statements.html#fls_iB25BeFys0j8", + "number": "8.1:5" }, { - "checksum": "3df9c673e62071885a6938566ce59609242470b1c63272f358bf5f999f2324fe", - "id": "fls_cpcd0vexfbhj", - "link": "concurrency.html#fls_cpcd0vexfbhj", - "number": "17.2:6" + "checksum": "65cba8734e75668ea9ff7e09a1d7c86536c79970652b19d3888805207b40c3b2", + "id": "fls_zObyLdya4DYc", + "link": "statements.html#fls_zObyLdya4DYc", + "number": "8.1:6" }, { - "checksum": "9c9a349252f3cb141e008067381e5a19c45f3b1738132bfebc02583b25c36873", - "id": "fls_jt7rfq9atbiv", - "link": "concurrency.html#fls_jt7rfq9atbiv", - "number": "17.2:7" + "checksum": "14989244832f8c7b3fd3884340df2f4df49e5f8442fe0aa7848cc011cb94a6f6", + "id": "fls_r38TXWKQPjxv", + "link": "statements.html#fls_r38TXWKQPjxv", + "number": "8.1:7" }, { - "checksum": "72763ab828f9c57a7ba7ae57c095d30f4ef5bb34e5319df45f176f6570588e00", - "id": "fls_2hqmfwswc6k", - "link": "concurrency.html#fls_2hqmfwswc6k", - "number": "17.2:8" + "checksum": "1635f6e94205f0755cc45a4da9f7272940034fa7496b94ccdff8a2284b0b8e38", + "id": "fls_6QSdwF4pzjoe", + "link": "statements.html#fls_6QSdwF4pzjoe", + "number": "8.1:8" }, { - "checksum": "051b06b8e012ef9e9c02369b0cb8074a99acd99781938a746c9de16fa2ce4dcd", - "id": "fls_5ab2sw3gwmt3", - "link": "concurrency.html#fls_5ab2sw3gwmt3", - "number": "17.2:9" + "checksum": "6e23f656fcfe54a647a2278c5484a3fc80ed08112e18b6d4d6a5f11a8c2f6b5f", + "id": "fls_1prqh1trybwz", + "link": "statements.html#fls_1prqh1trybwz", + "number": "8.1:9" }, { - "checksum": "a8ab421b6894b056917c74396d9ac843c73dad27e12268a43fad03a877c8009c", - "id": "fls_w2mw833g28eb", - "link": "concurrency.html#fls_w2mw833g28eb", - "number": "17.2:10" + "checksum": "271df8c51d41c031901cb4d22bea984e6f6a8e260e878213b8ed7c580900eabc", + "id": "fls_djkm8r2iuu6u", + "link": "statements.html#fls_djkm8r2iuu6u", + "number": "8.1:10" }, { - "checksum": "f182e5451b68bf0b4094178052d33c3a59b7d40c5282e4bf77de423b44b77f8d", - "id": "fls_mjq1l1y0vmz4", - "link": "concurrency.html#fls_mjq1l1y0vmz4", - "number": "17.2:11" + "checksum": "5c3b2ab8a0b6d48dff5c50e3ca9743ba453c77aadcaa49d6a9485a5fc393c244", + "id": "fls_ppj9gvhp8wcj", + "link": "statements.html#fls_ppj9gvhp8wcj", + "number": "8.1:11" }, { - "checksum": "3b07334d52a95685b653abaecb58f071b99276e911b0f4bc4ab80cda4ef4ff1f", - "id": "fls_906978wtss6n", - "link": "concurrency.html#fls_906978wtss6n", - "number": "17.2:12" + "checksum": "9fc5558523a5dc96084b645182a9748a4ea46e67b31afc2341107cee0b0121aa", + "id": "fls_1eBQDZdBuDsN", + "link": "statements.html#fls_1eBQDZdBuDsN", + "number": "8.1:12" }, { - "checksum": "5b5d7f2d72de32a5c8c9e36b4111885655abad9b351cd2bdd65dac06dcdf6660", - "id": "fls_4urmnh4mfehl", - "link": "concurrency.html#fls_4urmnh4mfehl", - "number": "17.2:13" + "checksum": "99c4feb3265bf0e6b3a0c606397811171dba2b964d2471c4ccefa87af19ab501", + "id": "fls_m8a7gesa4oim", + "link": "statements.html#fls_m8a7gesa4oim", + "number": "8.1:13" }, { - "checksum": "b14552f886fa68d1904ccff6320f2d395f90fee027a70f30588a9ba68a53b172", - "id": "fls_2qkrcd5eovpe", - "link": "concurrency.html#fls_2qkrcd5eovpe", - "number": "17.2:14" + "checksum": "ed71cf991f9a7542c7a7106e4754bb01cdd386dcf21b1c27e7f21519ac4a12cc", + "id": "fls_oaxnre7m9s10", + "link": "statements.html#fls_oaxnre7m9s10", + "number": "8.1:14" }, { - "checksum": "1bf7e1661a0f7e7762a5549fe82988a4afbe4436720588214b9ad3857ecc02a7", - "id": "fls_cry1e78gp19q", - "link": "concurrency.html#fls_cry1e78gp19q", - "number": "17.2:15" - } - ], - "title": "Atomics" - }, - { - "id": "fls_mtuwzinpfvkl", - "informational": false, - "link": "concurrency.html#asynchronous-computation", - "number": "17.3", - "paragraphs": [ - { - "checksum": "dfbc3bd3bcdae5a3f76ab60f5c1fbcb25458c3975778746db7eea952015a2f79", - "id": "fls_g40xp4andj5g", - "link": "concurrency.html#fls_g40xp4andj5g", - "number": "17.3:1" + "checksum": "24c8c95a02e8ee0e3828c7b00c71b666881c4ac437e3c018d06b7d83ad58b70d", + "id": "fls_t5bjwluyv8za", + "link": "statements.html#fls_t5bjwluyv8za", + "number": "8.1:15" }, { - "checksum": "0a94602025c891b37d5f98e1ccd525f0cd66c06df02df85b06336668d0711cbe", - "id": "fls_fte085hi1yqj", - "link": "concurrency.html#fls_fte085hi1yqj", - "number": "17.3:2" + "checksum": "017c4fc4f03e296f8d96a0a62fc006c647bf0a9faaa94ad561061c93e9402928", + "id": "fls_4j9riqyf4p9", + "link": "statements.html#fls_4j9riqyf4p9", + "number": "8.1:16" }, { - "checksum": "306a3b2eabc2ff31f77f6446060185578f9427fcc497d1588cb51a4c94ae4819", - "id": "fls_7muubin2wn1v", - "link": "concurrency.html#fls_7muubin2wn1v", - "number": "17.3:3" + "checksum": "ab96a34396479306ef71b507703ffdd2fe9dd68512c87538bc972ad910a8d508", + "id": "fls_t53g5hlabqw1", + "link": "statements.html#fls_t53g5hlabqw1", + "number": "8.1:17" }, { - "checksum": "d6389f23a8eff8343451429941d835b9e1b00fa45cf260112041467021172752", - "id": "fls_ftzey2156ha", - "link": "concurrency.html#fls_ftzey2156ha", - "number": "17.3:4" - } - ], - "title": "Asynchronous Computation" - } - ], - "title": "Concurrency" - }, - { - "informational": false, - "link": "types-and-traits.html", - "sections": [ - { - "id": "fls_vgb6ev541b2r", - "informational": false, - "link": "types-and-traits.html", - "number": "4", - "paragraphs": [], - "title": "Types and Traits" - }, - { - "id": "fls_kwsbxmqntrnl", - "informational": false, - "link": "types-and-traits.html#types", - "number": "4.1", - "paragraphs": [ - { - "checksum": "0f3224c81474a43ebf7a037ebb226c3b28b450e79a50de818459bb6d93a03b4d", - "id": "fls_4rhjpdu4zfqj", - "link": "types-and-traits.html#fls_4rhjpdu4zfqj", - "number": "4.1:1" + "checksum": "3c6ccdcf4fec0d1389605503a83c401562b7c707fe92ad33814c8cd10b1d3b1e", + "id": "fls_7j4qlwg72ege", + "link": "statements.html#fls_7j4qlwg72ege", + "number": "8.1:18" }, { - "checksum": "d7948a2adf90ff0fa015b4f90967c0801c9271d1aaf7f17e291db8a682522035", - "id": "fls_0yaYKnFrJkhG", - "link": "types-and-traits.html#fls_0yaYKnFrJkhG", - "number": "4.1:2" + "checksum": "14d436071a7dda876a858e8877ad564c71299dacb6239d9bff7ea3299d8d6a65", + "id": "fls_ea9bRFZjH8Im", + "link": "statements.html#fls_ea9bRFZjH8Im", + "number": "8.1:19" } ], - "title": "Types" + "title": "Let Statements" }, { - "id": "fls_963gsjp2jas2", - "informational": true, - "link": "types-and-traits.html#type-classification", - "number": "4.2", + "id": "fls_1pg5ig740tg1", + "informational": false, + "link": "statements.html#expression-statements", + "number": "8.2", "paragraphs": [ { - "checksum": "23b6212015d6348a703cb5be2c0d3c0f812bfb0ece7139873025d578f50472c1", - "id": "fls_c4xe3pkn0n3o", - "link": "types-and-traits.html#fls_c4xe3pkn0n3o", - "number": "4.2:1" - }, - { - "checksum": "e9bace1e5151ffdeeff57cb5069120c81735b17734fa05f4c4075277f6e081ad", - "id": "fls_69zyas59o8ff", - "link": "types-and-traits.html#fls_69zyas59o8ff", - "number": "4.2:2" - }, - { - "checksum": "40842ff24d030ea0ccb5075ecd6ec1492cd9d9c98e960ed9563cfe03bb7e2487", - "id": "fls_65hcyqizo1da", - "link": "types-and-traits.html#fls_65hcyqizo1da", - "number": "4.2:3" + "checksum": "990f51f353986bd884828681d0f82474e27e8ff3042cd1c0b1908f048e7c7315", + "id": "fls_xmdj8uj7ixoe", + "link": "statements.html#fls_xmdj8uj7ixoe", + "number": "8.2:1" }, { - "checksum": "cb6e139cbd46fe7899e08fd9f0c9a48a4bccedd925c0a7bcf78f8babca270948", - "id": "fls_zge99l49az8w", - "link": "types-and-traits.html#fls_zge99l49az8w", - "number": "4.2:4" + "checksum": "0c5f0f107d5b1e196ef1a3003b0eb3d235bc6cbebd98a9537e5a9caf9df0c146", + "id": "fls_gzzmudc1hl6s", + "link": "statements.html#fls_gzzmudc1hl6s", + "number": "8.2:2" }, { - "checksum": "e8e0cdfb96e0c3ecba3291745e9a1299586805ca7b8b0aecf19b7d32f235e8f7", - "id": "fls_vizoconv3ir", - "link": "types-and-traits.html#fls_vizoconv3ir", - "number": "4.2:5" + "checksum": "28b62503095184fa5c0dc9898c27c50ecaeebfef606f663370bdcaefa1c68cea", + "id": "fls_kc99n8qrszxh", + "link": "statements.html#fls_kc99n8qrszxh", + "number": "8.2:3" }, { - "checksum": "25f59f09cdc29f543ccae977ee66e0ee9b0fee1d55202cfa983eabe4f00b16a3", - "id": "fls_ne6bgnh1eyrj", - "link": "types-and-traits.html#fls_ne6bgnh1eyrj", - "number": "4.2:6" + "checksum": "f02ee18552936ea98c3764181f5651c46b54e5af37f3149b17bd6906ecb33556", + "id": "fls_r8poocwqaglf", + "link": "statements.html#fls_r8poocwqaglf", + "number": "8.2:4" }, { - "checksum": "00ac1f2e576d6d3d14dcb244d363bf14795ddfee222c420efa28a171c91598f2", - "id": "fls_jvj8l8366kl2", - "link": "types-and-traits.html#fls_jvj8l8366kl2", - "number": "4.2:7" + "checksum": "cf7b0c182db5007a86bf5c05bdfb68e63de338e95ef2e377bafa1708a7c00738", + "id": "fls_88e6s3erk8tj", + "link": "statements.html#fls_88e6s3erk8tj", + "number": "8.2:5" }, { - "checksum": "65b9ca746467c1af24795a49cf507e5a0494d59801db4665214cf5d87fb169db", - "id": "fls_eek1jn1rwjh9", - "link": "types-and-traits.html#fls_eek1jn1rwjh9", - "number": "4.2:8" + "checksum": "fe5fb38b0a811437c9b5e6e9f70af727c9013f79573eee3673146cf030c660c4", + "id": "fls_4q90jb39apwr", + "link": "statements.html#fls_4q90jb39apwr", + "number": "8.2:6" }, { - "checksum": "a23c0b697c2b2e686673f2956a5b9c81a3369d2a7897c67840ba014cc59c625d", - "id": "fls_s0aduyvz4i7f", - "link": "types-and-traits.html#fls_s0aduyvz4i7f", - "number": "4.2:9" + "checksum": "737bef4656101e6322af305e26b7c23b0c56fe07e9b5404ed0fe6daff16a8d02", + "id": "fls_xqtztcu8ibwq", + "link": "statements.html#fls_xqtztcu8ibwq", + "number": "8.2:7" }, { - "checksum": "b59affa079dfb0e436f28a313ff4e96d3831d765ff0063eb18342f6e749c79a7", - "id": "fls_zb5e79ai7w5i", - "link": "types-and-traits.html#fls_zb5e79ai7w5i", - "number": "4.2:10" + "checksum": "93c30bb9a1f379d6fce63a4b7aded720c5328d70432fdb6c05a7aa2312426a73", + "id": "fls_2p9xnt519nbw", + "link": "statements.html#fls_2p9xnt519nbw", + "number": "8.2:8" + } + ], + "title": "Expression Statements" + } + ], + "title": "Statements" + }, + { + "informational": false, + "link": "patterns.html", + "sections": [ + { + "id": "fls_xgqh0ju6bmbn", + "informational": false, + "link": "patterns.html", + "number": "5", + "paragraphs": [ + { + "checksum": "fd701b3ef4e17c49416e15bb5e0685b07ec2165ebe338ee5ed86433fe2a1791c", + "id": "fls_imegtsi224ts", + "link": "patterns.html#fls_imegtsi224ts", + "number": "5:1" }, { - "checksum": "4eb758fbe24b08d1aee74c97d4f8541b0dbcb444784a6569c21d17094fb49fb1", - "id": "fls_yjp19vt46asy", - "link": "types-and-traits.html#fls_yjp19vt46asy", - "number": "4.2:11" + "checksum": "2e7d16a81699ee46155a23eecb0a8984103307894e9947cc4e2b25beb667d156", + "id": "fls_VQMmveZUfNTn", + "link": "patterns.html#fls_VQMmveZUfNTn", + "number": "5:2" }, { - "checksum": "1e5dc2e803a63fd05b7dec479ef1d0add6eea160c45fe290772c02fd26bc89d9", - "id": "fls_xflj5df6upc7", - "link": "types-and-traits.html#fls_xflj5df6upc7", - "number": "4.2:12" + "checksum": "f11f4d96ce76157d36700b1642b225614023417f88f39e02004f751fa0ceca1c", + "id": "fls_mp6i4blzexnu", + "link": "patterns.html#fls_mp6i4blzexnu", + "number": "5:3" }, { - "checksum": "b85bfd2e2cfc13a7fc78a87c008d62c82d235fb2d2d7e4d188df4acda7e8aac5", - "id": "fls_u43jnp9jnw29", - "link": "types-and-traits.html#fls_u43jnp9jnw29", - "number": "4.2:13" + "checksum": "640a01339d6302f50e834a92db0b99f585848a4dc1dfb4801941267dfe6c1eff", + "id": "fls_JJ1fJa1SsaWh", + "link": "patterns.html#fls_JJ1fJa1SsaWh", + "number": "5:4" }, { - "checksum": "454e730423cb4d7f069989e61ff7aa256e85e03a5c1e7b29e64e627e192c47e1", - "id": "fls_lric8bf631nw", - "link": "types-and-traits.html#fls_lric8bf631nw", - "number": "4.2:14" + "checksum": "28dec289d15cad76b56462aa121b43b5d1e42ef0f0d0636c38ac0eb7eca4b4f9", + "id": "fls_6xx34zr069bj", + "link": "patterns.html#fls_6xx34zr069bj", + "number": "5:5" }, { - "checksum": "e821be0746413ff9f5096aec87f2ce2d467ea6cb27fb68fa64c8a116c0207238", - "id": "fls_98djh9avlqc0", - "link": "types-and-traits.html#fls_98djh9avlqc0", - "number": "4.2:15" + "checksum": "32457ed53a0bf4373c4cfb97677b5e208930233d39b87a5accc8c508af6f58ce", + "id": "fls_8xzjb0yzftkd", + "link": "patterns.html#fls_8xzjb0yzftkd", + "number": "5:6" }, { - "checksum": "fe0aac8860ab0c5746c0c07b373e29983a82fc0ebd52f6dd2630ace413a9fabb", - "id": "fls_b3ymsm8dmo4", - "link": "types-and-traits.html#fls_b3ymsm8dmo4", - "number": "4.2:16" + "checksum": "5ac967f09550667532acd0916172717779d286369a004f6e89c12e7852e25bc4", + "id": "fls_cma5t8waon0x", + "link": "patterns.html#fls_cma5t8waon0x", + "number": "5:7" }, { - "checksum": "c36d1f9962298745f389857334fa11b8218d392e0116702a80ad091719503e13", - "id": "fls_9x5atvhdq0j2", - "link": "types-and-traits.html#fls_9x5atvhdq0j2", - "number": "4.2:17" + "checksum": "d92c5c133b1308dfee746f9722cc628be4cc1b150f3f387c0d2c08e961e687fb", + "id": "fls_TUanRT7WU14E", + "link": "patterns.html#fls_TUanRT7WU14E", + "number": "5:8" }, { - "checksum": "e46c85f680edc5614a12b9207c497460b0042af968c92f40fa5ab637e14571a2", - "id": "fls_n5rgqgnxk9to", - "link": "types-and-traits.html#fls_n5rgqgnxk9to", - "number": "4.2:18" + "checksum": "76ef1771e13cdc1b8dad94d945851e96cf1e0dca5248e57477f71ce6cda80f69", + "id": "fls_72JHo343O7jp", + "link": "patterns.html#fls_72JHo343O7jp", + "number": "5:9" }, { - "checksum": "eaf6ddda440baca7044641c33a4f8d9d84b75648732ea1f9dc8bbb44641bbb7a", - "id": "fls_s7ndqc5sizdy", - "link": "types-and-traits.html#fls_s7ndqc5sizdy", - "number": "4.2:19" + "checksum": "0bfe474ea8833b7a5007ff4a1e538f5d0205dbcb0de5771c8971175d781819ec", + "id": "fls_6bwTtGKb7ba7", + "link": "patterns.html#fls_6bwTtGKb7ba7", + "number": "5:10" }, { - "checksum": "64386326aff45271fd69d938c23b27ac3d34103a3520b4b76a78cf860663636b", - "id": "fls_jrohsv7hx7yw", - "link": "types-and-traits.html#fls_jrohsv7hx7yw", - "number": "4.2:20" + "checksum": "75924b5c01c922a97bc85d9c2d6f7b9d4da495212045063f33bccd613da4869a", + "id": "fls_8luyomzppck", + "link": "patterns.html#fls_8luyomzppck", + "number": "5:11" }, { - "checksum": "da5f5e5fcdb5726c1a0dabc99f8770f5e3fbae1459f5833ff33532110822caf6", - "id": "fls_1kg1mknf4yx7", - "link": "types-and-traits.html#fls_1kg1mknf4yx7", - "number": "4.2:21" + "checksum": "b70079f33aaa9cb4c2507706bdb13c9e8beed748288a7751770b7aa4309c17bf", + "id": "fls_rpvdfmy3n05a", + "link": "patterns.html#fls_rpvdfmy3n05a", + "number": "5:12" }, { - "checksum": "22c62b9e8996f6fbd50fec2e75e53a84a790744792c0aa40834de29215569f84", - "id": "fls_bw8zutjcteki", - "link": "types-and-traits.html#fls_bw8zutjcteki", - "number": "4.2:22" + "checksum": "9931e26d30bcf23edc7433916de4a6c22fec5fc0614afe05991620ec08509266", + "id": "fls_YDVgFaTQwcL8", + "link": "patterns.html#fls_YDVgFaTQwcL8", + "number": "5:13" }, { - "checksum": "b0d2ad5646194c0d8310df4d3333f7afb79755cea464d60a3fbda29273383ef6", - "id": "fls_nqezuc9u6wpn", - "link": "types-and-traits.html#fls_nqezuc9u6wpn", - "number": "4.2:23" + "checksum": "52545837b9862e70dc97203f0a7e1d899fb3e37432c52e62a76dbf52c298f7e1", + "id": "fls_kv533rntni1x", + "link": "patterns.html#fls_kv533rntni1x", + "number": "5:14" + } + ], + "title": "Patterns" + }, + { + "id": "fls_uh76pw6ykd57", + "informational": false, + "link": "patterns.html#refutability", + "number": "5.1", + "paragraphs": [ + { + "checksum": "74e294f01efeecaf5f7ea7508839e45750aee3872825525951cd7e1a5a369127", + "id": "fls_9ntc4qmjmo90", + "link": "patterns.html#fls_9ntc4qmjmo90", + "number": "5.1:1" }, { - "checksum": "f1a647d67b3a801d2ab2b7a315771e5d26e67d2662bb331181a88afa5c152bc1", - "id": "fls_lh52q6f6snfh", - "link": "types-and-traits.html#fls_lh52q6f6snfh", - "number": "4.2:24" + "checksum": "bedd24701ba15fd54193582bf452c8d028b7e7d24e62f7cf549bccc5f8d4e4b8", + "id": "fls_9fjspnefoyvz", + "link": "patterns.html#fls_9fjspnefoyvz", + "number": "5.1:2" }, { - "checksum": "9ca4aa25f308f26f33fb8ec4fe5dec350c1d8526c6675053e5be2719e863556f", - "id": "fls_qqg0uixrd1a4", - "link": "types-and-traits.html#fls_qqg0uixrd1a4", - "number": "4.2:25" + "checksum": "30c83040e0dcb6e5472460546b82a2f1223224d1b6b315de2204a12c412741b0", + "id": "fls_uq7ftuuq1sig", + "link": "patterns.html#fls_uq7ftuuq1sig", + "number": "5.1:3" }, { - "checksum": "d8cca7522f47f0479ce66bd850d2931ae4f7c4c1d4b198e4aad88f9b90a1ac07", - "id": "fls_b8ecqp2argmn", - "link": "types-and-traits.html#fls_b8ecqp2argmn", - "number": "4.2:26" + "checksum": "788cc1b098646556626fa9669f0f2c05d8907e582b4bc899ecac453fcfc61dde", + "id": "fls_mnbyt7jfYAZ9", + "link": "patterns.html#fls_mnbyt7jfYAZ9", + "number": "5.1:4" }, { - "checksum": "21c1d49526e5ca8d23c874a230759bb497548ab543740fce1095850fdc4fcf63", - "id": "fls_m5vtcars8aga", - "link": "types-and-traits.html#fls_m5vtcars8aga", - "number": "4.2:27" + "checksum": "fb93c07735fd3880cae598758a01af1eb04fd98aa6fbdca79a2fd2e8a525c26d", + "id": "fls_l76ycteulo8e", + "link": "patterns.html#fls_l76ycteulo8e", + "number": "5.1:5" }, { - "checksum": "753ab4b03e9c965a6fffd92d30e2769c5a63a344bfb1b7e76971ac8499be82d8", - "id": "fls_lw38557rqikt", - "link": "types-and-traits.html#fls_lw38557rqikt", - "number": "4.2:28" + "checksum": "8cbff3175ce2f1524fa0ece5a443ba2edd4a78433ff410a98a31ed00f38a2bde", + "id": "fls_lh0d85tl4qvy", + "link": "patterns.html#fls_lh0d85tl4qvy", + "number": "5.1:6" }, { - "checksum": "0dd8bcf2a274d471aaa36ab380441dd3b6ca39976303324ddd118e50013d02dd", - "id": "fls_jxn63ow9xby3", - "link": "types-and-traits.html#fls_jxn63ow9xby3", - "number": "4.2:29" + "checksum": "abe5de2e97771298a372e5a404f34278ed54f0e8ada2a337d6f5d85fc5a7e7c0", + "id": "fls_sgu9bnp7xajv", + "link": "patterns.html#fls_sgu9bnp7xajv", + "number": "5.1:7" }, { - "checksum": "1fa312839ea42e8a1bbcaecbc879efed22264c8ac233c6cfe5339ac2db24a2b3", - "id": "fls_a81tweobvm0p", - "link": "types-and-traits.html#fls_a81tweobvm0p", - "number": "4.2:30" + "checksum": "d347b514f8d0662ae967270ae73f6f7be119b4ab60fdf44106ebe781e251e33d", + "id": "fls_cl1g4fxfa020", + "link": "patterns.html#fls_cl1g4fxfa020", + "number": "5.1:8" } ], - "title": "Type Classification" - }, - { - "id": "fls_id66vnaqw0zt", - "informational": false, - "link": "types-and-traits.html#scalar-types", - "number": "4.3", - "paragraphs": [], - "title": "Scalar Types" + "title": "Refutability" }, { - "id": "fls_tiqp1gxf116z", + "id": "fls_7bxv8lybxm18", "informational": false, - "link": "types-and-traits.html#bool-type", - "number": "4.3.1", + "link": "patterns.html#identifier-patterns", + "number": "5.2", "paragraphs": [ { - "checksum": "7c27f15ca1c523ef1db5071f2a63bedb21181cf04aa94d9878250ca53b434971", - "id": "fls_h5994su1yft3", - "link": "types-and-traits.html#fls_h5994su1yft3", - "number": "4.3.1:1" - }, - { - "checksum": "930adef3b1506fb337f66675f973e54769ff8fca1db32c838183ef22a2422029", - "id": "fls_v8atmrwz6wzk", - "link": "types-and-traits.html#fls_v8atmrwz6wzk", - "number": "4.3.1:2" - }, - { - "checksum": "9858c13d3708357c30873cd608d2b3383dcc3035f0dd687ef3896541dd1695fd", - "id": "fls_iye7ho2ynyhn", - "link": "types-and-traits.html#fls_iye7ho2ynyhn", - "number": "4.3.1:3" + "checksum": "12df3c9420d70b5492aee216c48d595bec10c1bfc7c95589c1aa150787788c68", + "id": "fls_uljdw9rf7ies", + "link": "patterns.html#fls_uljdw9rf7ies", + "number": "5.2:1" }, { - "checksum": "ac65b3d93d3994e5d0068317e02275db13c2c1913d48494bb2de79b24ec72eec", - "id": "fls_7nd5tixyqir8", - "link": "types-and-traits.html#fls_7nd5tixyqir8", - "number": "4.3.1:4" + "checksum": "241eeb37b7dae1635d202ba61372dfbf4a693e183ca1ece17e3818904f6c809d", + "id": "fls_vy9uw586wy0d", + "link": "patterns.html#fls_vy9uw586wy0d", + "number": "5.2:2" }, { - "checksum": "1d12935dda46819ad41ab1cb8f82b49105c7771bd832109bde39a210a366c23f", - "id": "fls_w2dzqq54fjhb", - "link": "types-and-traits.html#fls_w2dzqq54fjhb", - "number": "4.3.1:5" + "checksum": "d9b79bd4e33b5e5d59d8f9ed2df014f66c7fa0c487fc3b0b70e3700fdc7dc420", + "id": "fls_hqwt3fvr063y", + "link": "patterns.html#fls_hqwt3fvr063y", + "number": "5.2:3" }, { - "checksum": "b94216bb8df77b4a82ab94c901933b3b6d6d065364960315dab51522e1c8c31e", - "id": "fls_ufmd38hi9t9y", - "link": "types-and-traits.html#fls_ufmd38hi9t9y", - "number": "4.3.1:6" + "checksum": "4f2142d8ccdb2e64efc1eaedfef6bdc5b02f5ab32bf7ed59698ff61681fd7384", + "id": "fls_joIQdDn44oIT", + "link": "patterns.html#fls_joIQdDn44oIT", + "number": "5.2:4" }, { - "checksum": "99e00eb99e3b71db0f644227f19ee709ef82e2c6f386deaa36c2a399f7979685", - "id": "fls_5allcjkjnon2", - "link": "types-and-traits.html#fls_5allcjkjnon2", - "number": "4.3.1:7" + "checksum": "e7b226edcf6dc4bc129b1212d923d2b0052764e5f3087f635a8f5c48af37d436", + "id": "fls_24c95c56tugl", + "link": "patterns.html#fls_24c95c56tugl", + "number": "5.2:5" }, { - "checksum": "09b53e4da86723e2bd2011d45d92331dab43e0172cfef5543bb7c74a77d1fc62", - "id": "fls_3bibysz95ktn", - "link": "types-and-traits.html#fls_3bibysz95ktn", - "number": "4.3.1:8" + "checksum": "3ee68e9c11cc6de58983cada3e69d6e25c9096bea32debb22614bb2a25d155f0", + "id": "fls_twcavjk7iquy", + "link": "patterns.html#fls_twcavjk7iquy", + "number": "5.2:6" }, { - "checksum": "6cb77addba9281730832f2bbc4ddd2082c3530a2c17878646b47db6c10dd80cf", - "id": "fls_fxq19dqtmifj", - "link": "types-and-traits.html#fls_fxq19dqtmifj", - "number": "4.3.1:9" + "checksum": "e762d3ac05f362349d87a2a26cc37f37eaa2148eadb4ab7a97745abed9a62df2", + "id": "fls_k1yBTstX7jEE", + "link": "patterns.html#fls_k1yBTstX7jEE", + "number": "5.2:7" }, { - "checksum": "98f3e91912ee93fd291d169f31739e7c2c7b38d644d27336dfff508ebb7dfb99", - "id": "fls_drhpcwoblcux", - "link": "types-and-traits.html#fls_drhpcwoblcux", - "number": "4.3.1:10" + "checksum": "a52f9ed5eaa2b34c018a779a37184c71defa111f92bc5a73d7642178df8e755f", + "id": "fls_hw26hy33guk5", + "link": "patterns.html#fls_hw26hy33guk5", + "number": "5.2:8" }, { - "checksum": "8a7e7470fb69b8816a48fa5e95512a5456665e561c4134f050a155f985eca72b", - "id": "fls_v86qrsqcs3nd", - "link": "types-and-traits.html#fls_v86qrsqcs3nd", - "number": "4.3.1:11" + "checksum": "b439ed8b20762b4889891571152f54745dc81b2f6b4abc45db0448e2cac4888d", + "id": "fls_svfxwz4yy5i", + "link": "patterns.html#fls_svfxwz4yy5i", + "number": "5.2:9" }, { - "checksum": "1c7470e26077ea7a073dc2a1445c678484af7588bb3235586c9091f71603cc7e", - "id": "fls_dd49lb2k3erc", - "link": "types-and-traits.html#fls_dd49lb2k3erc", - "number": "4.3.1:12" + "checksum": "db308f657bf02a10ddea3390b29681c6eccf4bb81b2069d5f1b2ecfbc7853dc1", + "id": "fls_x6f6q22b5jpc", + "link": "patterns.html#fls_x6f6q22b5jpc", + "number": "5.2:10" }, { - "checksum": "32464bf4444f9dc7d6b1679119e45d5351fa00d79dea5e7ad487d3ddf76fc881", - "id": "fls_t6ef5x4x5poi", - "link": "types-and-traits.html#fls_t6ef5x4x5poi", - "number": "4.3.1:13" + "checksum": "dd8b86458df1b70addc97086f4ada7c7692ff5103622010059fbf0283f8ffbb3", + "id": "fls_r2mb8v2lh3x0", + "link": "patterns.html#fls_r2mb8v2lh3x0", + "number": "5.2:11" }, { - "checksum": "bd7ead8e9ea1b7683e87bfdc7788222d47c759c66c031ef374dda05805dbd6d3", - "id": "fls_kqtgjgn1hqrj", - "link": "types-and-traits.html#fls_kqtgjgn1hqrj", - "number": "4.3.1:14" + "checksum": "0905da49ff8fceb1c24a37a019ba80b02f466cbede9db934f3506067251411de", + "id": "fls_7oioaitb075g", + "link": "patterns.html#fls_7oioaitb075g", + "number": "5.2:12" }, { - "checksum": "9a544d8e0daca2681dbcfedf72121255fea14ab0944581480d57fba0890b95fe", - "id": "fls_ws15ilzf8n6z", - "link": "types-and-traits.html#fls_ws15ilzf8n6z", - "number": "4.3.1:15" + "checksum": "11934cfbec627d51e8441eeef28fa1983d4ed59d73d9dcbff26e5d4cf4007fed", + "id": "fls_40qin0ss5sqd", + "link": "patterns.html#fls_40qin0ss5sqd", + "number": "5.2:13" }, { - "checksum": "f3145e61ced027d25036f3200ca5bbb0618bc0477cb9b3603a1413652b3ac917", - "id": "fls_ni4mgq3mouek", - "link": "types-and-traits.html#fls_ni4mgq3mouek", - "number": "4.3.1:16" + "checksum": "05ce4678c850feb8fbdc7b0bf9a65ccdd82414dec3163903b5ff487cf3cc1b8c", + "id": "fls_pivz0v7ey6sw", + "link": "patterns.html#fls_pivz0v7ey6sw", + "number": "5.2:14" }, { - "checksum": "ac4e3a21890e3221845618cc38dbace293fd0e68d017770aa5e7e78e2f5ab08e", - "id": "fls_6c9ax4qsr1gy", - "link": "types-and-traits.html#fls_6c9ax4qsr1gy", - "number": "4.3.1:17" + "checksum": "6827708c08fa8d7048aaf70d448f1ed456a018a5621ccbad9e38ba38eb4f001f", + "id": "fls_2ahkrddxwj1n", + "link": "patterns.html#fls_2ahkrddxwj1n", + "number": "5.2:15" }, { - "checksum": "2385dd82b0f0349c33f8b5af00d47e3440494e1faa8b63553050feb2a8459e67", - "id": "fls_sqcgvpr4egtx", - "link": "types-and-traits.html#fls_sqcgvpr4egtx", - "number": "4.3.1:18" + "checksum": "5938b3e5c53f5eb860f3c7aae1864e4e5b74c0a93b9460455798ee597639f3bd", + "id": "fls_eucnafj3uedy", + "link": "patterns.html#fls_eucnafj3uedy", + "number": "5.2:16" }, { - "checksum": "441b5d0aa5e6c402c6beb7fde327f5827525511f230577ba7c6fe5a18d28539c", - "id": "fls_9ys0itbp4okd", - "link": "types-and-traits.html#fls_9ys0itbp4okd", - "number": "4.3.1:19" + "checksum": "25e0df417675d625d1a57e95afba6af3de12fa61abe805e4d2d81e90613250d0", + "id": "fls_f8zo4scodhcr", + "link": "patterns.html#fls_f8zo4scodhcr", + "number": "5.2:17" }, { - "checksum": "9480e4048cd08398ae0f13cb516a29613f3e742d3d99bc779578398b92272c24", - "id": "fls_b46gbyid15zx", - "link": "types-and-traits.html#fls_b46gbyid15zx", - "number": "4.3.1:20" + "checksum": "7d886e7fba9c09e14a0789ddb951c710bb88a50cf8e06a6bcdd6629fb55562f6", + "id": "fls_d3fs2h7oqjl0", + "link": "patterns.html#fls_d3fs2h7oqjl0", + "number": "5.2:18" }, { - "checksum": "166b43ef343f45896926b2fbb09d952a0c23d942c2a113f0860ffe979b8437b3", - "id": "fls_f8ag276ecbze", - "link": "types-and-traits.html#fls_f8ag276ecbze", - "number": "4.3.1:21" + "checksum": "655158cf590f06600319e8e2695db1b56219fadc4cc034888e17c53b07a84274", + "id": "fls_exo8asevh5x1", + "link": "patterns.html#fls_exo8asevh5x1", + "number": "5.2:19" }, { - "checksum": "ca76c9fea33319ebf91566f9a4e1cadfeadce5282e098010c5ef67471d744f45", - "id": "fls_twwjcrcfirdi", - "link": "types-and-traits.html#fls_twwjcrcfirdi", - "number": "4.3.1:22" + "checksum": "d3b46f205bcb595fe85d75191fcd80dd7933343080fd05b57200c0118ad6b609", + "id": "fls_sfyfdxhvhk44", + "link": "patterns.html#fls_sfyfdxhvhk44", + "number": "5.2:20" }, { - "checksum": "a2d98732aced3240075311e8a7738d2601f8a1e817a29a0b1d2d0ada3525abea", - "id": "fls_wovu7330vdrq", - "link": "types-and-traits.html#fls_wovu7330vdrq", - "number": "4.3.1:23" - }, + "checksum": "b32516046ade3590c7036c2a5ecbf17d55a9f6f00da394c3d5281096f8cf26dc", + "id": "fls_as0pqqmo1des", + "link": "patterns.html#fls_as0pqqmo1des", + "number": "5.2:21" + } + ], + "title": "Identifier Patterns" + }, + { + "id": "fls_2krxnq8q9ef1", + "informational": false, + "link": "patterns.html#literal-patterns", + "number": "5.3", + "paragraphs": [ { - "checksum": "d3df0e58549b33e4a804eced95611bbf74505e40e62fb919b895a104ee2cdd93", - "id": "fls_7xopdco6iy74", - "link": "types-and-traits.html#fls_7xopdco6iy74", - "number": "4.3.1:24" + "checksum": "7afee4377bf01eee53e398835c943ac86067eba24c1cf64551d7f801915e5ece", + "id": "fls_pah15qa54irs", + "link": "patterns.html#fls_pah15qa54irs", + "number": "5.3:1" }, { - "checksum": "5689110f0fe899e7c5dff9675885b2c735444de21ad35f3a1ba69dc76fb327b6", - "id": "fls_nb5cb6en2p5w", - "link": "types-and-traits.html#fls_nb5cb6en2p5w", - "number": "4.3.1:25" + "checksum": "c8e3035b1a25edf2fccd9214fd3039e797d799bd22e801fdeda70976a39c68a2", + "id": "fls_COQKJC0dvtNO", + "link": "patterns.html#fls_COQKJC0dvtNO", + "number": "5.3:2" }, { - "checksum": "699e51364da08d56388376c014ee12beee95aeae6302a1234dc8c567774a82e4", - "id": "fls_gd28wfcfs2pv", - "link": "types-and-traits.html#fls_gd28wfcfs2pv", - "number": "4.3.1:26" + "checksum": "279b07c6a35f3bcfed9d5bcccc78afdc28e8982a5be9e9b768dd95c2a3b0ae9c", + "id": "fls_JP8YSbxSN0Ym", + "link": "patterns.html#fls_JP8YSbxSN0Ym", + "number": "5.3:3" }, { - "checksum": "1a3ae601414d01796aaf6b322e02874830121fe0ab9b9bf076606a5329772a19", - "id": "fls_67a7p57nzbul", - "link": "types-and-traits.html#fls_67a7p57nzbul", - "number": "4.3.1:27" + "checksum": "b5e4dea23898449138503e31ca3616865806df2f2af08e0460bf75c15cfda0d0", + "id": "fls_co60bzvwashg", + "link": "patterns.html#fls_co60bzvwashg", + "number": "5.3:4" }, { - "checksum": "581c5941548d5821d9a034095690bb3c131fc9ba9dffcc9a2e85aee1b8d8cd88", - "id": "fls_cq0qunw51m94", - "link": "types-and-traits.html#fls_cq0qunw51m94", - "number": "4.3.1:28" - }, + "checksum": "617fb1beb5774f8f6685a4671e5bf2d72fd056bbe348e8236f22254e8462a51a", + "id": "fls_fqclaznjgtb1", + "link": "patterns.html#fls_fqclaznjgtb1", + "number": "5.3:5" + } + ], + "title": "Literal Patterns" + }, + { + "id": "fls_1xit18et4ohh", + "informational": false, + "link": "patterns.html#parenthesized-patterns", + "number": "5.4", + "paragraphs": [ { - "checksum": "d2b050f0a32abdd45281470a08c3d6427a82b2db396f1dc7ff325370895077b6", - "id": "fls_o1e4tnh7v3db", - "link": "types-and-traits.html#fls_o1e4tnh7v3db", - "number": "4.3.1:29" + "checksum": "24403c83d4acc034aaa73d968a14afbfe7d9f1bfdbee90b17ef960075453aaac", + "id": "fls_kvqzmt7my5dh", + "link": "patterns.html#fls_kvqzmt7my5dh", + "number": "5.4:1" }, { - "checksum": "2e6d782fc4fad2d1e3f018b424d77847ffab9b7df0a4aa34218e81df37554d42", - "id": "fls_6vnv3ygisjr", - "link": "types-and-traits.html#fls_6vnv3ygisjr", - "number": "4.3.1:30" + "checksum": "5f42a52d41b664edb742dfd664f01990bf8432d041e601e283d750cb83a85f90", + "id": "fls_mrjhpiq5refe", + "link": "patterns.html#fls_mrjhpiq5refe", + "number": "5.4:2" }, { - "checksum": "b50023ba7c3a0f6e5d60727a63cc05db279f39c522363df22b0dd73fe324e5ff", - "id": "fls_s6m9abmmtc9i", - "link": "types-and-traits.html#fls_s6m9abmmtc9i", - "number": "4.3.1:31" + "checksum": "41b7c39a45d4144509bc833e84ea455b7d86c0a67c5d58960e14e6337b2c44d4", + "id": "fls_pe5kh8y8u664", + "link": "patterns.html#fls_pe5kh8y8u664", + "number": "5.4:3" }, { - "checksum": "3737ba54b255bd74767127bd9ba5b403b1b0aa6d139fffa616bf524ed612b7e0", - "id": "fls_s19vu65z96y5", - "link": "types-and-traits.html#fls_s19vu65z96y5", - "number": "4.3.1:32" + "checksum": "edcbe640bd79fa7513605658a4cc16f8d648069f3cca0dd200ee8a2224d1612e", + "id": "fls_2xq8852gihn9", + "link": "patterns.html#fls_2xq8852gihn9", + "number": "5.4:4" }, { - "checksum": "cb35c3709e468c678e9fd298ff75cab85223c9b69520bb115ece6c07f023f163", - "id": "fls_2d4aqspw0wlt", - "link": "types-and-traits.html#fls_2d4aqspw0wlt", - "number": "4.3.1:33" - }, + "checksum": "6b2bf289b930cfce356ecbd497063fe3f045ee08dc3919d303a7a90c52eae0fb", + "id": "fls_2dmeukyjqz9y", + "link": "patterns.html#fls_2dmeukyjqz9y", + "number": "5.4:5" + } + ], + "title": "Parenthesized Patterns" + }, + { + "id": "fls_uloyjbaso8pz", + "informational": false, + "link": "patterns.html#path-patterns", + "number": "5.5", + "paragraphs": [ { - "checksum": "d296305e4aa3dbcbb0a1effad25118ecc1395dc6b1b33bb86459f96b9531fd3e", - "id": "fls_msjo2zd67zn1", - "link": "types-and-traits.html#fls_msjo2zd67zn1", - "number": "4.3.1:34" + "checksum": "6eab6a03669622290fe084028190344d03230efeca455f6bd9e2891ac8781c05", + "id": "fls_1crq0mexo5r1", + "link": "patterns.html#fls_1crq0mexo5r1", + "number": "5.5:1" }, { - "checksum": "1b458f804645a74fb0e0cc1aa355732c67172fd9e75c6ea28177303787c8e662", - "id": "fls_w1oti03tm1y6", - "link": "types-and-traits.html#fls_w1oti03tm1y6", - "number": "4.3.1:35" + "checksum": "6b5457b32a2fa8723c542bd4b63d6de477dfc4e8406457a6b0ec05a421fbb295", + "id": "fls_xz5otkhogn31", + "link": "patterns.html#fls_xz5otkhogn31", + "number": "5.5:2" }, { - "checksum": "a86bf07935fb2c1b2bb4874fe3bcea6b86ba3cf8ed709399288fd302b122b3ef", - "id": "fls_9gqd7eevbknt", - "link": "types-and-traits.html#fls_9gqd7eevbknt", - "number": "4.3.1:36" + "checksum": "853158ce8e75116be9bf2d63bc0d49626010b9d0fa0c5ebe80a595ef4da37aa3", + "id": "fls_t8sjzsif2ilf", + "link": "patterns.html#fls_t8sjzsif2ilf", + "number": "5.5:3" }, { - "checksum": "a6a7931eaf2965dfdac9021f4d8a09873dfdc6abf5f148f661c3ec557f154dcd", - "id": "fls_r4o2rmhqg4br", - "link": "types-and-traits.html#fls_r4o2rmhqg4br", - "number": "4.3.1:37" + "checksum": "32107632e6d0b56902397df91b587c925c1cf6ce0a1f364dd9a3708ce1e015f6", + "id": "fls_zCswsyuitexI", + "link": "patterns.html#fls_zCswsyuitexI", + "number": "5.5:4" }, { - "checksum": "b64f4f41ff39543e9a12b4c7c0a292db35c6c2a3b0ae33722cf7b5ec71852ce1", - "id": "fls_1n7p6ij1dpm", - "link": "types-and-traits.html#fls_1n7p6ij1dpm", - "number": "4.3.1:38" + "checksum": "a7a4f59c59d6e146496c10f6e1dd0db5adf148faf1242b54a905805871e1fefb", + "id": "fls_wJ9f906BlBvg", + "link": "patterns.html#fls_wJ9f906BlBvg", + "number": "5.5:5" }, { - "checksum": "ed886b0d040a0415b87015806dc7a838d193bb1e227659410f6314a41feff113", - "id": "fls_4x27kfiodb8", - "link": "types-and-traits.html#fls_4x27kfiodb8", - "number": "4.3.1:39" + "checksum": "c1c27a19deb008cded1ee11e11c906c699c4a1a086d847190fd58dcc97f4785c", + "id": "fls_hF19K8sWU8FF", + "link": "patterns.html#fls_hF19K8sWU8FF", + "number": "5.5:6" }, { - "checksum": "865ad088e423aa6b12fff0c2f48ac1e92557389ec93b6582e7643d28c1707ed5", - "id": "fls_me6bf9m2ypt", - "link": "types-and-traits.html#fls_me6bf9m2ypt", - "number": "4.3.1:40" + "checksum": "e3791cb88451803cd0eccbbf5f370fc3c972abe87f321825dc692aacb617742b", + "id": "fls_bv9psmitxfuw", + "link": "patterns.html#fls_bv9psmitxfuw", + "number": "5.5:7" }, { - "checksum": "24889b443c8a3583df4a999169a461d56a81737c8776d003ea3e7968c2670737", - "id": "fls_2j659ns8wop4", - "link": "types-and-traits.html#fls_2j659ns8wop4", - "number": "4.3.1:41" + "checksum": "7f7d5841b9bc8e1f3eca2f3f52fb1c78bb016e93d8212e3225a673fd4bc4b053", + "id": "fls_sl47k9oj5p7t", + "link": "patterns.html#fls_sl47k9oj5p7t", + "number": "5.5:8" }, { - "checksum": "7b78ae0a4fa328edf20bd1ee52ca050c8a7913beeafc6215a0e8f6cbbab2a236", - "id": "fls_d09l2rl0161l", - "link": "types-and-traits.html#fls_d09l2rl0161l", - "number": "4.3.1:42" + "checksum": "e7dd58da27f566ab9464de185e98749b36b5c3340a8c22fed6be8e199d11dd91", + "id": "fls_cfoy86mkmqa4", + "link": "patterns.html#fls_cfoy86mkmqa4", + "number": "5.5:9" }, { - "checksum": "938539d9e20602a05fe699126091fadcead0ba21c69cbe4a271eb3dc3c3ed250", - "id": "fls_2sd39mj05mb9", - "link": "types-and-traits.html#fls_2sd39mj05mb9", - "number": "4.3.1:43" - } - ], - "title": "Bool Type" - }, - { - "id": "fls_wrvjizrqf3po", - "informational": false, - "link": "types-and-traits.html#char-type", - "number": "4.3.2", - "paragraphs": [ - { - "checksum": "de3e486bd6bfec35ccbc18c244a07b0400fb7afad866d64ad99471d9b5fb3d51", - "id": "fls_vnwbs0exbwcn", - "link": "types-and-traits.html#fls_vnwbs0exbwcn", - "number": "4.3.2:1" + "checksum": "2a1df52ca100bd83979fa4b150b96d836a748c8114e32462390076966acbb005", + "id": "fls_rnppz6y5z8pi", + "link": "patterns.html#fls_rnppz6y5z8pi", + "number": "5.5:10" }, { - "checksum": "f9760034f4a5a74b8badb11d87b6f5ef432d7e91e80802425e89bbbbe5a20476", - "id": "fls_juysxea25owj", - "link": "types-and-traits.html#fls_juysxea25owj", - "number": "4.3.2:2" - } - ], - "title": "Char Type" - }, - { - "id": "fls_qwljwqr07slp", - "informational": false, - "link": "types-and-traits.html#numeric-types", - "number": "4.3.3", - "paragraphs": [], - "title": "Numeric Types" - }, - { - "id": "fls_b4xporvr64s", - "informational": false, - "link": "types-and-traits.html#floating-point-types", - "number": "4.3.3.1", - "paragraphs": [ - { - "checksum": "9f2ef3098e38ced762fc72ca6a4bc1dec698a33084b9c778e96312d3ffe46bf1", - "id": "fls_30yny2xb9b6b", - "link": "types-and-traits.html#fls_30yny2xb9b6b", - "number": "4.3.3.1:1" + "checksum": "686e4dfe42f5f913ee2465c883ade027d7e59e9e065e490993681639adbc6114", + "id": "fls_ag6m4mvpturw", + "link": "patterns.html#fls_ag6m4mvpturw", + "number": "5.5:11" }, { - "checksum": "cec08d9131666b39b8d3688a587aa88db5e36d49b4c55c6fa925a6d90e9338b8", - "id": "fls_yqflrq9s6p6n", - "link": "types-and-traits.html#fls_yqflrq9s6p6n", - "number": "4.3.3.1:2" + "checksum": "0156b2335414d9b4db2b75d75ed55fbca446bd1b5b81604e0171ea7872fd44a2", + "id": "fls_pedy2pqrvnx7", + "link": "patterns.html#fls_pedy2pqrvnx7", + "number": "5.5:12" }, { - "checksum": "f9a65c44f2a9e6e74c497676bea2fa3f5c8baa37891736f84802dbe50e0758d1", - "id": "fls_nuFAwLHOdQBx", - "link": "types-and-traits.html#fls_nuFAwLHOdQBx", - "number": "4.3.3.1:3" + "checksum": "546530c46d84a01d6017493ea2f9e61645c557cd4ca99d9d518a6145f9ec0367", + "id": "fls_u59rilepu8z9", + "link": "patterns.html#fls_u59rilepu8z9", + "number": "5.5:13" } ], - "title": "Floating Point Types" + "title": "Path Patterns" }, { - "id": "fls_3qnpv2z7yjil", + "id": "fls_6tl1fx99yn6c", "informational": false, - "link": "types-and-traits.html#integer-types", - "number": "4.3.3.2", + "link": "patterns.html#range-patterns", + "number": "5.6", "paragraphs": [ { - "checksum": "c91bb9a77a65e19d420bace1cde89e8b2bd3a6b0a5554c2e2d6c5d4b2b8a6a45", - "id": "fls_cokwseo3nnr", - "link": "types-and-traits.html#fls_cokwseo3nnr", - "number": "4.3.3.2:1" + "checksum": "959fe2ad28f3934a1ce306646ec29b82f95de36f84a5c69840957aeac1b88eb9", + "id": "fls_okupyoav13rm", + "link": "patterns.html#fls_okupyoav13rm", + "number": "5.6:1" }, { - "checksum": "49357a257118ef34e9a0f670c27a41b0d29de7f40242b56aed6fcbd777d0759b", - "id": "fls_vk1skn6ek36u", - "link": "types-and-traits.html#fls_vk1skn6ek36u", - "number": "4.3.3.2:2" + "checksum": "0b382770b9780b9239f762eb87f95835b03588805b1a4b3def28336d666ff7a1", + "id": "fls_jhchm7dy927k", + "link": "patterns.html#fls_jhchm7dy927k", + "number": "5.6:2" }, { - "checksum": "78b32c0312f4dd50cbc257f858487dcb75f68d6bdf006abab97290c56ce8d417", - "id": "fls_iikexw8ps6mk", - "link": "types-and-traits.html#fls_iikexw8ps6mk", - "number": "4.3.3.2:3" + "checksum": "1cfc9d659ff30640a5179ad40746d5c08c3c187b5271f28b88305b4de6ba138a", + "id": "fls_q86j23iiqv8w", + "link": "patterns.html#fls_q86j23iiqv8w", + "number": "5.6:3" }, { - "checksum": "b24ffd36ee78bac00ba6eef084b0bbaf5b5915c937f0c8172e3068776977c8c1", - "id": "fls_cavasxxlgs7g", - "link": "types-and-traits.html#fls_cavasxxlgs7g", - "number": "4.3.3.2:4" + "checksum": "b49f2f20e78bc39e912613682452652bf00d8a9b7ad85fcbbeef325cc638e298", + "id": "fls_3PyquOKjA7SI", + "link": "patterns.html#fls_3PyquOKjA7SI", + "number": "5.6:4" }, { - "checksum": "9ad8b2b92eaa9597eb9af4e33da92fe59cbffaa97c890e6086435f557e43fb50", - "id": "fls_7sx92xsjx3pl", - "link": "types-and-traits.html#fls_7sx92xsjx3pl", - "number": "4.3.3.2:5" + "checksum": "2730f4be7e58310acdcc2e8df0d5792b76b2437850f3bea63f0e94ad25e46eee", + "id": "fls_akf9x5r6e0ta", + "link": "patterns.html#fls_akf9x5r6e0ta", + "number": "5.6:5" }, { - "checksum": "b1e3f5d5efc7e3aacb83d9a6a8f313f9e28d458979266baee253365b8454376a", - "id": "fls_q9f95uet7gq4", - "link": "types-and-traits.html#fls_q9f95uet7gq4", - "number": "4.3.3.2:6" + "checksum": "0ba8c1ec907ade4c15c689e735f0a5a8b187b5496f656c893968eca7e5468b54", + "id": "fls_vrpr6ttpfpal", + "link": "patterns.html#fls_vrpr6ttpfpal", + "number": "5.6:6" }, { - "checksum": "a9f676afe50447b4889e6b3ba228a3314e819d297389ab57b60c289c43192d76", - "id": "fls_yjb3kzijd19v", - "link": "types-and-traits.html#fls_yjb3kzijd19v", - "number": "4.3.3.2:7" + "checksum": "656232ce77042076d43593f3f21939aac2c7066d0c750836345ff9ec0a42124b", + "id": "fls_nk48gregn3me", + "link": "patterns.html#fls_nk48gregn3me", + "number": "5.6:7" }, { - "checksum": "278b33ab9e405eba53e40103ea42d106398186c9d15ba8ee1995c7a728b563d3", - "id": "fls_75lntwhg20l", - "link": "types-and-traits.html#fls_75lntwhg20l", - "number": "4.3.3.2:8" + "checksum": "6316acea87cf2eeeb7b2e0acb3d99f9f56716dc64ba70e167716659033b9bde4", + "id": "fls_83v1xqbebs58", + "link": "patterns.html#fls_83v1xqbebs58", + "number": "5.6:8" }, { - "checksum": "9dec417eca723a851afba4b1fbf40615b81b0b680eaadd85d45cecd136fda1a3", - "id": "fls_p2shoji3xg5a", - "link": "types-and-traits.html#fls_p2shoji3xg5a", - "number": "4.3.3.2:9" + "checksum": "785db35c5291babacd0f8648588325297f8f45c2eebe7fb665e52b8ac76eca68", + "id": "fls_2hpuccwh2xml", + "link": "patterns.html#fls_2hpuccwh2xml", + "number": "5.6:9" }, { - "checksum": "b4b087216df02d090a94bd841759e6bcd8283ef3c137cfe09f138ae17ddd5071", - "id": "fls_fsyt05u9y4sl", - "link": "types-and-traits.html#fls_fsyt05u9y4sl", - "number": "4.3.3.2:10" + "checksum": "0c9d1f2587c2e5d8ed9ee166a68762909b9b7936245dc9d2341c1f7df6670e6e", + "id": "fls_9kk81isk0mlp", + "link": "patterns.html#fls_9kk81isk0mlp", + "number": "5.6:10" }, { - "checksum": "f3bf5c08e43865c62017a10e1b7306084527a18b37767d4c100314a6e0c1c2f6", - "id": "fls_p9ffvtajr832", - "link": "types-and-traits.html#fls_p9ffvtajr832", - "number": "4.3.3.2:11" + "checksum": "7254a7b52add01e9d7d977900c9322544a9970def2f7c73fdb9a63b417574f5c", + "id": "fls_8bdOqkO1NuJW", + "link": "patterns.html#fls_8bdOqkO1NuJW", + "number": "5.6:11" }, { - "checksum": "ec17bdab7274d1767c59acd310b20c0c01f2da8a8b7716c404d17f1cc07484ba", - "id": "fls_j6xan9f8udw7", - "link": "types-and-traits.html#fls_j6xan9f8udw7", - "number": "4.3.3.2:12" + "checksum": "88a5cfae2ddc1c38d1df4a2dc1b86e99bcced79bc5b40afef543633e7c48bce9", + "id": "fls_s2b5n4snc4d7", + "link": "patterns.html#fls_s2b5n4snc4d7", + "number": "5.6:12" }, { - "checksum": "fd5107a41862e15e8bf69245f1ca0a14bd39123490c3f4b6af8e4a980f4ec4ea", - "id": "fls_4t39p3ibkzu7", - "link": "types-and-traits.html#fls_4t39p3ibkzu7", - "number": "4.3.3.2:13" + "checksum": "b55a395def7ce875a26f0fe8a3f186342864481fa72edba9c13c5d5f3f93c9e6", + "id": "fls_4o4ge6x9a8rs", + "link": "patterns.html#fls_4o4ge6x9a8rs", + "number": "5.6:13" }, { - "checksum": "3c6952dc4550a4388ae5861a5f0f6e10a791a23bc1f697d783dbac6b3281a6ec", - "id": "fls_egfoxke0lzje", - "link": "types-and-traits.html#fls_egfoxke0lzje", - "number": "4.3.3.2:14" + "checksum": "fe437f18136d4053707f9c2e3b8a79e527d78c77863abc6bfd04f4a73204d3b5", + "id": "fls_6o995ak4hywq", + "link": "patterns.html#fls_6o995ak4hywq", + "number": "5.6:14" }, { - "checksum": "27d4b5e93c8a23fea7497ff03639ed73f72387be79afe5bb2ef004898a152b78", - "id": "fls_4c4qpel1tbqs", - "link": "types-and-traits.html#fls_4c4qpel1tbqs", - "number": "4.3.3.2:15" + "checksum": "5d22fc340f1c7e324df48a970cf5ae8a4811f8a5ad3880704eda257dd3d2add8", + "id": "fls_3js1645tgh31", + "link": "patterns.html#fls_3js1645tgh31", + "number": "5.6:15" }, { - "checksum": "adc4ea56adb80d9c82a72320b251471c3e69a20eb1fca729a0ba24c65f115ede", - "id": "fls_t9oyfmgqka6u", - "link": "types-and-traits.html#fls_t9oyfmgqka6u", - "number": "4.3.3.2:16" - } - ], - "title": "Integer Types" - }, - { - "id": "fls_fbchw64p6n2x", - "informational": false, - "link": "types-and-traits.html#sequence-types", - "number": "4.4", - "paragraphs": [], - "title": "Sequence Types" - }, - { - "id": "fls_uj0kpjwyld60", - "informational": false, - "link": "types-and-traits.html#array-types", - "number": "4.4.1", - "paragraphs": [ - { - "checksum": "a0e28c81206aaca44d668b56c00cdb06b2afcb1957637817f895be1919625591", - "id": "fls_fx7b3qv3ghca", - "link": "types-and-traits.html#fls_fx7b3qv3ghca", - "number": "4.4.1:1" + "checksum": "d004b00ad56daa028bd112c7ad04042c90cfb4ab5ad4df095a5a9d01f32c4bba", + "id": "fls_8Q6NfRx4j5V7", + "link": "patterns.html#fls_8Q6NfRx4j5V7", + "number": "5.6:16" }, { - "checksum": "3842e56226791bfaa21a704608441353c9476f08a8ad83197c785ec4b705b6f6", - "id": "fls_pkts1p2dnxo", - "link": "types-and-traits.html#fls_pkts1p2dnxo", - "number": "4.4.1:2" + "checksum": "4993d699bb304cc5b32245e03cfd7425d322876b5dc54fef12048c5874c667c0", + "id": "fls_rgr7t33s0m7m", + "link": "patterns.html#fls_rgr7t33s0m7m", + "number": "5.6:17" }, { - "checksum": "11446d2cbdde9d3d9a2a4c6206fbe529ac4257c999d03b3d6cddd6fbae17eefd", - "id": "fls_imr2jx6cbuzq", - "link": "types-and-traits.html#fls_imr2jx6cbuzq", - "number": "4.4.1:3" + "checksum": "0315d5b53fda43c91943de2a08eda96b79f469004237b8d0f02f52f4c1f35567", + "id": "fls_5ey5mj8t8knd", + "link": "patterns.html#fls_5ey5mj8t8knd", + "number": "5.6:18" }, { - "checksum": "101671326f28fb3bee73ae610842cfdd0b7e1f09d04855a7041d29d72688b48d", - "id": "fls_r8nqxry2dlww", - "link": "types-and-traits.html#fls_r8nqxry2dlww", - "number": "4.4.1:4" + "checksum": "9f6d9020a6778f7a34629150159b8a67764f0aea6638d4f0d47a20fc22fbe70e", + "id": "fls_z4js96mchcsv", + "link": "patterns.html#fls_z4js96mchcsv", + "number": "5.6:19" }, { - "checksum": "c944934ba19e6c4ebe12cd9453b9f0747ab302ecb654be487cbd6b29567cefc9", - "id": "fls_9vjijqi9w8wn", - "link": "types-and-traits.html#fls_9vjijqi9w8wn", - "number": "4.4.1:5" + "checksum": "5dc529cd76b4f3bba978dfd0fcaf3a618fcaf8f381f22c9f5d63d39585ed34fa", + "id": "fls_3wwpq8i6mo2a", + "link": "patterns.html#fls_3wwpq8i6mo2a", + "number": "5.6:20" } ], - "title": "Array Types" + "title": "Range Patterns" }, { - "id": "fls_vpbikb73dw4k", + "id": "fls_d2sc9hl3v0mk", "informational": false, - "link": "types-and-traits.html#slice-types", - "number": "4.4.2", + "link": "patterns.html#reference-patterns", + "number": "5.7", "paragraphs": [ { - "checksum": "1dec4d96c2383bcd59d32a561f257ee89281b2f6079093fa3efe7c807374bfdc", - "id": "fls_ftvua2hlvr08", - "link": "types-and-traits.html#fls_ftvua2hlvr08", - "number": "4.4.2:1" + "checksum": "0ac86e5a12c490f5eb80008b0e18cc9da39af2074dfbdc99545777192814afa3", + "id": "fls_fhahcc1mz2qh", + "link": "patterns.html#fls_fhahcc1mz2qh", + "number": "5.7:1" }, { - "checksum": "c1765e314370989e27d28ca48d5189af4457e08b3395ee284192a5060a4dd3b7", - "id": "fls_acgtczhk8ci0", - "link": "types-and-traits.html#fls_acgtczhk8ci0", - "number": "4.4.2:2" + "checksum": "028c5b3284521a34ff97d008866bec2c4654a464089a7da864aad3e4a4f04686", + "id": "fls_x0bmzl1315gq", + "link": "patterns.html#fls_x0bmzl1315gq", + "number": "5.7:2" }, { - "checksum": "fa8054851ecf8587aa441b48e5bc8cc1e660b60f0c0542b45df2390c5f7fcea4", - "id": "fls_5gl67ftc3m21", - "link": "types-and-traits.html#fls_5gl67ftc3m21", - "number": "4.4.2:3" + "checksum": "cdf387e98a664c3724ff90541e30f67670be603da49923fcb4e3a4ee54ba432e", + "id": "fls_fedo8zhgpla5", + "link": "patterns.html#fls_fedo8zhgpla5", + "number": "5.7:3" }, { - "checksum": "12398a33d4062f70cdd542b837f7158f3015de99e28242ccb150779a45e440da", - "id": "fls_nsny832ap4v1", - "link": "types-and-traits.html#fls_nsny832ap4v1", - "number": "4.4.2:4" + "checksum": "b78639be70162116c61abcb66a81cfa0e2adb505b2a71ffb60f9639cf7a038d5", + "id": "fls_30u9ij164ww3", + "link": "patterns.html#fls_30u9ij164ww3", + "number": "5.7:4" + }, + { + "checksum": "cc0ee91216508f36e23696e7ee09cd4fa77b065e4c323a2cf3610c891336fe3f", + "id": "fls_d1kc73hpncpo", + "link": "patterns.html#fls_d1kc73hpncpo", + "number": "5.7:5" + }, + { + "checksum": "f829652e4b19ceb3e612e92c40aa060fe77b46424b5fc3858eccbcfcea45016c", + "id": "fls_mpeuhov0umfa", + "link": "patterns.html#fls_mpeuhov0umfa", + "number": "5.7:6" } ], - "title": "Slice Types" + "title": "Reference Patterns" }, { - "id": "fls_4agmmu5al6gt", + "id": "fls_7wpgnp4kjq82", "informational": false, - "link": "types-and-traits.html#str-type", - "number": "4.4.3", + "link": "patterns.html#rest-patterns", + "number": "5.8", "paragraphs": [ { - "checksum": "4741170fc77c3caa889d2b61c37ca8bf858103e8dfb471f5259bf3977733f2d3", - "id": "fls_wlnoq1qoq2kr", - "link": "types-and-traits.html#fls_wlnoq1qoq2kr", - "number": "4.4.3:1" + "checksum": "071b1b905e799a04118d9b0fe22e82b9944ef00a527e80a327c0c4f6511fd52b", + "id": "fls_eso51epfofxb", + "link": "patterns.html#fls_eso51epfofxb", + "number": "5.8:1" }, { - "checksum": "b76cf1e29c346895c7a64d1ebecd1285879329d64c6a566ec2b62a40874a44bf", - "id": "fls_1xa6fas6laha", - "link": "types-and-traits.html#fls_1xa6fas6laha", - "number": "4.4.3:2" + "checksum": "d787720d08f2fd60c09276b2b5be62b0bd3a0ad5aef799a71bacba9691897fda", + "id": "fls_5a75a2y43uev", + "link": "patterns.html#fls_5a75a2y43uev", + "number": "5.8:2" }, { - "checksum": "c7d00bb480fddeed611bf8a4b29accfc6ba24e9843f57af35f84b92b3d91af55", - "id": "fls_yu7r2077n9m7", - "link": "types-and-traits.html#fls_yu7r2077n9m7", - "number": "4.4.3:3" + "checksum": "3bf650bd8078af8405ff969b3616899e1bc80ef2ef591d2b291cf99f03008f32", + "id": "fls_rsqyza99vl3x", + "link": "patterns.html#fls_rsqyza99vl3x", + "number": "5.8:3" }, { - "checksum": "31fd188232e88dbd814ef19206f91c486077f1d7d40d1682c48152055d1131ee", - "id": "fls_wacoqrtzvrwu", - "link": "types-and-traits.html#fls_wacoqrtzvrwu", - "number": "4.4.3:4" - } - ], - "title": "Str Type" - }, - { - "id": "fls_4ckl3n2ko3i4", - "informational": false, - "link": "types-and-traits.html#tuple-types", - "number": "4.4.4", - "paragraphs": [ - { - "checksum": "0e048e80d2703d29c830283c91c9363981bcaef3c5713dc5917214cdc99e0a32", - "id": "fls_bn7wmf681ngt", - "link": "types-and-traits.html#fls_bn7wmf681ngt", - "number": "4.4.4:1" + "checksum": "11486a40e40c1eaa6529d06f4f52771cd804d9d0cea29d215e5e9e6748877b01", + "id": "fls_w1pw40phsv2o", + "link": "patterns.html#fls_w1pw40phsv2o", + "number": "5.8:4" }, { - "checksum": "f90d6cbe2f05a1690b19b882378e0c5577f7f70fd8a6b6c0a9ec01d3c19d14b2", - "id": "fls_s9a36zsrfqew", - "link": "types-and-traits.html#fls_s9a36zsrfqew", - "number": "4.4.4:2" - } - ], - "title": "Tuple Types" - }, - { - "id": "fls_wdec78luqh5b", - "informational": false, - "link": "types-and-traits.html#abstract-data-types", - "number": "4.5", - "paragraphs": [], - "title": "Abstract Data Types" - }, - { - "id": "fls_szibmtfv117b", - "informational": false, - "link": "types-and-traits.html#enum-types", - "number": "4.5.1", - "paragraphs": [ - { - "checksum": "41a04bcc279a52fe4a0d644f08c7974945044713d151a621e622736caa79f834", - "id": "fls_gbdd37seqoab", - "link": "types-and-traits.html#fls_gbdd37seqoab", - "number": "4.5.1:1" + "checksum": "318a513cb1fdf4e37047eccc4975636818c7cd86126e00b5b3e132411c629003", + "id": "fls_x8ylgxrf9ca", + "link": "patterns.html#fls_x8ylgxrf9ca", + "number": "5.8:5" }, { - "checksum": "6ec5fbcdfa7bc0e2e0fa597521d52d44f4cdb850f944944654faecd563781897", - "id": "fls_il9a1olqmu38", - "link": "types-and-traits.html#fls_il9a1olqmu38", - "number": "4.5.1:2" + "checksum": "de50b523931b5b23b262977da389190179884668be06d4b514d2908f9ad2aeab", + "id": "fls_zgoke73xrhk3", + "link": "patterns.html#fls_zgoke73xrhk3", + "number": "5.8:6" }, { - "checksum": "b933c4ceea18830211cb015864f5ffc6aa7466256320ccd3eaefe96a2ad46495", - "id": "fls_wQTFwl88VujQ", - "link": "types-and-traits.html#fls_wQTFwl88VujQ", - "number": "4.5.1:3" + "checksum": "3e6c88357b38cf981ef309c03b2df1cb25d9f796365ca21fbbd67f2d4571cd42", + "id": "fls_bdcv6rwx0fsv", + "link": "patterns.html#fls_bdcv6rwx0fsv", + "number": "5.8:7" }, { - "checksum": "8fadd9f05034847f538c7ea8c1f61165df9bb4c87e711d4b4a4cfc7ea2d2aa76", - "id": "fls_g5qle7xzaoif", - "link": "types-and-traits.html#fls_g5qle7xzaoif", - "number": "4.5.1:4" - }, + "checksum": "c6203fbb28f63565945031f16bc7f7096f16bee4c02f2c16161cb0c3cf80c7a5", + "id": "fls_qz9guhlg19j3", + "link": "patterns.html#fls_qz9guhlg19j3", + "number": "5.8:8" + } + ], + "title": "Rest Patterns" + }, + { + "id": "fls_qte70mgzpras", + "informational": false, + "link": "patterns.html#slice-patterns", + "number": "5.9", + "paragraphs": [ { - "checksum": "9018d5bb55a9ad10867addcd1bc986fa5e8cd4c111f3cb348c42f11f7343d886", - "id": "fls_t4yeovFm83Wo", - "link": "types-and-traits.html#fls_t4yeovFm83Wo", - "number": "4.5.1:5" + "checksum": "f9dbdad2eec41e1e1c60cd8c0cacf14973b904c94631089f41ca0e1bbda98a17", + "id": "fls_qqiu594hki8g", + "link": "patterns.html#fls_qqiu594hki8g", + "number": "5.9:1" }, { - "checksum": "dbf85d1dbaa9e65e9ee411a69098c8ff2c3a6b0a9372e68ed8ea0f1f7e0e87bc", - "id": "fls_hp5frc752dam", - "link": "types-and-traits.html#fls_hp5frc752dam", - "number": "4.5.1:6" + "checksum": "948b35cfd7619032dc69a5d84fa17b28a615e792a6f0e0d49e140816b136e7e9", + "id": "fls_h6x9xlxi7y5n", + "link": "patterns.html#fls_h6x9xlxi7y5n", + "number": "5.9:2" }, { - "checksum": "d3209c56e335f945e3a200cfdae47cd6e4386e6a13052c36cd96cb27f665ee36", - "id": "fls_pijczoq4k9ij", - "link": "types-and-traits.html#fls_pijczoq4k9ij", - "number": "4.5.1:7" + "checksum": "b21f2e9bea39902d97b0a75bd48f584b5bcb685635cecfeab096b27a096de184", + "id": "fls_jbmxu7y5fnm6", + "link": "patterns.html#fls_jbmxu7y5fnm6", + "number": "5.9:3" }, { - "checksum": "2a4978216847213cc9cc89c486697babf78f3a02b2df4a408a19dc42fcd37c62", - "id": "fls_x7nh42on06bg", - "link": "types-and-traits.html#fls_x7nh42on06bg", - "number": "4.5.1:8" + "checksum": "9ea93d82c4cebaa6550a0787f2925118cf5370ed8a0c02104b3a623c197ae9c1", + "id": "fls_r78zzw7yyg34", + "link": "patterns.html#fls_r78zzw7yyg34", + "number": "5.9:4" }, { - "checksum": "f7fc7e37e48e1d9beec7400be5000d3f97bbaf12e49da3f3db7b8478895128d1", - "id": "fls_duqbzvpuehvv", - "link": "types-and-traits.html#fls_duqbzvpuehvv", - "number": "4.5.1:9" + "checksum": "93891609dbf9042a20ed470adda1893453d146707a716c6a05793ff9c64e10f0", + "id": "fls_ndor56nou676", + "link": "patterns.html#fls_ndor56nou676", + "number": "5.9:5" }, { - "checksum": "9276d9aa4b9d2a7a1fb0208d0f4b3b20336b00b99e8f85483048116ed6672925", - "id": "fls_ly183pj4fkgh", - "link": "types-and-traits.html#fls_ly183pj4fkgh", - "number": "4.5.1:10" + "checksum": "b7fb9cf107baa52a1c3140dbb24ee079813601a2c74f83321a61e14127737833", + "id": "fls_9yuobz1jsehf", + "link": "patterns.html#fls_9yuobz1jsehf", + "number": "5.9:6" + } + ], + "title": "Slice Patterns" + }, + { + "id": "fls_7dbd5t2750ce", + "informational": false, + "link": "patterns.html#struct-patterns", + "number": "5.10", + "paragraphs": [ + { + "checksum": "d667b4f41510c041296f0f8c9950b33a0e697269e268024bd87aac1d04c4b450", + "id": "fls_vjdkpr3zml51", + "link": "patterns.html#fls_vjdkpr3zml51", + "number": "5.10:1" }, { - "checksum": "5d6b1172331de835223d74f329d2a086a76f5d59d89900a06cb5022f9e99e1c2", - "id": "fls_w7sggezgq9o4", - "link": "types-and-traits.html#fls_w7sggezgq9o4", - "number": "4.5.1:11" + "checksum": "1226159f94faa8a70839d078914f48a7ec073bb47550a83a77554fab5106550c", + "id": "fls_6o3x101wo478", + "link": "patterns.html#fls_6o3x101wo478", + "number": "5.10:2" }, { - "checksum": "864ba312cb813aaa2c285d358764732ca53afa7b59f44b4a26e959f8ed5d1906", - "id": "fls_93l5o6qar5p2", - "link": "types-and-traits.html#fls_93l5o6qar5p2", - "number": "4.5.1:12" + "checksum": "4951a056adbfabc5da1900d164468c7f4be7716b2094657ab9e28e53f3b985a9", + "id": "fls_k9zih9s0oe5h", + "link": "patterns.html#fls_k9zih9s0oe5h", + "number": "5.10:3" }, { - "checksum": "c34f5ea6b85e16a716ab014d804766dbd0f681269e22f5fb113072b3aac7b40a", - "id": "fls_t36rk3wikq28", - "link": "types-and-traits.html#fls_t36rk3wikq28", - "number": "4.5.1:13" + "checksum": "b68597321297c9f745d6b9be53beda335702f6e28a1bb97a08c277dac931130d", + "id": "fls_r8rat3qmc4hy", + "link": "patterns.html#fls_r8rat3qmc4hy", + "number": "5.10:4" }, { - "checksum": "788a386db9bca5ece0658ad379be6e9720a7502dacc604b6b7199c62d0600a86", - "id": "fls_8ajw5trd23wi", - "link": "types-and-traits.html#fls_8ajw5trd23wi", - "number": "4.5.1:14" + "checksum": "c8b65f1ad8bbb7a147b1cfa9636cd2ebe6b61c1727898f08637df63d004a8391", + "id": "fls_hUX723DmLg2a", + "link": "patterns.html#fls_hUX723DmLg2a", + "number": "5.10:5" }, { - "checksum": "0e045bf46b8ce2319bc906617e1284401c1431e818f333adc0a52da4a692a36f", - "id": "fls_w9xj26ej869w", - "link": "types-and-traits.html#fls_w9xj26ej869w", - "number": "4.5.1:15" + "checksum": "83183cd23b19214bc0cd0c9da72221ac67d6ec71f26a49c5445307f86a2d7d8c", + "id": "fls_p4OplpUvS04l", + "link": "patterns.html#fls_p4OplpUvS04l", + "number": "5.10:6" }, { - "checksum": "b34358c9f5a38221a3128afbba7bcac38af1eb7ef2bfea390d1ddf04c523b76e", - "id": "fls_wqbuof7kxsrg", - "link": "types-and-traits.html#fls_wqbuof7kxsrg", - "number": "4.5.1:16" + "checksum": "0bdc00a13a57ef1c0b6611c62de6e170f3d281e81b68d9cf86a7b8f227180905", + "id": "fls_pre3YwAv01FE", + "link": "patterns.html#fls_pre3YwAv01FE", + "number": "5.10:7" }, { - "checksum": "36a62c297e5ac360801a96f184401b9074a30dbdd36cc171b0c48cbb24144c7d", - "id": "fls_f046du2fkgr6", - "link": "types-and-traits.html#fls_f046du2fkgr6", - "number": "4.5.1:17" + "checksum": "81f1732cfd416b1abb73fa22440e8d2cd8f4406e031d6f14d72610567aac5bca", + "id": "fls_MK83WE0iDqNf", + "link": "patterns.html#fls_MK83WE0iDqNf", + "number": "5.10:8" } ], - "title": "Enum Types" + "title": "Struct Patterns" }, { - "id": "fls_9ucqbbd0s2yo", + "id": "fls_nruvg0es3kx7", "informational": false, - "link": "types-and-traits.html#struct-types", - "number": "4.5.2", + "link": "patterns.html#record-struct-patterns", + "number": "5.10.1", "paragraphs": [ { - "checksum": "a39b4fadca9c6209a8eb00ee7b0ba2b4d03af79dbb8378379345c8836b5bc7a5", - "id": "fls_g1azfj548136", - "link": "types-and-traits.html#fls_g1azfj548136", - "number": "4.5.2:1" + "checksum": "939f66a2cc6a712f7ec20d7536b02ec2600f18a1799b2144aea19eec95022846", + "id": "fls_g6dytd6aq62d", + "link": "patterns.html#fls_g6dytd6aq62d", + "number": "5.10.1:1" }, { - "checksum": "99627103e41c406457ad5ca125407339483d91f6d8d598e7da9524f857d3c7c1", - "id": "fls_r885av95eivp", - "link": "types-and-traits.html#fls_r885av95eivp", - "number": "4.5.2:2" + "checksum": "62c9ac85366cbfb730c4705821715b95ce875f485a87b252fa3dd810128a62b6", + "id": "fls_3px4oiweg9dm", + "link": "patterns.html#fls_3px4oiweg9dm", + "number": "5.10.1:2" }, { - "checksum": "ddd7e64188789d0613207e30279466396e9387d4c837a9a700715c38aae1c3cb", - "id": "fls_auurdv1zvzb", - "link": "types-and-traits.html#fls_auurdv1zvzb", - "number": "4.5.2:3" + "checksum": "e1814d800e0feb6269bf895ed95022e6deb5cc33cc635c2bb63b14baec538159", + "id": "fls_mnh35ehva8tx", + "link": "patterns.html#fls_mnh35ehva8tx", + "number": "5.10.1:3" }, { - "checksum": "943e0f1a6572313a255e14bc95a0134edbbb021f031683bfb43b344ea0342979", - "id": "fls_vce7w0904du5", - "link": "types-and-traits.html#fls_vce7w0904du5", - "number": "4.5.2:4" - } - ], - "title": "Struct Types" - }, - { - "id": "fls_fmdn7n7s413d", - "informational": false, - "link": "types-and-traits.html#union-types", - "number": "4.5.3", - "paragraphs": [ + "checksum": "3e48a4f72d15cce2c39a5d3dc6ee479532f77da0b1bf93691b99fc840384f6fe", + "id": "fls_p2rjnlbvifaa", + "link": "patterns.html#fls_p2rjnlbvifaa", + "number": "5.10.1:4" + }, { - "checksum": "5367bfc0baaa05cd86579ae2623abf07a4ae259cb95582431afbf551136ca939", - "id": "fls_nskmnzq95yqm", - "link": "types-and-traits.html#fls_nskmnzq95yqm", - "number": "4.5.3:1" + "checksum": "6645895b56834ed4cc394b448f835a215f06b82857dcc694fa41f1413c2e8780", + "id": "fls_23be2x50at14", + "link": "patterns.html#fls_23be2x50at14", + "number": "5.10.1:5" }, { - "checksum": "2c5fb1623a74093762302c5a947e315ef8436798d09769a3a6d1f4eded4244dc", - "id": "fls_I5fN5Fmo5CyK", - "link": "types-and-traits.html#fls_I5fN5Fmo5CyK", - "number": "4.5.3:2" + "checksum": "a4aa45e4d07c9304ffcc9d862e913507d5000a1730b669278ac285dc603a771c", + "id": "fls_46u4ddj0yf93", + "link": "patterns.html#fls_46u4ddj0yf93", + "number": "5.10.1:6" }, { - "checksum": "92febc08b74e580a8eccc9d1233d135ee4e59041e9ad926ad857fd36d22ce44e", - "id": "fls_1caus8ybmfli", - "link": "types-and-traits.html#fls_1caus8ybmfli", - "number": "4.5.3:3" + "checksum": "7df489a39e2a1942c161d32a9ce2ef5ce0e4848eab53b9ff7321c1d546572c74", + "id": "fls_qu3dvfdq6oy7", + "link": "patterns.html#fls_qu3dvfdq6oy7", + "number": "5.10.1:7" }, { - "checksum": "ab71eea2a3c33b3222811c295c4dc83aba111daba6b56f8fe94c3669bbf808b7", - "id": "fls_ZJG2Q6lJYXhY", - "link": "types-and-traits.html#fls_ZJG2Q6lJYXhY", - "number": "4.5.3:4" + "checksum": "b07b1a9330e57dc967b57e196fbc8f1a408952860995a438d3d757900c0a9bb5", + "id": "fls_4b2hchdzv30u", + "link": "patterns.html#fls_4b2hchdzv30u", + "number": "5.10.1:8" }, { - "checksum": "0ce4ccb5dd76241dbc1faba186867478c49bd6a850f7c9a71d95a3a7a4e406ad", - "id": "fls_hLTnHnZuaHve", - "link": "types-and-traits.html#fls_hLTnHnZuaHve", - "number": "4.5.3:5" + "checksum": "a8132d829bf34f884995af4cb9ae77e246d14ffc39f464dfd0579841244ed1a7", + "id": "fls_9wfizujx0szd", + "link": "patterns.html#fls_9wfizujx0szd", + "number": "5.10.1:9" + }, + { + "checksum": "6e903cc0782eee63efea8b0c7d9bd83aaeca3c3659c72b4f1b64b24dbae63273", + "id": "fls_jTh9Hur0qsIb", + "link": "patterns.html#fls_jTh9Hur0qsIb", + "number": "5.10.1:10" + }, + { + "checksum": "c8519c37bf9a85c68075db3cb98c5ff8ab8db77174740ac05497368044b20534", + "id": "fls_as54u97xis8z", + "link": "patterns.html#fls_as54u97xis8z", + "number": "5.10.1:11" }, { - "checksum": "8837c04fd5dbb01d08026d2cafaaafd8ab2287f60e984d4fb1792425c0089812", - "id": "fls_JWgSckDtN13c", - "link": "types-and-traits.html#fls_JWgSckDtN13c", - "number": "4.5.3:6" + "checksum": "3515e0658c65c83d72e0dd7ad0e82d86216d338c31040d3e27832951a2aa83b5", + "id": "fls_8364ueejn5y3", + "link": "patterns.html#fls_8364ueejn5y3", + "number": "5.10.1:12" }, { - "checksum": "5959734aa5fddd303916db4c88648c4a0f7e216586b20e2ccc01f56a202afa58", - "id": "fls_sXZknxozJxtC", - "link": "types-and-traits.html#fls_sXZknxozJxtC", - "number": "4.5.3:7" + "checksum": "f951f3c203a03b558cf318fb35a3313fa155405830ed6d804fcdea0b828eb716", + "id": "fls_7t0be1w2hq3c", + "link": "patterns.html#fls_7t0be1w2hq3c", + "number": "5.10.1:13" }, { - "checksum": "f66f5248019636eefd0a3849d4710a419847eb4da4ffab3af715a08c5c7f4c02", - "id": "fls_vgNK01SXacnx", - "link": "types-and-traits.html#fls_vgNK01SXacnx", - "number": "4.5.3:8" + "checksum": "6103ce2aaa63552db1ddd1712d330355e209efd8b3f05e2ee118128d79c0f57a", + "id": "fls_3vgmkm2mzwwy", + "link": "patterns.html#fls_3vgmkm2mzwwy", + "number": "5.10.1:14" }, { - "checksum": "686f435b04adf578310fab3434b24706f77b75b4b7c9a189b81799cd033a4244", - "id": "fls_bQhh3zHAKjSu", - "link": "types-and-traits.html#fls_bQhh3zHAKjSu", - "number": "4.5.3:9" - } - ], - "title": "Union Types" - }, - { - "id": "fls_hbbek3z4wtcs", - "informational": false, - "link": "types-and-traits.html#function-types", - "number": "4.6", - "paragraphs": [], - "title": "Function Types" - }, - { - "id": "fls_xd2oxlebhs14", - "informational": false, - "link": "types-and-traits.html#closure-types", - "number": "4.6.1", - "paragraphs": [ - { - "checksum": "a13570cc647e2fe3f7daa098e9b82168dd6556c25ddc1bfb7dc12f25e9eae54e", - "id": "fls_bsykgnbatpmi", - "link": "types-and-traits.html#fls_bsykgnbatpmi", - "number": "4.6.1:1" + "checksum": "b94ca5c69e41ecdd7da89cf82764d0e4561528548bd0a3491afd0dcc1dc27452", + "id": "fls_m91ith3rjy79", + "link": "patterns.html#fls_m91ith3rjy79", + "number": "5.10.1:15" }, { - "checksum": "94c7fbe22c296aee3cf9ce06a1da0aac2dca927a95d394cc715b6fe61265f90e", - "id": "fls_zfj4l8bigdg0", - "link": "types-and-traits.html#fls_zfj4l8bigdg0", - "number": "4.6.1:2" + "checksum": "b71b81a6c560dbce28bfa29908e6476ea8fd75a9b27df354ac6d310149124194", + "id": "fls_c09jf2vpcr58", + "link": "patterns.html#fls_c09jf2vpcr58", + "number": "5.10.1:16" }, { - "checksum": "6509cd4ac82ace29c2f9a29046a8fc164311d5de6ffc2956bb878dbbe701c889", - "id": "fls_bn0ueivujnqk", - "link": "types-and-traits.html#fls_bn0ueivujnqk", - "number": "4.6.1:3" + "checksum": "cb358844aa1dd3a9c5395b092c62c4e62c28a58e0b909d2c9d99344e409fafac", + "id": "fls_4h00oqypa8qg", + "link": "patterns.html#fls_4h00oqypa8qg", + "number": "5.10.1:17" }, { - "checksum": "494dee99ed22dbebd0d0ac76a1fa787b15f999f68855fefcdb9cd603d22489da", - "id": "fls_u01kt5glbuz8", - "link": "types-and-traits.html#fls_u01kt5glbuz8", - "number": "4.6.1:4" + "checksum": "2406df736b1c1b7e052beb8d6a704041b3804de21cf83ac7e64bb84ea5c0180f", + "id": "fls_195mqijyrnam", + "link": "patterns.html#fls_195mqijyrnam", + "number": "5.10.1:18" }, { - "checksum": "2a45235137530e81bb8f2281cf9deca9542bed2f95dd65858edb54f18e243164", - "id": "fls_3jeootwe6ucu", - "link": "types-and-traits.html#fls_3jeootwe6ucu", - "number": "4.6.1:5" + "checksum": "685de37c2ee81a66dccabb7f81942c82479d896f00e1e1334d4a60a9ea3b6bdc", + "id": "fls_ta0vdoqmt2k1", + "link": "patterns.html#fls_ta0vdoqmt2k1", + "number": "5.10.1:19" }, { - "checksum": "3bd0fbff2aa919bc05fdb9a7a301baf9be62bfb0c6ebf65df45c88713582d37f", - "id": "fls_63jqtyw0rz8c", - "link": "types-and-traits.html#fls_63jqtyw0rz8c", - "number": "4.6.1:6" + "checksum": "c02d71a02dfad46132450d2be620a7cabbd2eabf47c6bd9b01945121cd5d3aa0", + "id": "fls_f0u0j4q90lpl", + "link": "patterns.html#fls_f0u0j4q90lpl", + "number": "5.10.1:20" }, { - "checksum": "b0e41c86f561de826ef136c25682433aef7f5636c0352e3d8756137e1446ab27", - "id": "fls_3c4g9njja5s5", - "link": "types-and-traits.html#fls_3c4g9njja5s5", - "number": "4.6.1:7" + "checksum": "b0f4dad87afccd06af733d465607f36dcde450f57438e710856d594b3dfb6e8d", + "id": "fls_8bi8q3usubby", + "link": "patterns.html#fls_8bi8q3usubby", + "number": "5.10.1:21" }, { - "checksum": "db27fe1b97d6577e59cc0f00cdd1fab1ddfa6645da18b3a83e52010a01ff8caa", - "id": "fls_2nuhy0ujgq18", - "link": "types-and-traits.html#fls_2nuhy0ujgq18", - "number": "4.6.1:8" + "checksum": "da482f0aac1389101e1acc7d29df971ea52cbb5a914a549589717f367179ec59", + "id": "fls_1x0o71kxj3yq", + "link": "patterns.html#fls_1x0o71kxj3yq", + "number": "5.10.1:22" }, { - "checksum": "929a4c4a21afe2d35b50abb7d7250277785667e33d2692b7c52fa2b6eecd4608", - "id": "fls_5jh07heok8sy", - "link": "types-and-traits.html#fls_5jh07heok8sy", - "number": "4.6.1:9" - } - ], - "title": "Closure Types" - }, - { - "id": "fls_airvr79xkcag", - "informational": false, - "link": "types-and-traits.html#function-item-types", - "number": "4.6.2", - "paragraphs": [ + "checksum": "d75c1d570151a0c0add5bd9dbd8879ab364860d21ddd5a1981f20b5a358d8ac6", + "id": "fls_1thgpx95lfg5", + "link": "patterns.html#fls_1thgpx95lfg5", + "number": "5.10.1:23" + }, { - "checksum": "ba8be95a7ac75beeb55990710d7b232e90aaefa46fbc965fc5ee97ff250687b1", - "id": "fls_t24iojx7yc23", - "link": "types-and-traits.html#fls_t24iojx7yc23", - "number": "4.6.2:1" + "checksum": "014416678ebb189d6688fb5242e5205e11770c286ca59a48f79dda7f6ae4a8b1", + "id": "fls_rpo1wimbmzhc", + "link": "patterns.html#fls_rpo1wimbmzhc", + "number": "5.10.1:24" }, { - "checksum": "6e4e49c145170caf512a89a36aab453064036bab3e47a5b7551d769810a454a8", - "id": "fls_sas3ahcshnrh", - "link": "types-and-traits.html#fls_sas3ahcshnrh", - "number": "4.6.2:2" + "checksum": "5dfa02c07c1e043d1fbc3b8eab7d4555147d7e61752f59b311c2486f4a53c2ef", + "id": "fls_brhtaaxt1s3s", + "link": "patterns.html#fls_brhtaaxt1s3s", + "number": "5.10.1:25" }, { - "checksum": "1d99ab5849365bb230cbc4a782bb93bc919bb2f07c62b63a7f63b380e66ac908", - "id": "fls_liwnzwu1el1i", - "link": "types-and-traits.html#fls_liwnzwu1el1i", - "number": "4.6.2:3" + "checksum": "bc8d158c05838e267c985dd76e4808b795b9c9b82d65690a8d2744bf8753e87e", + "id": "fls_jwz3arnfkxwn", + "link": "patterns.html#fls_jwz3arnfkxwn", + "number": "5.10.1:26" }, { - "checksum": "d667ede8ab6893069841a33b2cf0e47b19b05d5fa2872b3bebec06b8852063d1", - "id": "fls_e9x4f7qxvvjv", - "link": "types-and-traits.html#fls_e9x4f7qxvvjv", - "number": "4.6.2:4" + "checksum": "cd32c48d96ca8821d8b097e61f7031858e9955ae4cb198bb1001a71624f77a7f", + "id": "fls_pfz8xlwezbw1", + "link": "patterns.html#fls_pfz8xlwezbw1", + "number": "5.10.1:27" }, { - "checksum": "8acd4d89f0c821d4aa22815001aca5bc9e977dc1e2712743e735bcf6aed3c248", - "id": "fls_1941wid94hlg", - "link": "types-and-traits.html#fls_1941wid94hlg", - "number": "4.6.2:5" - } - ], - "title": "Function Item Types" - }, - { - "id": "fls_3i4ou0dq64ny", - "informational": false, - "link": "types-and-traits.html#indirection-types", - "number": "4.7", - "paragraphs": [], - "title": "Indirection Types" - }, - { - "id": "fls_xztr1kebz8bo", - "informational": false, - "link": "types-and-traits.html#function-pointer-types", - "number": "4.7.1", - "paragraphs": [ + "checksum": "892be146ad066dad2bd25128cc3091054cbd49abdf7268f54e6f21339362e750", + "id": "fls_XFKBJZe6k1o2", + "link": "patterns.html#fls_XFKBJZe6k1o2", + "number": "5.10.1:28" + }, { - "checksum": "276ef1476e7ceccaf33821c0dfadd47de95e12defa884eb6eb9da1bcd89311d4", - "id": "fls_v2wrytr3t04h", - "link": "types-and-traits.html#fls_v2wrytr3t04h", - "number": "4.7.1:1" + "checksum": "3dda2685fefad7736c09572d77850f39aea37aff6a75b4775df949965c478004", + "id": "fls_mu166csowj71", + "link": "patterns.html#fls_mu166csowj71", + "number": "5.10.1:29" }, { - "checksum": "d51b172a0b8675c28c409e4d5ea5bce285ae17d936f4f4af0f5a80373b22ada7", - "id": "fls_5dd7icjcl3nt", - "link": "types-and-traits.html#fls_5dd7icjcl3nt", - "number": "4.7.1:2" + "checksum": "ae7dbf4db5d0804bb1b4f47f1a3d6ac4f752d00aacb5fd25e7d8f748f4760adc", + "id": "fls_y09fygnglu3n", + "link": "patterns.html#fls_y09fygnglu3n", + "number": "5.10.1:30" }, { - "checksum": "2d95eda82b646831bce5a240ff70e298c0a039e3ac40cb6198cb334c8e980d60", - "id": "fls_B0SMXRqQMS1E", - "link": "types-and-traits.html#fls_B0SMXRqQMS1E", - "number": "4.7.1:3" + "checksum": "b61a46d5ed3d3618df19128c327bc64de735101d00f7fc3b645d60b73ded72dd", + "id": "fls_2tadaatmauzk", + "link": "patterns.html#fls_2tadaatmauzk", + "number": "5.10.1:31" }, { - "checksum": "730f52bbaf5e2e9bd48089d7a8aa41c2e14dace0153b74d1bceb0aaf0c4d977c", - "id": "fls_hbn1l42xmr3h", - "link": "types-and-traits.html#fls_hbn1l42xmr3h", - "number": "4.7.1:4" + "checksum": "d75819f44a8d7f85c50196f3a44487fcbfdaa07c19627675b6bb09b4f2fc97c4", + "id": "fls_oq30xkmvyz72", + "link": "patterns.html#fls_oq30xkmvyz72", + "number": "5.10.1:32" }, { - "checksum": "4107a933398a84d0b8af66fd006db6a72808511b4af3e4c47b71b0dc795fc8f8", - "id": "fls_g1iYVw7upBnH", - "link": "types-and-traits.html#fls_g1iYVw7upBnH", - "number": "4.7.1:5" + "checksum": "5f55bfd0214be05b8229314e347c9671c485811ab7606a2c8c5a06c39aa89aae", + "id": "fls_9y1gbv47z23o", + "link": "patterns.html#fls_9y1gbv47z23o", + "number": "5.10.1:33" }, { - "checksum": "5860cccedbc0ac25984e9603b83f1259c4af97cecc8084f5c140164f6865c5e1", - "id": "fls_8gpvNJfVlyaD", - "link": "types-and-traits.html#fls_8gpvNJfVlyaD", - "number": "4.7.1:6" + "checksum": "013fca3143fcb9507c780ebe41cc1f74a40222e55f6c4b695758e3b78c605d12", + "id": "fls_dHtV2BPRFVBB", + "link": "patterns.html#fls_dHtV2BPRFVBB", + "number": "5.10.1:34" }, { - "checksum": "d2541a3280f1d894821db0f1648bbadb29e2c211db75fba997f3d2832ee527c4", - "id": "fls_KcI6yK0P8Onn", - "link": "types-and-traits.html#fls_KcI6yK0P8Onn", - "number": "4.7.1:7" + "checksum": "af8c99976d128dfd24b2788359de45493a9f94be7c7f89b9a4f4f5864bdbf1b0", + "id": "fls_zRCiKnhQebyp", + "link": "patterns.html#fls_zRCiKnhQebyp", + "number": "5.10.1:35" }, { - "checksum": "3a43cc40eddf138b65ff58d01174bf0f377be62e4998881c0e34b316af1831fb", - "id": "fls_52thmi9hnoks", - "link": "types-and-traits.html#fls_52thmi9hnoks", - "number": "4.7.1:8" + "checksum": "c268e10c795ada4873656e9f0fab449fea0c4a36994b5b024693b3b6bb4ee3a7", + "id": "fls_D5tAGzrjXFTu", + "link": "patterns.html#fls_D5tAGzrjXFTu", + "number": "5.10.1:36" + }, + { + "checksum": "16873908fc7b0e081b3a93d248479b79ce08a2c8d800fe5b80a55f81b3507300", + "id": "fls_FhvMzLPRlY7p", + "link": "patterns.html#fls_FhvMzLPRlY7p", + "number": "5.10.1:37" } ], - "title": "Function Pointer Types" + "title": "Record Struct Patterns" }, { - "id": "fls_ppd1xwve3tr7", + "id": "fls_vlrto778v49m", "informational": false, - "link": "types-and-traits.html#raw-pointer-types", - "number": "4.7.2", + "link": "patterns.html#tuple-struct-patterns", + "number": "5.10.2", "paragraphs": [ { - "checksum": "1854f3b6844b0c2671dfc60d36c4b998358f29c120b234bec672c72aaed507cc", - "id": "fls_rpbhr0xukbx9", - "link": "types-and-traits.html#fls_rpbhr0xukbx9", - "number": "4.7.2:1" + "checksum": "398db4748805660067dff91c2d8c07f0ce9a02f750eedab4d786b7097ea8a772", + "id": "fls_ks6y1syab2bp", + "link": "patterns.html#fls_ks6y1syab2bp", + "number": "5.10.2:1" }, { - "checksum": "f69e8334863b9e2b7f3bf48725d48a8adf59c48c3dd802341bfd611dcc03d690", - "id": "fls_bYWfGDAQcWfA", - "link": "types-and-traits.html#fls_bYWfGDAQcWfA", - "number": "4.7.2:2" + "checksum": "c49ac22e3f94f28c61a3b041503608d3e3f6a337ad9fdd9162106b3e8a729ddd", + "id": "fls_t1mrijw16k9a", + "link": "patterns.html#fls_t1mrijw16k9a", + "number": "5.10.2:2" }, { - "checksum": "82bd1634fa77a9d6d3b725fb993892cad15fbe87ece6afe59b2f171f2a7721bf", - "id": "fls_8uWfFAsZeRCs", - "link": "types-and-traits.html#fls_8uWfFAsZeRCs", - "number": "4.7.2:3" + "checksum": "caed82759506953820cc05207e2ff166b8c73479f82882f1d52cec340b5ea5f2", + "id": "fls_ryfcrqrkp28y", + "link": "patterns.html#fls_ryfcrqrkp28y", + "number": "5.10.2:3" }, { - "checksum": "6bfba3dbe307a3b99ae68a7d7ad6ee97648893f1aaa0a786262bff92bea33aa5", - "id": "fls_hrum767l6dte", - "link": "types-and-traits.html#fls_hrum767l6dte", - "number": "4.7.2:4" + "checksum": "52efa16e2d2072309e596e1e678e1ed4c435a2a10a044d200823d457ae75c86b", + "id": "fls_ehf9r6halgh1", + "link": "patterns.html#fls_ehf9r6halgh1", + "number": "5.10.2:4" }, { - "checksum": "9a80943af437a0fd5e67c5bd656fadb3ce16e6d7b59218c8685ae1e0a2c4d9a4", - "id": "fls_k6ues2936pjq", - "link": "types-and-traits.html#fls_k6ues2936pjq", - "number": "4.7.2:5" - } - ], - "title": "Raw Pointer Types" - }, - { - "id": "fls_142vncdktbin", - "informational": false, - "link": "types-and-traits.html#reference-types", - "number": "4.7.3", - "paragraphs": [ + "checksum": "88a50cbe98942f52e03b0825ff7e25f2b0c89e3c3a66979381da5376a170cee7", + "id": "fls_5lo1hs8wzz0t", + "link": "patterns.html#fls_5lo1hs8wzz0t", + "number": "5.10.2:5" + }, { - "checksum": "e9a8f32c3bfd274934e7c471016afe023d42728464a9c6ad6934f0738cf79a22", - "id": "fls_twhq24s8kchh", - "link": "types-and-traits.html#fls_twhq24s8kchh", - "number": "4.7.3:1" + "checksum": "3dc2817ca063542426c88f985729203dbe054c09d6efe49d7eb52097ec52bca6", + "id": "fls_gwuc2xffosu", + "link": "patterns.html#fls_gwuc2xffosu", + "number": "5.10.2:6" }, { - "checksum": "15503795396e6eb08752e159af664fcaf59c4c7eb0d754480fbef0251d74b28d", - "id": "fls_w4NbA7WhZfR2", - "link": "types-and-traits.html#fls_w4NbA7WhZfR2", - "number": "4.7.3:2" + "checksum": "541bd9b4e34b9b13bac861950cbc1ff7a06f887ca4d3a08d65ca538b08a21639", + "id": "fls_w369n8lmwr7g", + "link": "patterns.html#fls_w369n8lmwr7g", + "number": "5.10.2:7" }, { - "checksum": "989727897539b6eef42387c5f240b13d4ab9aa9f269a111e510879ada1ae0960", - "id": "fls_ie0avzljmxfm", - "link": "types-and-traits.html#fls_ie0avzljmxfm", - "number": "4.7.3:3" + "checksum": "ca44a0d983f6a2c58de0d0da866f70042fb1589545be9d179d6b19542bf94f4f", + "id": "fls_4is6h95jj3gd", + "link": "patterns.html#fls_4is6h95jj3gd", + "number": "5.10.2:8" }, { - "checksum": "fef244abbffea76d85673c8b768c80e3998096122c5887ffacd05a346939ff67", - "id": "fls_15zdiqsm1q3p", - "link": "types-and-traits.html#fls_15zdiqsm1q3p", - "number": "4.7.3:4" + "checksum": "d8b0cbea39dac23dc53d3b24c98a4832ca33e84bd17e7a35598838c1ec69d58b", + "id": "fls_budf0rpsa4lx", + "link": "patterns.html#fls_budf0rpsa4lx", + "number": "5.10.2:9" }, { - "checksum": "823004ffa0528651765ee5a2a23b26f7a33b4255a564f742c3923781a6b70102", - "id": "fls_csdjfwczlzfd", - "link": "types-and-traits.html#fls_csdjfwczlzfd", - "number": "4.7.3:5" + "checksum": "f47bc7199534d69e8491c54e3507a6dab7fa624a91bc061b87c451e8ed8af807", + "id": "fls_vo6mtauh4qhb", + "link": "patterns.html#fls_vo6mtauh4qhb", + "number": "5.10.2:10" }, { - "checksum": "19c8db7f4aaa3f7224c336d60e3c57d17ef21d93e5ececa9fbb1d09175936b0f", - "id": "fls_GUZuiST7ucib", - "link": "types-and-traits.html#fls_GUZuiST7ucib", - "number": "4.7.3:6" + "checksum": "9e91b8f7ea80554c6c9d801b284fd737f8072273ca2869a7a1a7c23f6cba1e10", + "id": "fls_rco3fwlx2a76", + "link": "patterns.html#fls_rco3fwlx2a76", + "number": "5.10.2:11" }, { - "checksum": "2784f42b85409ac61fe875e01d21162c086f0411280475dac703bf1a2ae98fc8", - "id": "fls_vaas9kns4zo6", - "link": "types-and-traits.html#fls_vaas9kns4zo6", - "number": "4.7.3:7" + "checksum": "435aadfae6890acd5b2f7c089d2bf6c6bde46b865278bc446f816f2900ad444d", + "id": "fls_4vrnxslad09e", + "link": "patterns.html#fls_4vrnxslad09e", + "number": "5.10.2:12" }, { - "checksum": "585dd4699fdeac3e280d38797a9c9f9565c4194543b3c1c048a0ef33913a5e66", - "id": "fls_n6ffcms5pr0r", - "link": "types-and-traits.html#fls_n6ffcms5pr0r", - "number": "4.7.3:8" + "checksum": "c18b1eaabbf21d6d906c4d4255fe8e1efcf58af3fbebbfffa7348ac6b6edf3c5", + "id": "fls_qgilaqy5zx7q", + "link": "patterns.html#fls_qgilaqy5zx7q", + "number": "5.10.2:13" }, { - "checksum": "fef35c5ee01a70578d0ee80139418ab1d555636b9d7a83d7cf5bcf38dc454153", - "id": "fls_ezh8aq6fmdvz", - "link": "types-and-traits.html#fls_ezh8aq6fmdvz", - "number": "4.7.3:9" + "checksum": "2d585002f71b603779602116d0639fd90c72f6c4832288b134b5a2c57dc36717", + "id": "fls_2u99arsbnlnk", + "link": "patterns.html#fls_2u99arsbnlnk", + "number": "5.10.2:14" } ], - "title": "Reference Types" - }, - { - "id": "fls_1ompd93w7c9f", - "informational": false, - "link": "types-and-traits.html#trait-types", - "number": "4.8", - "paragraphs": [], - "title": "Trait Types" + "title": "Tuple Struct Patterns" }, { - "id": "fls_3xqobbu7wfsf", + "id": "fls_urbr5rg9206v", "informational": false, - "link": "types-and-traits.html#impl-trait-types", - "number": "4.8.1", + "link": "patterns.html#tuple-patterns", + "number": "5.10.3", "paragraphs": [ { - "checksum": "9013893b3cd5acfa5451f4ad18860a989a9ad70cd891c50e846503ea7f274dcf", - "id": "fls_a6zlvyxpgsew", - "link": "types-and-traits.html#fls_a6zlvyxpgsew", - "number": "4.8.1:1" + "checksum": "86e5cf86cc1727a1ec3d2c9b3c7770ba2d5a0a0be11e05f181cf89208cef7e8f", + "id": "fls_e2manugp4e0b", + "link": "patterns.html#fls_e2manugp4e0b", + "number": "5.10.3:1" }, { - "checksum": "16cb713fd246c7e18f528a4ffd5338a0fbce00bff8b4aa68d64f137a4d38b4c1", - "id": "fls_ieyqx5vzas2m", - "link": "types-and-traits.html#fls_ieyqx5vzas2m", - "number": "4.8.1:2" + "checksum": "968065a0acde4dc1fc6772c9a80d889371d997d4d812bece3bb143b855b791f3", + "id": "fls_xk8udu4k61kj", + "link": "patterns.html#fls_xk8udu4k61kj", + "number": "5.10.3:2" }, { - "checksum": "2fd602bae21ca8be8d6cf24e5f3559c0846d9301830440115c150d3a57aac7bf", - "id": "fls_3aKZB0ILIkZw", - "link": "types-and-traits.html#fls_3aKZB0ILIkZw", - "number": "4.8.1:3" + "checksum": "c046f561eb355602680f839ad87e202a633dd889397bbc12f7027ffc7b5a284a", + "id": "fls_yhcaz6v49ub2", + "link": "patterns.html#fls_yhcaz6v49ub2", + "number": "5.10.3:3" }, { - "checksum": "637383dc4d1660eea32118dd12d2223bac4c904a62d1b6362ee20154a1fd8a9c", - "id": "fls_Xo1ODwOyX7Vm", - "link": "types-and-traits.html#fls_Xo1ODwOyX7Vm", - "number": "4.8.1:4" + "checksum": "208f90ce72da5d32c30755d34df67702194f5fe887e9c7a1bd0554c4718eeede", + "id": "fls_6WCm0Ra8NQl4", + "link": "patterns.html#fls_6WCm0Ra8NQl4", + "number": "5.10.3:4" }, { - "checksum": "05a4175995d776a46a98e4d297c0a67cfb2ed6dcef4796010a1d0cc829263bce", - "id": "fls_kTGFLFymTWch", - "link": "types-and-traits.html#fls_kTGFLFymTWch", - "number": "4.8.1:5" + "checksum": "d4a0b43d9d6a5e0b17d67fcdf081a940f340d1b85d971f31c684778d9986fb2b", + "id": "fls_a3qvQjyilORx", + "link": "patterns.html#fls_a3qvQjyilORx", + "number": "5.10.3:5" }, { - "checksum": "0dc7132c60e97eae3fe8c4763fb5b0ee4e27b69d33014e5a64c902c25e992bba", - "id": "fls_ECjhEI7eCwAj", - "link": "types-and-traits.html#fls_ECjhEI7eCwAj", - "number": "4.8.1:6" + "checksum": "289e79602b945e44a6b259d8d0c1e225e914afa943874c7698ef97f89f8f3def", + "id": "fls_KmIHFxlBYelZ", + "link": "patterns.html#fls_KmIHFxlBYelZ", + "number": "5.10.3:6" }, { - "checksum": "13a60d90ebb030b15b25dc8cb8e67dacf1551162e23af35c46d25856b08a7364", - "id": "fls_69hqMjvNno9u", - "link": "types-and-traits.html#fls_69hqMjvNno9u", - "number": "4.8.1:7" + "checksum": "bd944455b4e5b2992d60a0276dd284685483fbdb2ce0a6e8b861676e3d2069b7", + "id": "fls_5bXqIaKiFcLg", + "link": "patterns.html#fls_5bXqIaKiFcLg", + "number": "5.10.3:7" }, { - "checksum": "ddef3b4e840fd2a4e762e483b2babf5dcdc86872d5c7e646d65db47730395ea5", - "id": "fls_KgH6c5cC4S0G", - "link": "types-and-traits.html#fls_KgH6c5cC4S0G", - "number": "4.8.1:8" + "checksum": "47877ba9b3984b4661b6db0b34526c8edb770ea1f800c33d64c5523a6d44b8f2", + "id": "fls_soHCAVfGlv5f", + "link": "patterns.html#fls_soHCAVfGlv5f", + "number": "5.10.3:8" }, { - "checksum": "33a093516df193a7fdc2815dbd081c61f7afc3126d1c8442b5009786e41ad74f", - "id": "fls_iT9WCNfUZQnC", - "link": "types-and-traits.html#fls_iT9WCNfUZQnC", - "number": "4.8.1:9" - } - ], - "title": "Impl Trait Types" - }, - { - "id": "fls_qa98qdi42orq", - "informational": false, - "link": "types-and-traits.html#trait-object-types", - "number": "4.8.2", - "paragraphs": [ - { - "checksum": "7e198679726fb24effe560d32f6ed44994be67d60bad4c175f3da2c84b40b0d9", - "id": "fls_sgrvona1nb6h", - "link": "types-and-traits.html#fls_sgrvona1nb6h", - "number": "4.8.2:1" + "checksum": "325628fddc5edcfee5dde126d580262949d54d516c4f6d9243baff7c4f607da9", + "id": "fls_iiKvYs61959S", + "link": "patterns.html#fls_iiKvYs61959S", + "number": "5.10.3:9" }, { - "checksum": "35adf29ee8c478f92e6058e972a281cd75546d4f895d5388d058c14a47be175e", - "id": "fls_eWac7zOda3lh", - "link": "types-and-traits.html#fls_eWac7zOda3lh", - "number": "4.8.2:2" + "checksum": "80b589c7aaacf22228397ab8de7f859870551fa23bbc58316341c4fe297455b0", + "id": "fls_F4k6ljuP8Amf", + "link": "patterns.html#fls_F4k6ljuP8Amf", + "number": "5.10.3:10" }, { - "checksum": "b537bc4965a9b1c056d0dd2da6fec6494eb39fa97f42b8f126dc42e23bad4e4c", - "id": "fls_9z8oleh0wdel", - "link": "types-and-traits.html#fls_9z8oleh0wdel", - "number": "4.8.2:3" + "checksum": "d0a0133555000675455b2bf5ab37681e6e7bb93783c7e8ebd5178f80704e6ce1", + "id": "fls_GjjCDkVJPQS8", + "link": "patterns.html#fls_GjjCDkVJPQS8", + "number": "5.10.3:11" }, { - "checksum": "ad208e723038631cfa6c6e948ce39a457dada12fd9e9dee7ca26c9420c6861f8", - "id": "fls_hJII8XYAtZeY", - "link": "types-and-traits.html#fls_hJII8XYAtZeY", - "number": "4.8.2:4" + "checksum": "46bd974ba3f88ec8c528de9c9907e74139719b19f51d32a06f37cede033afd44", + "id": "fls_9Qw9N87swwNe", + "link": "patterns.html#fls_9Qw9N87swwNe", + "number": "5.10.3:12" }, { - "checksum": "6257637cc04d7a1e7f32788a155b69595ebd5872f7e7366b15a24bc0cb01c3ec", - "id": "fls_s0oy2c8t4yz9", - "link": "types-and-traits.html#fls_s0oy2c8t4yz9", - "number": "4.8.2:5" + "checksum": "f1887b73be484d54fe02b6a04ea8aee012209872ae40fb9179a705c3f4856242", + "id": "fls_CQ84wkLyrAJv", + "link": "patterns.html#fls_CQ84wkLyrAJv", + "number": "5.10.3:13" }, { - "checksum": "8afd9b150960eee9cad7ad77a00f4d78e50ae87ac73c9a1198b12f79aa8ff09e", - "id": "fls_CcoUug6b9ohU", - "link": "types-and-traits.html#fls_CcoUug6b9ohU", - "number": "4.8.2:6" + "checksum": "176644151cc57b936a43b35578e7182a3c89ac881b62ba4e3aa5bfede1008ecf", + "id": "fls_cC6ohNuiltfL", + "link": "patterns.html#fls_cC6ohNuiltfL", + "number": "5.10.3:14" }, { - "checksum": "80503a5bbbf247afc4589e84831bd9388ca01a63f6f99729d27e31db000127ad", - "id": "fls_88b9bmhra55f", - "link": "types-and-traits.html#fls_88b9bmhra55f", - "number": "4.8.2:7" + "checksum": "c479906bca39314417e1caa942e7ef8efbb0987857a2c5ea2e3dc30d69cc4a20", + "id": "fls_8r81vtv5hnrd", + "link": "patterns.html#fls_8r81vtv5hnrd", + "number": "5.10.3:15" } ], - "title": "Trait Object Types" - }, - { - "id": "fls_3pbipk8ki18d", - "informational": false, - "link": "types-and-traits.html#other-types", - "number": "4.9", - "paragraphs": [], - "title": "Other Types" + "title": "Tuple Patterns" }, { - "id": "fls_s45k21yn4qur", + "id": "fls_qfsfnql1t7m", "informational": false, - "link": "types-and-traits.html#inferred-types", - "number": "4.9.1", + "link": "patterns.html#underscore-patterns", + "number": "5.11", "paragraphs": [ { - "checksum": "45bfb5537d2e07e5037870981039b3817a257151920d49f5a8939e10ff7a0815", - "id": "fls_xdtgr5toulpb", - "link": "types-and-traits.html#fls_xdtgr5toulpb", - "number": "4.9.1:1" - }, - { - "checksum": "1d559796531e476dc3d85e19f7dc9e8b9e85dc14e75d7e22f7fd0d9cd3e0753c", - "id": "fls_3abhsuaa8nas", - "link": "types-and-traits.html#fls_3abhsuaa8nas", - "number": "4.9.1:2" - }, - { - "checksum": "a61fcb03ca3303b870d5a7bf9412d89fa8c8a999d7275efa89ada3e204974f57", - "id": "fls_hBXlJhbhuoHY", - "link": "types-and-traits.html#fls_hBXlJhbhuoHY", - "number": "4.9.1:3" - }, - { - "checksum": "f472521e59830a8a2807dd85f1821de685a14eda76e2e46a580818dcb6e86b8d", - "id": "fls_Vxlr9ZcqiOvY", - "link": "types-and-traits.html#fls_Vxlr9ZcqiOvY", - "number": "4.9.1:4" + "checksum": "ffe05e2b6f44aab3204db8f037bcbabda722b0b2a4c36233bd344165b0915664", + "id": "fls_dreny9e0ei6r", + "link": "patterns.html#fls_dreny9e0ei6r", + "number": "5.11:1" }, { - "checksum": "ea3bbe8655934a40bc687f54cf38d663ed640a043e0a93e57b63097ed7b378ec", - "id": "fls_gE9VC8JXrl1N", - "link": "types-and-traits.html#fls_gE9VC8JXrl1N", - "number": "4.9.1:5" + "checksum": "512af904f1afbc0ae83e21197bac2017c65eb4d2820eb33ef0e964251753ac99", + "id": "fls_42fye1v0th8l", + "link": "patterns.html#fls_42fye1v0th8l", + "number": "5.11:2" }, { - "checksum": "1dd464889720d923a6efc632871acb0d7f1fa780fec361617485a85c8eaef334", - "id": "fls_ybyQjFamI1Q5", - "link": "types-and-traits.html#fls_ybyQjFamI1Q5", - "number": "4.9.1:6" + "checksum": "4df12d4b4abe0cd1029651c34033e70582bd69ad83cfc6a3a733e4bf20d8da0a", + "id": "fls_b87mvrcc13f2", + "link": "patterns.html#fls_b87mvrcc13f2", + "number": "5.11:3" }, { - "checksum": "8d410f326c8e233e5773842346d9f3d9783f75fad42585cb76ed490065470f5f", - "id": "fls_9d8wbugmar1m", - "link": "types-and-traits.html#fls_9d8wbugmar1m", - "number": "4.9.1:7" + "checksum": "7dd5b2d964186c34d5eb0d46902b5bcc8cef29ddc8f75425c634837f9354dbcd", + "id": "fls_j3u6x1ensrbe", + "link": "patterns.html#fls_j3u6x1ensrbe", + "number": "5.11:4" } ], - "title": "Inferred Types" + "title": "Underscore Patterns" }, { - "id": "fls_xjcxbajhzp3d", + "id": "fls_qssijtofa9i8", "informational": false, - "link": "types-and-traits.html#type-parameters", - "number": "4.9.2", + "link": "patterns.html#binding-modes", + "number": "5.12", "paragraphs": [ { - "checksum": "e42bea781e368e51e6a841e48a716968a41dbbc44fbbebc2447280a69d1d25fb", - "id": "fls_dCIIVXGhXDlO", - "link": "types-and-traits.html#fls_dCIIVXGhXDlO", - "number": "4.9.2:1" - } - ], - "title": "Type Parameters" - }, - { - "id": "fls_98lnexk53ru4", - "informational": false, - "link": "types-and-traits.html#never-type", - "number": "4.9.3", - "paragraphs": [ + "checksum": "ce737fd6a8e0011aa5361836e179e7da00b62d001685d8a3099eda2e5f94ff52", + "id": "fls_7xby6d1903kw", + "link": "patterns.html#fls_7xby6d1903kw", + "number": "5.12:1" + }, { - "checksum": "c2d4459a9d3124b6eb26bd54c1d0a2741f70e371c43c323dca07a05f424933ba", - "id": "fls_4u0v5uy95pyf", - "link": "types-and-traits.html#fls_4u0v5uy95pyf", - "number": "4.9.3:1" + "checksum": "ee24110781d1935c43cf0567d764e1aadf2cc3db7f17740efe4e767ac0ee9632", + "id": "fls_vnh9wfrvumdz", + "link": "patterns.html#fls_vnh9wfrvumdz", + "number": "5.12:2" }, { - "checksum": "77b18aa146d69730f692da58a1c8e9a4953cad14ad3289ca106e7a071fc98a83", - "id": "fls_xmtc10qzw0ui", - "link": "types-and-traits.html#fls_xmtc10qzw0ui", - "number": "4.9.3:2" + "checksum": "3689001f3e5a3fadb5bce3f16c76e8babf3aa8a1b12b24dc1d08cc2709b6336c", + "id": "fls_RViC5UEZPQUV", + "link": "patterns.html#fls_RViC5UEZPQUV", + "number": "5.12:3" }, { - "checksum": "a31ec075c01c1bf5fe6b674ca2fa9dd54e2f8402cde5540b9faa80e0cb2b6d9a", - "id": "fls_22e8quna7ed5", - "link": "types-and-traits.html#fls_22e8quna7ed5", - "number": "4.9.3:3" - } - ], - "title": "Never Type" - }, - { - "id": "fls_olbj67eyxz2k", - "informational": false, - "link": "types-and-traits.html#parenthesized-types", - "number": "4.9.4", - "paragraphs": [ + "checksum": "1a8541da9715b497aff0fb8fd6116cd249a0350898d3dd89d00b2c30c7e82671", + "id": "fls_6lXtoxebD5It", + "link": "patterns.html#fls_6lXtoxebD5It", + "number": "5.12:4" + }, { - "checksum": "676be38dcb5032a5b66c95c6e0c85e8d4f049bf01933b118a0967607b5fbbf6c", - "id": "fls_1dvo1epstrdv", - "link": "types-and-traits.html#fls_1dvo1epstrdv", - "number": "4.9.4:1" - } - ], - "title": "Parenthesized Types" - }, - { - "id": "fls_kgvleup5mdhq", - "informational": false, - "link": "types-and-traits.html#type-aliases", - "number": "4.10", - "paragraphs": [ + "checksum": "0b306a42847f796ecf6339eefc0af21e5781a2cde421ae3141018b146e11ffb0", + "id": "fls_xNxQN8sgpZ3O", + "link": "patterns.html#fls_xNxQN8sgpZ3O", + "number": "5.12:5" + }, { - "checksum": "25fd89d90847d3d474a7da20e5aba2d29f31f8f7e17c2aeb98d2a62683b49efc", - "id": "fls_bibigic4jjad", - "link": "types-and-traits.html#fls_bibigic4jjad", - "number": "4.10:1" + "checksum": "0871ae0bb0826a8d223e8c2cee52f184b7c0e39f86c5063fcd676f7fbc614365", + "id": "fls_dqe75i8h2fie", + "link": "patterns.html#fls_dqe75i8h2fie", + "number": "5.12:6" }, { - "checksum": "8ad56706cf3948a37e7738f416f5065e7f1335a2443524ff1c3ebea736ac75fa", - "id": "fls_rosdkeck5ax2", - "link": "types-and-traits.html#fls_rosdkeck5ax2", - "number": "4.10:2" + "checksum": "959846317dd64ecd23fd6c93f96608048d818682c7cd48599dfffcd231a8557a", + "id": "fls_y3wuvj1y5j20", + "link": "patterns.html#fls_y3wuvj1y5j20", + "number": "5.12:7" }, { - "checksum": "9db2e47ca9bc68f63d03dab529165f2062950ff28398da808d41f83989833ac9", - "id": "fls_drxl7u3etfp9", - "link": "types-and-traits.html#fls_drxl7u3etfp9", - "number": "4.10:3" - } - ], - "title": "Type Aliases" - }, - { - "id": "fls_7pby13muw48o", - "informational": false, - "link": "types-and-traits.html#representation", - "number": "4.11", - "paragraphs": [], - "title": "Representation" - }, - { - "id": "fls_g1z6bpyjqxkz", - "informational": false, - "link": "types-and-traits.html#type-layout", - "number": "4.11.1", - "paragraphs": [ + "checksum": "edc7cf534c55c29625cd191432a05763797465d54cbd387fee8127a17ad0f1ed", + "id": "fls_55jtzh6a292x", + "link": "patterns.html#fls_55jtzh6a292x", + "number": "5.12:8" + }, { - "checksum": "ec574263a104f5220dbeec30e0118b25ac35e37d6a614ad3bc5cd6e58681532d", - "id": "fls_kdbq02iguzgl", - "link": "types-and-traits.html#fls_kdbq02iguzgl", - "number": "4.11.1:1" + "checksum": "174692122d916648a54e197fff15a1e7bf17984b9523fd06224c71a7484e0445", + "id": "fls_qcaf2kup7zn0", + "link": "patterns.html#fls_qcaf2kup7zn0", + "number": "5.12:9" }, { - "checksum": "85b5fbe662d8867421f671eced959a90cc1ae59ca88984af3e8db0fddecd7774", - "id": "fls_26Xgem831Nqg", - "link": "types-and-traits.html#fls_26Xgem831Nqg", - "number": "4.11.1:2" + "checksum": "c9dfbc0519fed7bb3f112777102c928f15a4d5c3cfc13d4d3017e9c15259ce08", + "id": "fls_6acdqz8rwnn", + "link": "patterns.html#fls_6acdqz8rwnn", + "number": "5.12:10" + }, + { + "checksum": "61b076c4aa153ed7b1ed996845d08b881a2327bfe131501f20b3ae9b3a2c042d", + "id": "fls_tv0avib387bv", + "link": "patterns.html#fls_tv0avib387bv", + "number": "5.12:11" + }, + { + "checksum": "a30925ea032d35a24204cb5c81fc5ad636e77d19ce00fb38615985b1bec396d6", + "id": "fls_dbgmwldye42e", + "link": "patterns.html#fls_dbgmwldye42e", + "number": "5.12:12" + }, + { + "checksum": "e9940ece8b67430f6507527efb4e0837eea8ecd615884b2d4a3773018ca5df88", + "id": "fls_t34oqarwcusu", + "link": "patterns.html#fls_t34oqarwcusu", + "number": "5.12:13" }, { - "checksum": "4b74a052b7ae0bcd4e6bd2091a170309e1d85fcb0c8e4fd4b397abf85839197a", - "id": "fls_ozYgHEHFTT5c", - "link": "types-and-traits.html#fls_ozYgHEHFTT5c", - "number": "4.11.1:3" + "checksum": "94de4c12fad70f9cac54c2a50a2c65b9a5d0f42b1847bddbbb2e5c23d1e9917c", + "id": "fls_7gxb74u1np36", + "link": "patterns.html#fls_7gxb74u1np36", + "number": "5.12:14" }, { - "checksum": "9fce4f034a0c87f5dc66db7b3e507d3b8d4ccadac4c182b83669e0c361da3f13", - "id": "fls_muxfn9soi47l", - "link": "types-and-traits.html#fls_muxfn9soi47l", - "number": "4.11.1:4" + "checksum": "7b6cefba0ed64069e11e48d9eddf7bdab4f5ec7b32d73b3a9c0c92d7fd078d3a", + "id": "fls_7y56d0ulxomf", + "link": "patterns.html#fls_7y56d0ulxomf", + "number": "5.12:15" }, { - "checksum": "07e54228ecadbf143bd9829ea2af5cd942e05ae28f70c0bf04ece785266561b3", - "id": "fls_1pbwigq6f3ha", - "link": "types-and-traits.html#fls_1pbwigq6f3ha", - "number": "4.11.1:5" - }, + "checksum": "7a06ffc02f7927cd15e2513d53f8a618b2100301b066a92e111ba6ce488b6941", + "id": "fls_pxvtqxke1enp", + "link": "patterns.html#fls_pxvtqxke1enp", + "number": "5.12:16" + } + ], + "title": "Binding Modes" + }, + { + "id": "fls_jm6l7b90h6wa", + "informational": false, + "link": "patterns.html#pattern-matching", + "number": "5.13", + "paragraphs": [ { - "checksum": "3d989ed20cee6bc9220874459a4ea548220f9e445447a73f0477590d4aaa0908", - "id": "fls_bk3nm2n47afu", - "link": "types-and-traits.html#fls_bk3nm2n47afu", - "number": "4.11.1:6" + "checksum": "fed5fae34172da372479806ae83b3c3ee3fddd0115580bd763e26a0af5da0cee", + "id": "fls_zv73CR8rplIa", + "link": "patterns.html#fls_zv73CR8rplIa", + "number": "5.13:1" }, { - "checksum": "509cfea09ca67250d7134d86496da2f7ddccb10a30cc84e8fe0614e40a946388", - "id": "fls_z3i758jshvhx", - "link": "types-and-traits.html#fls_z3i758jshvhx", - "number": "4.11.1:7" + "checksum": "6b0c9b448ffa1317860d5ce72327a183542c035ca71f57b637fe069226784ff8", + "id": "fls_tlwr4u7bjhh5", + "link": "patterns.html#fls_tlwr4u7bjhh5", + "number": "5.13:2" }, { - "checksum": "955ca7ce99e58bbbb5f65e867911434aaa20ce3d4b5859af83963a91b4528556", - "id": "fls_uixe1ruv52be", - "link": "types-and-traits.html#fls_uixe1ruv52be", - "number": "4.11.1:8" + "checksum": "4ea2d8ef09b45f966ea91c945da620f5ef84eadc6c24c492c2a79b4140fab595", + "id": "fls_tZJgZDWVChJV", + "link": "patterns.html#fls_tZJgZDWVChJV", + "number": "5.13:3" }, { - "checksum": "596b59e14342649bfc88ebf54917a1503fc3ecb4de2d025b3df3a6048d0516ba", - "id": "fls_7at60xlxm9u4", - "link": "types-and-traits.html#fls_7at60xlxm9u4", - "number": "4.11.1:9" + "checksum": "2efb0c843c3347a6968949abf2fca84daa99dd61608b4660accbec71d32cde04", + "id": "fls_62626ws222op", + "link": "patterns.html#fls_62626ws222op", + "number": "5.13:4" }, { - "checksum": "65566a896e699d4581061cbfa9881b61ebbce21a79ba3ae60cfebc267d506a43", - "id": "fls_395247pkxv48", - "link": "types-and-traits.html#fls_395247pkxv48", - "number": "4.11.1:10" + "checksum": "c6772d317d3324a285727d769064ce8e2af5e86ef7c2e382aa8518b4bf698356", + "id": "fls_q0z46h1gnzez", + "link": "patterns.html#fls_q0z46h1gnzez", + "number": "5.13:5" }, { - "checksum": "ea4301cd4ad66803e16f0b4f0d2b5c2a336a7cf59a8a11148379d67b5d3371e0", - "id": "fls_tbe9sc75timc", - "link": "types-and-traits.html#fls_tbe9sc75timc", - "number": "4.11.1:11" + "checksum": "57dbb3cb675ba83f531a2f5110697c39a191fb7fa60f0f3c6c7848d2770af6ca", + "id": "fls_1r0vm6rg13o9", + "link": "patterns.html#fls_1r0vm6rg13o9", + "number": "5.13:6" }, { - "checksum": "16ddd66245cad2e311b9193a332f01ac5e3875c354baf1b3bebb93bc4dd430af", - "id": "fls_7jaqx33re3hg", - "link": "types-and-traits.html#fls_7jaqx33re3hg", - "number": "4.11.1:12" + "checksum": "6b05b0f396c6a2f7c1c6ed27a9344405d935e0e8c6ea2d8c832886a2ac168357", + "id": "fls_am5h8r887bz5", + "link": "patterns.html#fls_am5h8r887bz5", + "number": "5.13:7" }, { - "checksum": "56149efe39f3ec896b5d41b41f82964eebe02b0be71a3cda53763e854c3559ed", - "id": "fls_asys0iz6m0md", - "link": "types-and-traits.html#fls_asys0iz6m0md", - "number": "4.11.1:13" + "checksum": "061060573b3a2fa03fecf4917de59898492a684a1b822036866ea0bba7b10b1f", + "id": "fls_eppmiloh7bgg", + "link": "patterns.html#fls_eppmiloh7bgg", + "number": "5.13:8" }, { - "checksum": "711825cc745b9af39e7e4048a16c47f77125cf76d5cbe980e5ff38079d76320f", - "id": "fls_wfv5vcxl2lc7", - "link": "types-and-traits.html#fls_wfv5vcxl2lc7", - "number": "4.11.1:14" + "checksum": "67acc5c7291756502e81a1f9f2d7f298408c1401f9f66dda32df1ddd48a62627", + "id": "fls_gwc08xayno7q", + "link": "patterns.html#fls_gwc08xayno7q", + "number": "5.13:9" }, { - "checksum": "e999468acaca0c1905fc4050b53eecbc0ba5d19207fe6a97efd97bad0ba0f84e", - "id": "fls_x8dfw50z9c", - "link": "types-and-traits.html#fls_x8dfw50z9c", - "number": "4.11.1:15" + "checksum": "b73206cc6af5294d375b8c5cfa306af1436fc759e88f281b0fb4cda31caf322a", + "id": "fls_19iygu12s315", + "link": "patterns.html#fls_19iygu12s315", + "number": "5.13:10" }, { - "checksum": "4d8795302b12790cc45fa4ebd52923e953f775313a4540fe398a33d90911f943", - "id": "fls_nyxnnlwmt5gu", - "link": "types-and-traits.html#fls_nyxnnlwmt5gu", - "number": "4.11.1:16" + "checksum": "ef18480cbda2466675a65cc4f5fb16ba8e68efa31c489b887c095d58364c3a01", + "id": "fls_r307spfk6cs9", + "link": "patterns.html#fls_r307spfk6cs9", + "number": "5.13:11" }, { - "checksum": "0824d734bae0a452fabd30f7eebe1ce756436f484b63cb76f82a47710b2b2613", - "id": "fls_lwmrljw9m0pb", - "link": "types-and-traits.html#fls_lwmrljw9m0pb", - "number": "4.11.1:17" + "checksum": "7c61fa36bd4eb0e4840994dc694926592d011f93b99f0f7e8516293a35531091", + "id": "fls_qhdofvbso3gl", + "link": "patterns.html#fls_qhdofvbso3gl", + "number": "5.13:12" }, { - "checksum": "f3191b0a3434aec33dea3707cfa15d73aa4eed96e7edc9ef18c8d26970af27f2", - "id": "fls_pzi6izljfv0f", - "link": "types-and-traits.html#fls_pzi6izljfv0f", - "number": "4.11.1:18" + "checksum": "f8fbe2a911bd99e50865c967dcaca5bf0fa1430fee37b7fb4b641a1f7d527495", + "id": "fls_drb114dtvlpt", + "link": "patterns.html#fls_drb114dtvlpt", + "number": "5.13:13" }, { - "checksum": "b8f0794e107a5077c3c243a841267feca917797abb0af99397715c0652488ec7", - "id": "fls_7cjbxleo998q", - "link": "types-and-traits.html#fls_7cjbxleo998q", - "number": "4.11.1:19" + "checksum": "094e9ddf51a91e3ddcad2423c543d0e1a3c8c9d3bd97c6d464e2ad41d548f326", + "id": "fls_uxysntb3u03j", + "link": "patterns.html#fls_uxysntb3u03j", + "number": "5.13:14" }, { - "checksum": "93a93b729d2fc10096d6f3e153b0d420ed95d0a2c40e7b3766a6775b99f2d23b", - "id": "fls_veotnstzigw2", - "link": "types-and-traits.html#fls_veotnstzigw2", - "number": "4.11.1:20" + "checksum": "8bd4950f12c3fc2761c334a84cb412e80a671197a21ca640ebe447e01836d826", + "id": "fls_wh201rmh6u6d", + "link": "patterns.html#fls_wh201rmh6u6d", + "number": "5.13:15" }, { - "checksum": "b65cf13de25acfcffa5571a788c21ffc81a25f4caf34236efd82d429143e4f38", - "id": "fls_nmoqk7jo1kzf", - "link": "types-and-traits.html#fls_nmoqk7jo1kzf", - "number": "4.11.1:21" + "checksum": "aa4932214e845c7edd44605a7721fc1476f6b66f527e69354b6b3b8060bba84b", + "id": "fls_vstdqifqipbh", + "link": "patterns.html#fls_vstdqifqipbh", + "number": "5.13:16" + } + ], + "title": "Pattern Matching" + }, + { + "id": "fls_vnai6ag4qrdb", + "informational": false, + "link": "patterns.html#identifier-pattern-matching", + "number": "5.13.1", + "paragraphs": [ + { + "checksum": "dd60a44dea7a972ddea069491ecd9f406558f404e901527ddd0d2149eeb48ebd", + "id": "fls_4f3lzw64myhk", + "link": "patterns.html#fls_4f3lzw64myhk", + "number": "5.13.1:1" }, { - "checksum": "5c51e84731e68ffbfb71d7770138e0e073c19250eef8f79cfe653382420ed1eb", - "id": "fls_gd7wozpn2ecp", - "link": "types-and-traits.html#fls_gd7wozpn2ecp", - "number": "4.11.1:22" + "checksum": "aa285e853a6324a841c40d2b74e066fb6002f1fb6707b20a81ff680616eae3fc", + "id": "fls_wauqwmdbcpna", + "link": "patterns.html#fls_wauqwmdbcpna", + "number": "5.13.1:2" }, { - "checksum": "a50cf2c5f56fb453ff79b573e1b12129846b5614280b059f31282bdbad00120a", - "id": "fls_18ke90udyp67", - "link": "types-and-traits.html#fls_18ke90udyp67", - "number": "4.11.1:23" + "checksum": "4aef617035a62c693b0db6e84d556a1afe10c28af4f9b0ca18065503767f6015", + "id": "fls_3jyog8n6x2aa", + "link": "patterns.html#fls_3jyog8n6x2aa", + "number": "5.13.1:3" }, { - "checksum": "16f17b053148dcb88c3d4eac54d2fa3ab5c26241239c5f849b5e78a7c1987c27", - "id": "fls_nrqG8i3fmpm4", - "link": "types-and-traits.html#fls_nrqG8i3fmpm4", - "number": "4.11.1:24" + "checksum": "0db8e23416d53e90ad73fa4df5b10d7e3ef8d53418d42a8860380a85d5d1163a", + "id": "fls_w637uvlbzsyo", + "link": "patterns.html#fls_w637uvlbzsyo", + "number": "5.13.1:4" }, { - "checksum": "61615c593b3ab4fbf2ecc57b3474628e35997aae869a5e77a7db2c1d329c4027", - "id": "fls_e5hivr6m5s3h", - "link": "types-and-traits.html#fls_e5hivr6m5s3h", - "number": "4.11.1:25" + "checksum": "1cf4f4074ffc3e6745944b1dcd726ac4b7d65f44568c669f04ddfcbe8068d57e", + "id": "fls_arz8ik3gf6u4", + "link": "patterns.html#fls_arz8ik3gf6u4", + "number": "5.13.1:5" }, { - "checksum": "10f4ec2a3b2055e8ac3f6ac122c55a3d72a69b32bc92531cb8cd3174405fc276", - "id": "fls_hlbsjggfxnt2", - "link": "types-and-traits.html#fls_hlbsjggfxnt2", - "number": "4.11.1:26" + "checksum": "5003a32c5e8a49a9e7d778d70ff05edcf2411bd284272a8bf7bd04d2df3a40fe", + "id": "fls_u6o5ndnezwbe", + "link": "patterns.html#fls_u6o5ndnezwbe", + "number": "5.13.1:6" }, { - "checksum": "e182b27b475774c568aecdb1bb2f1070177f14e235e96672e4884e9085b0e263", - "id": "fls_sdrb0k2r18my", - "link": "types-and-traits.html#fls_sdrb0k2r18my", - "number": "4.11.1:27" + "checksum": "de66751e709611b36e4517f705acf75d052724e2501a8c5ee5ab8c40757115e5", + "id": "fls_h1er04t0yta7", + "link": "patterns.html#fls_h1er04t0yta7", + "number": "5.13.1:7" + } + ], + "title": "Identifier Pattern Matching" + }, + { + "id": "fls_azzf1llv3wf", + "informational": false, + "link": "patterns.html#literal-pattern-matching", + "number": "5.13.2", + "paragraphs": [ + { + "checksum": "ffb06275d46d70835ab2e118cff4b0071d02544ee94a260f428fd2b5533bed1c", + "id": "fls_fqkhhgushje9", + "link": "patterns.html#fls_fqkhhgushje9", + "number": "5.13.2:1" }, { - "checksum": "69bc4664319d5665fede7b3cce71149d020a6355095a53a2e4628588ed83b8a2", - "id": "fls_gt3tkbn4bsa6", - "link": "types-and-traits.html#fls_gt3tkbn4bsa6", - "number": "4.11.1:28" + "checksum": "d04dcff15efbb13e790e1f78948bf446426570ec9618c3b921293adfc2012199", + "id": "fls_m01eo9sa55s", + "link": "patterns.html#fls_m01eo9sa55s", + "number": "5.13.2:2" }, { - "checksum": "bda0b012a16d3a292ca3bd3cd3bf7397fc037ec2df84e2d98d42f8caad457108", - "id": "fls_njvdevz0xqc0", - "link": "types-and-traits.html#fls_njvdevz0xqc0", - "number": "4.11.1:29" + "checksum": "fe6504c691ded75e993e4d66b216f7260d30ac52c0a60e35151af31c01905992", + "id": "fls_294jtwbfq3p9", + "link": "patterns.html#fls_294jtwbfq3p9", + "number": "5.13.2:3" } ], - "title": "Type Layout" + "title": "Literal Pattern Matching" }, { - "id": "fls_ohhsmifo0urd", + "id": "fls_5loglxds6zik", "informational": false, - "link": "types-and-traits.html#type-representation", - "number": "4.11.2", + "link": "patterns.html#parenthesized-pattern-matching", + "number": "5.13.3", "paragraphs": [ { - "checksum": "5f5949a619afc73e47aec91fbc3a9a63a26a1c89ac23fff54dedd659fa5ce152", - "id": "fls_mpqlyi3lgrfv", - "link": "types-and-traits.html#fls_mpqlyi3lgrfv", - "number": "4.11.2:1" - }, - { - "checksum": "b57e3b6dad3a8e0cb95ec2f29b9c0c03d35a3d2274ce1372f790c18e6d8fe08a", - "id": "fls_9dhnanv21y9z", - "link": "types-and-traits.html#fls_9dhnanv21y9z", - "number": "4.11.2:2" - }, - { - "checksum": "9af98560b964faa10575d6b41e63eab6392b21e562bd53d52334986e8d562170", - "id": "fls_3dwtkr7vzha0", - "link": "types-and-traits.html#fls_3dwtkr7vzha0", - "number": "4.11.2:3" - }, - { - "checksum": "4e17b1a0b34e4bf31d8a8074de82b29eab2771c248d198c7505d85d025d02401", - "id": "fls_q465p1xuzxi", - "link": "types-and-traits.html#fls_q465p1xuzxi", - "number": "4.11.2:4" - }, - { - "checksum": "5f34cbfe6622d2c818a60366091bc5da0bc271de40adb90bbb62a83edca10e7e", - "id": "fls_hrsdn21jmgx2", - "link": "types-and-traits.html#fls_hrsdn21jmgx2", - "number": "4.11.2:5" - }, + "checksum": "7f3d87f0e2bf7a6dc15e6e0bcda2506008be955f016459a792ea2b99d16bacbf", + "id": "fls_jajvvwoy3399", + "link": "patterns.html#fls_jajvvwoy3399", + "number": "5.13.3:1" + } + ], + "title": "Parenthesized Pattern Matching" + }, + { + "id": "fls_d44aflefat88", + "informational": false, + "link": "patterns.html#path-pattern-matching", + "number": "5.13.4", + "paragraphs": [ { - "checksum": "6d966d386110f51effa3166dbdfd2297301ac38191e2cf2c2fb7bd2786dfaec6", - "id": "fls_ergdb18tpx25", - "link": "types-and-traits.html#fls_ergdb18tpx25", - "number": "4.11.2:6" + "checksum": "b62c95bf3039a32f793c95cd983bcc5cd80b8faeb5d469cc963c3a487f437fae", + "id": "fls_4faltss0xbn4", + "link": "patterns.html#fls_4faltss0xbn4", + "number": "5.13.4:1" }, { - "checksum": "e149df36aa01e86286388dbbe81e921138ff3db34c7e3b70fe56471e82478501", - "id": "fls_8s1vddh8vdhy", - "link": "types-and-traits.html#fls_8s1vddh8vdhy", - "number": "4.11.2:7" + "checksum": "ee205e802f76cf743647cd0b8c7be7f9d118ea51ef62a9f8ccd237ee2a7c874c", + "id": "fls_fqt5w3qsykca", + "link": "patterns.html#fls_fqt5w3qsykca", + "number": "5.13.4:2" }, { - "checksum": "eccb8cf4151d93a2f40c470c83debcdb0664c35fb9b1a56d77688b9bbc34fa14", - "id": "fls_b005bktrkrxy", - "link": "types-and-traits.html#fls_b005bktrkrxy", - "number": "4.11.2:8" - }, + "checksum": "10a9ff549fbafbc317a299a46f8f61ed3ec121a3f1c97d94c1a3ae2aeee961f7", + "id": "fls_h3y8r4298s53", + "link": "patterns.html#fls_h3y8r4298s53", + "number": "5.13.4:3" + } + ], + "title": "Path Pattern Matching" + }, + { + "id": "fls_fyskeih6twyb", + "informational": false, + "link": "patterns.html#range-pattern-matching", + "number": "5.13.5", + "paragraphs": [ { - "checksum": "2df37a1590825b869d3fe88027b64189cf4a81a9bf29a7fbcb2ba1790f600026", - "id": "fls_7plbkqlmed0r", - "link": "types-and-traits.html#fls_7plbkqlmed0r", - "number": "4.11.2:9" + "checksum": "74c808f389212d771098798db71f913b01fad773d198d3babf0a21ae6f4ce792", + "id": "fls_mrh9vfdek5fi", + "link": "patterns.html#fls_mrh9vfdek5fi", + "number": "5.13.5:1" }, { - "checksum": "ab1548f30cb5de39a5164639b856c80192fe4565c992ebc857d91f330cfd95b1", - "id": "fls_ml4khttq3w5k", - "link": "types-and-traits.html#fls_ml4khttq3w5k", - "number": "4.11.2:10" + "checksum": "4e77c3805f9c1fde39877a54a3b99be7582ff7dde64d6f8b033465fcfb625c8c", + "id": "fls_7nxkgls0a5os", + "link": "patterns.html#fls_7nxkgls0a5os", + "number": "5.13.5:2" }, { - "checksum": "7705e77c0b72bbab2b260c177130bc9c9b2b0cd780adfef1c71bef4a3fa35b1c", - "id": "fls_9q2iqzbup8oy", - "link": "types-and-traits.html#fls_9q2iqzbup8oy", - "number": "4.11.2:11" + "checksum": "fd8fba85c57e8cde11619040e0cf63b8bee10a4ad77a5a663655cf44f7527d00", + "id": "fls_6kgj2fjccoig", + "link": "patterns.html#fls_6kgj2fjccoig", + "number": "5.13.5:3" }, { - "checksum": "bcb966f534ed9405fc751a9c095cd83a0ebf9e78d42f0fa542522fc9ba0ec64f", - "id": "fls_fsbf6ist38ix", - "link": "types-and-traits.html#fls_fsbf6ist38ix", - "number": "4.11.2:12" + "checksum": "6ceef4a4fb5e7197f8e4fbeea43c666aa1ee532e0e198a0242be6ab5dca9d26c", + "id": "fls_EDL1Pi56KQ2H", + "link": "patterns.html#fls_EDL1Pi56KQ2H", + "number": "5.13.5:4" }, { - "checksum": "6ea20905fb18b0e4a39823e4d29c64d3000f65f803a757758399867d375784b8", - "id": "fls_qkkc8x2oghst", - "link": "types-and-traits.html#fls_qkkc8x2oghst", - "number": "4.11.2:13" + "checksum": "7ee404fd4fb97fe0f1daafdb86a76994a1056bc3cbc4571522f37b405f651572", + "id": "fls_n4t3xah1pk7i", + "link": "patterns.html#fls_n4t3xah1pk7i", + "number": "5.13.5:5" } ], - "title": "Type Representation" + "title": "Range Pattern Matching" }, { - "id": "fls_xc1hof4qbf6p", + "id": "fls_org6hqv397fp", "informational": false, - "link": "types-and-traits.html#enum-type-representation", - "number": "4.11.2.1", + "link": "patterns.html#reference-pattern-matching", + "number": "5.13.6", "paragraphs": [ { - "checksum": "3eb2acc0e91b94225e6e44043b9ed3ef348d1fbdfb8db38506d619d240c6f6e5", - "id": "fls_p0c62ejo1u1t", - "link": "types-and-traits.html#fls_p0c62ejo1u1t", - "number": "4.11.2.1:1" + "checksum": "b6dad8a8eb45a793423595800323ed5f357d864241174e7198f4101b4d5c9c9b", + "id": "fls_ysfgdzaiww8z", + "link": "patterns.html#fls_ysfgdzaiww8z", + "number": "5.13.6:1" }, { - "checksum": "9223cf2e73ff1760ca4c9edcf7ea6c2b6a71a16c836006c862557a4a50880a0b", - "id": "fls_efp1kfgkpba8", - "link": "types-and-traits.html#fls_efp1kfgkpba8", - "number": "4.11.2.1:2" + "checksum": "9932447e7ad4cb062a332bea07f5eb902fa9f0482de8917d2a3bee05f86c2fdf", + "id": "fls_7rxnxd4ybxbt", + "link": "patterns.html#fls_7rxnxd4ybxbt", + "number": "5.13.6:2" }, { - "checksum": "596c70e3ed03568cc8842491654b253cff2dd3c53ecff886f8790626e79cdf54", - "id": "fls_s9c0a0lg6c0p", - "link": "types-and-traits.html#fls_s9c0a0lg6c0p", - "number": "4.11.2.1:3" - }, + "checksum": "b76bff46c3fb725fb95fd036da6eec2951e358e24662e16fe7a2b277948422eb", + "id": "fls_l2nwz166curc", + "link": "patterns.html#fls_l2nwz166curc", + "number": "5.13.6:3" + } + ], + "title": "Reference Pattern Matching" + }, + { + "id": "fls_57ic33pwdvp3", + "informational": false, + "link": "patterns.html#slice-pattern-matching", + "number": "5.13.7", + "paragraphs": [ { - "checksum": "a91c1cca73e27a37a40cfe16ebf7b3dd4618e680e133ba6257ba1c08b26b66a6", - "id": "fls_slhvf3gmqz4h", - "link": "types-and-traits.html#fls_slhvf3gmqz4h", - "number": "4.11.2.1:4" + "checksum": "545a7b4ddaf1e7bfb2237db616a2cf9a24e53676e02591cec035c44ace0b0fd1", + "id": "fls_hzyv4ofu0ny", + "link": "patterns.html#fls_hzyv4ofu0ny", + "number": "5.13.7:1" }, { - "checksum": "fea4e03795d124973a8ffab094f6ee265a8f520b32a2c7d748015882a86761cc", - "id": "fls_u1zy06510m56", - "link": "types-and-traits.html#fls_u1zy06510m56", - "number": "4.11.2.1:5" + "checksum": "a08c2a45fe77ae5f047f5e6cf64b8f743c4433b940e5f5fb0245051c079e7e8a", + "id": "fls_x10AKxoXXbs8", + "link": "patterns.html#fls_x10AKxoXXbs8", + "number": "5.13.7:2" }, { - "checksum": "8a55fc830de2323e466a47edb68b3963f8dbb9972e1194ecb80a9c6b4a9252fd", - "id": "fls_ryvqkcx48u74", - "link": "types-and-traits.html#fls_ryvqkcx48u74", - "number": "4.11.2.1:6" + "checksum": "00b9a97b9280c3716ddfa172c721a2bf0e2081a9fa912cffde3cda55d1197952", + "id": "fls_69bnxrtj0nar", + "link": "patterns.html#fls_69bnxrtj0nar", + "number": "5.13.7:3" }, { - "checksum": "9511193dd174fc3bafe80e8350dddde4679a8741fb947fe3f5e1e891a33d51ae", - "id": "fls_zhle0rb0vhpc", - "link": "types-and-traits.html#fls_zhle0rb0vhpc", - "number": "4.11.2.1:7" + "checksum": "c48b717f055594a928a658edaf02b1fabf8a096bdefdb3fbc8f24437462635da", + "id": "fls_twhwiy213ibf", + "link": "patterns.html#fls_twhwiy213ibf", + "number": "5.13.7:4" }, { - "checksum": "f51fdfab79671ea2955e16f13e207e87820a37d5f55b1d4c22064de5c137211a", - "id": "fls_45f57s1gmmh5", - "link": "types-and-traits.html#fls_45f57s1gmmh5", - "number": "4.11.2.1:8" + "checksum": "3f7ea24067807e7941333db26d088679f21fbd2c64a2407cf545ff6861841872", + "id": "fls_ei7y4ul6n6hu", + "link": "patterns.html#fls_ei7y4ul6n6hu", + "number": "5.13.7:5" }, { - "checksum": "b8004c9524d9dcc6e7c1fad6f95241fbb7388c13ce2a105eea1f0fe9fc97517a", - "id": "fls_hz012yus6b4g", - "link": "types-and-traits.html#fls_hz012yus6b4g", - "number": "4.11.2.1:9" + "checksum": "4925bc389627a9c0258c8b5aa732c62f27ea3c62e2b3c0bbfd6e8f9c1350920e", + "id": "fls_ad2jud5h1rfp", + "link": "patterns.html#fls_ad2jud5h1rfp", + "number": "5.13.7:6" }, { - "checksum": "b6f79b18180060bbc48e3b28c798cfabb1fbe4261d4f29db7e66461a870e1b05", - "id": "fls_q5akku2idrwh", - "link": "types-and-traits.html#fls_q5akku2idrwh", - "number": "4.11.2.1:10" + "checksum": "0a3498e1551afa3c7f492391c8187746fccf5ebb43ae48f55741d4dd39aaceab", + "id": "fls_pc97m47p34wq", + "link": "patterns.html#fls_pc97m47p34wq", + "number": "5.13.7:7" }, { - "checksum": "7f5298c7fcf4553ee2096af2930625f9d951363c4f6529b7d5d99c07b1fc520f", - "id": "fls_r6o1wv76yw6m", - "link": "types-and-traits.html#fls_r6o1wv76yw6m", - "number": "4.11.2.1:11" + "checksum": "ff3c524edac17baef16b63f1da2eead531f9c065f671838de8c437b87e87f859", + "id": "fls_kwQyiSoyAwZ8", + "link": "patterns.html#fls_kwQyiSoyAwZ8", + "number": "5.13.7:8" }, { - "checksum": "939150187e841861875019dc485b2aa948610bdabcf75995a8afad5299edfc9c", - "id": "fls_3k1tcfxp0g63", - "link": "types-and-traits.html#fls_3k1tcfxp0g63", - "number": "4.11.2.1:12" + "checksum": "624d69de485ef8707d6d98a26dcdfd92081861894d0c06dba07882fe19de3ee2", + "id": "fls_zAdtysiuUwBX", + "link": "patterns.html#fls_zAdtysiuUwBX", + "number": "5.13.7:9" }, { - "checksum": "89652dc80a444cd6f5989de7e2a59fcbee6155ba69450db9b25c7c463673bdf4", - "id": "fls_ebs77rxvk9st", - "link": "types-and-traits.html#fls_ebs77rxvk9st", - "number": "4.11.2.1:13" + "checksum": "8f94a20f9d18e18084cc5e7931d668de6254cce6b14813fce2f2a16d4a699598", + "id": "fls_SezcYXcSlEq7", + "link": "patterns.html#fls_SezcYXcSlEq7", + "number": "5.13.7:10" }, { - "checksum": "8bea878c1256e24db0fc455c3c525bca9c6c94300cfac8d7f432673f4a43fafd", - "id": "fls_k907i6w83s2", - "link": "types-and-traits.html#fls_k907i6w83s2", - "number": "4.11.2.1:14" + "checksum": "f14008365b9b2173763090e4ac8e371acae7932276efa64dd5c4bef234a9b625", + "id": "fls_6xRXEt2pGnZi", + "link": "patterns.html#fls_6xRXEt2pGnZi", + "number": "5.13.7:11" } ], - "title": "Enum Type Representation" + "title": "Slice Pattern Matching" }, { - "id": "fls_rjxpof29a3nl", + "id": "fls_asj8rgccvkoe", "informational": false, - "link": "types-and-traits.html#struct-type-representation", - "number": "4.11.2.2", + "link": "patterns.html#record-struct-pattern-matching", + "number": "5.13.8", "paragraphs": [ { - "checksum": "5f76aa9810ba03c28ebf2942db3a8fb181ab22d0b664086601393227aa7bc9eb", - "id": "fls_jr9dykj6rydn", - "link": "types-and-traits.html#fls_jr9dykj6rydn", - "number": "4.11.2.2:1" + "checksum": "a64d7a8315d7d5f47ec10463a26465d1b20225bc5c426030b5af68285a662722", + "id": "fls_evuhau2rwm8i", + "link": "patterns.html#fls_evuhau2rwm8i", + "number": "5.13.8:1" }, { - "checksum": "5fc0a7214a5fa354bcf5cb9aca684565b666321cce8849cf8e89065c73dca2bc", - "id": "fls_6ck71twmnbg5", - "link": "types-and-traits.html#fls_6ck71twmnbg5", - "number": "4.11.2.2:2" + "checksum": "202123ed4c141cd00114f671cc7b4a9caaafd86ff150c865e93c8e9dad4257fc", + "id": "fls_bde1hpvrosui", + "link": "patterns.html#fls_bde1hpvrosui", + "number": "5.13.8:2" }, { - "checksum": "cc7a74a98eb6db165a74e65d78a53928cd6ceb8d9b8f47c5549d190d9c561b5d", - "id": "fls_hydq3pvm00bn", - "link": "types-and-traits.html#fls_hydq3pvm00bn", - "number": "4.11.2.2:3" + "checksum": "d0da6696f0030559710cd9ee013efb4dbfb95b1df235fe5f469bfcd2d24034c4", + "id": "fls_447s4hc07ozn", + "link": "patterns.html#fls_447s4hc07ozn", + "number": "5.13.8:3" }, { - "checksum": "f0a3247f449084aa9aae9debf4ffa6a6913bb680bf0b458cb343993f72476848", - "id": "fls_yzcdffahxcz", - "link": "types-and-traits.html#fls_yzcdffahxcz", - "number": "4.11.2.2:4" + "checksum": "b11dc2c5dfcaf758c45e4029e582f72a53c74e7c2c98575fb2b7461ae1e7d805", + "id": "fls_vfdb1i5l41yk", + "link": "patterns.html#fls_vfdb1i5l41yk", + "number": "5.13.8:4" }, { - "checksum": "f655a5cbd5d73a7e395bd286c59614f96f179cfba822ade23467a58dd59c9416", - "id": "fls_t2yqmphfd6he", - "link": "types-and-traits.html#fls_t2yqmphfd6he", - "number": "4.11.2.2:5" + "checksum": "36081581a365a6503d56139e07b8e1749757cf878634d805e84663d3172cb005", + "id": "fls_yfk52fr7trw3", + "link": "patterns.html#fls_yfk52fr7trw3", + "number": "5.13.8:5" }, { - "checksum": "8f137c8f4826a9313a42e8c5bec10771adb0f8414465461fd20d604736c77eea", - "id": "fls_fa5nkvu07jlp", - "link": "types-and-traits.html#fls_fa5nkvu07jlp", - "number": "4.11.2.2:6" - }, + "checksum": "8b8f1725d0cf2ef6489bd548eec9d59fab537eca1f33a509f8141751325cba76", + "id": "fls_6sdcykdrpe5d", + "link": "patterns.html#fls_6sdcykdrpe5d", + "number": "5.13.8:6" + } + ], + "title": "Record Struct Pattern Matching" + }, + { + "id": "fls_eexupzdsu7f", + "informational": false, + "link": "patterns.html#tuple-struct-pattern-matching", + "number": "5.13.9", + "paragraphs": [ { - "checksum": "1d2016f00c240d05addd5fab89f965d3641505505bfb01110d95a59a9d0abfda", - "id": "fls_x2pkmgbp63xx", - "link": "types-and-traits.html#fls_x2pkmgbp63xx", - "number": "4.11.2.2:7" + "checksum": "ed45c176d905341bee988af8640b3011dd6a97d9c8fe53b45410925a79d5e056", + "id": "fls_dexg9g9cct30", + "link": "patterns.html#fls_dexg9g9cct30", + "number": "5.13.9:1" }, { - "checksum": "a43584acfcb4f9504ce4581592588e3c52f9f07bc3fac6200626820608fe115a", - "id": "fls_y6dwc1ndm395", - "link": "types-and-traits.html#fls_y6dwc1ndm395", - "number": "4.11.2.2:8" + "checksum": "03c31f4e25eebbe78169c4bc173609effc1e8269dfe4f263b29363409e316dab", + "id": "fls_boc7juqj69hw", + "link": "patterns.html#fls_boc7juqj69hw", + "number": "5.13.9:2" }, { - "checksum": "03a369fe556766892948f430b4527499cd07a737e8ebb566d9f4935b1ed0bd81", - "id": "fls_2npku94ookdn", - "link": "types-and-traits.html#fls_2npku94ookdn", - "number": "4.11.2.2:9" + "checksum": "6b75afdc8b9b09fcd7df94b0c730c87e30d4bb2de200714adf3a08575e90a90a", + "id": "fls_4dr1stiw82v9", + "link": "patterns.html#fls_4dr1stiw82v9", + "number": "5.13.9:3" }, { - "checksum": "53b32f8e3a420b85e3831feb7488281662c227e49e7585de6de3f2147d00ec6d", - "id": "fls_h7nvs25rsi0y", - "link": "types-and-traits.html#fls_h7nvs25rsi0y", - "number": "4.11.2.2:10" - }, + "checksum": "13343ca1b2a108f58c91e7fcdb4c9e4c975f5fa11d841db4951a8883a4bcc7d5", + "id": "fls_h14emtt6iyk3", + "link": "patterns.html#fls_h14emtt6iyk3", + "number": "5.13.9:4" + } + ], + "title": "Tuple Struct Pattern Matching" + }, + { + "id": "fls_rce8bb7nz2jy", + "informational": false, + "link": "patterns.html#tuple-pattern-matching", + "number": "5.13.10", + "paragraphs": [ { - "checksum": "81829e6f383a4c65151f020b3440763a7261c3898230a6504f0d83bbb62f8f40", - "id": "fls_iu93vpyihrpj", - "link": "types-and-traits.html#fls_iu93vpyihrpj", - "number": "4.11.2.2:11" + "checksum": "746ffe5085ba7cfbb59d3c44688b6f8584fa574128327bb37f2f051173efb07d", + "id": "fls_w4xypnrnhycb", + "link": "patterns.html#fls_w4xypnrnhycb", + "number": "5.13.10:1" }, { - "checksum": "779b7205751c55010c330675d65578fe47e6bdaefe5a76eef9dc878a6ad61921", - "id": "fls_7sjkej5otxo", - "link": "types-and-traits.html#fls_7sjkej5otxo", - "number": "4.11.2.2:12" + "checksum": "5f1c0e7fad583c2e4331c5805f1dbf89717118d493a01549a42dfeaa3f0279ca", + "id": "fls_vnx1bpval595", + "link": "patterns.html#fls_vnx1bpval595", + "number": "5.13.10:2" }, { - "checksum": "83a4d8e45980a24abbd901d57911b4c082699c53464ebbe23837f6862ff654c0", - "id": "fls_gwhceoy0m3or", - "link": "types-and-traits.html#fls_gwhceoy0m3or", - "number": "4.11.2.2:13" + "checksum": "e86a7e2ae632d8ce6ffaeb404902846bb48e160aab297c2c2652d9b985f7982e", + "id": "fls_dzf32f40y7fr", + "link": "patterns.html#fls_dzf32f40y7fr", + "number": "5.13.10:3" }, { - "checksum": "cc35a963cac35f4ca35b83a2c99092463e9f24c42b164f201487fd81193c4138", - "id": "fls_hvkalvr4e2v0", - "link": "types-and-traits.html#fls_hvkalvr4e2v0", - "number": "4.11.2.2:14" + "checksum": "37eed365431808077ba3f82d504515f343520002fd244c7649305e39f6c96d7e", + "id": "fls_krl32txvxxkz", + "link": "patterns.html#fls_krl32txvxxkz", + "number": "5.13.10:4" } ], - "title": "Struct Type Representation" + "title": "Tuple Pattern Matching" }, { - "id": "fls_cmq8ogs84ivh", + "id": "fls_yc4xm4hrfyw7", "informational": false, - "link": "types-and-traits.html#union-type-representation", - "number": "4.11.2.3", + "link": "patterns.html#underscore-pattern-matching", + "number": "5.13.11", "paragraphs": [ { - "checksum": "7acef59265b9dc0eb9021e3c4144a4243c0f3bcc0119c1cd151745c380b1bd69", - "id": "fls_opz1p1neldsg", - "link": "types-and-traits.html#fls_opz1p1neldsg", - "number": "4.11.2.3:1" + "checksum": "b9ccebce07421997dfd958ff40f0aef3a1b9e546fb3bcf0c2cefe7fbfdd64d01", + "id": "fls_dvk7r1gf7pwp", + "link": "patterns.html#fls_dvk7r1gf7pwp", + "number": "5.13.11:1" }, { - "checksum": "151cb09b57de0325104c84d42d1aac2b8865f4dc1fdbf7fc0da0db0967ba3270", - "id": "fls_y5qtvbx5m90g", - "link": "types-and-traits.html#fls_y5qtvbx5m90g", - "number": "4.11.2.3:2" + "checksum": "33c34bf81e9f7a3651a3718b8ab5b88c0ba416ea85e283b4fbd4a7e30e78b48a", + "id": "fls_e0uprihqn1y6", + "link": "patterns.html#fls_e0uprihqn1y6", + "number": "5.13.11:2" + }, + { + "checksum": "8e3a3ff978133c3bfffe09986eace47f5b4afb21f6c71af39fd93f4f27ad2860", + "id": "fls_ljcq2vyo052q", + "link": "patterns.html#fls_ljcq2vyo052q", + "number": "5.13.11:3" } ], - "title": "Union Type Representation" - }, + "title": "Underscore Pattern Matching" + } + ], + "title": "Patterns" + }, + { + "informational": false, + "link": "ownership-and-deconstruction.html", + "sections": [ { - "id": "fls_j02707n615z0", + "id": "fls_ronnwodjjjsh", "informational": false, - "link": "types-and-traits.html#type-model", - "number": "4.12", + "link": "ownership-and-deconstruction.html", + "number": "15", "paragraphs": [], - "title": "Type Model" + "title": "Ownership and Destruction" }, { - "id": "fls_3gapgqys3ceb", + "id": "fls_svkx6szhr472", "informational": false, - "link": "types-and-traits.html#recursive-types", - "number": "4.12.1", + "link": "ownership-and-deconstruction.html#ownership", + "number": "15.1", + "paragraphs": [ + { + "checksum": "5144c7d76d836132f4544481a5a0aa566e56d0824bc6757bb51dfdf413f2809e", + "id": "fls_wt81sbsecmu0", + "link": "ownership-and-deconstruction.html#fls_wt81sbsecmu0", + "number": "15.1:1" + }, + { + "checksum": "317ff352b8f0f3c112a25e2c334aa6f7e2c227140ef1153851b0068b65544c4b", + "id": "fls_ckcnkbb6y3cq", + "link": "ownership-and-deconstruction.html#fls_ckcnkbb6y3cq", + "number": "15.1:2" + }, + { + "checksum": "5aae1e903f79062d2e7d47807976de140feb73932e1bd4d1db63f2eda9a280e8", + "id": "fls_ze0u9gfylmhn", + "link": "ownership-and-deconstruction.html#fls_ze0u9gfylmhn", + "number": "15.1:3" + } + ], + "title": "Ownership" + }, + { + "id": "fls_3xvm61x0t251", + "informational": false, + "link": "ownership-and-deconstruction.html#initialization", + "number": "15.2", "paragraphs": [ { - "checksum": "740cfb5bd47f58876ee5586174ee7d3acae1e4bdfac31a402c4461aafee6966f", - "id": "fls_z22std1crl49", - "link": "types-and-traits.html#fls_z22std1crl49", - "number": "4.12.1:1" + "checksum": "e347ab2d0c9e7fb6d14b8e7ee107a2220a39391724c13eeaab6b25939b1be2d9", + "id": "fls_drfzu02bo7oe", + "link": "ownership-and-deconstruction.html#fls_drfzu02bo7oe", + "number": "15.2:1" + }, + { + "checksum": "6ee782d7b7403e35dd81c6622fb5562c3c6116ef95eca20a6496d36f6acfe442", + "id": "fls_wnhci8phdu4m", + "link": "ownership-and-deconstruction.html#fls_wnhci8phdu4m", + "number": "15.2:2" + }, + { + "checksum": "ba3e26bd692acacc422e0367466b77045e1802b133f0ba5cdf155ae1e2c0b5d4", + "id": "fls_ch2lvm50olqd", + "link": "ownership-and-deconstruction.html#fls_ch2lvm50olqd", + "number": "15.2:3" + }, + { + "checksum": "b86bc58a37e2d666845ddee6d0ec0fd647264ab45a39b676bc6bf201782eb03a", + "id": "fls_46910buiwvv9", + "link": "ownership-and-deconstruction.html#fls_46910buiwvv9", + "number": "15.2:4" + }, + { + "checksum": "5948683124b4a837517cb460bac495efff05e298bed8420a688b19d16de45eb2", + "id": "fls_caufcwkpz689", + "link": "ownership-and-deconstruction.html#fls_caufcwkpz689", + "number": "15.2:5" + }, + { + "checksum": "0cd3d0fa123e7c11a9a250acaa0cce74c5f8760b6099acac8e084cea9be81ebd", + "id": "fls_4lg92b9yima6", + "link": "ownership-and-deconstruction.html#fls_4lg92b9yima6", + "number": "15.2:6" }, { - "checksum": "3f12892eabfac9dd07124c6ca395e4ab531b1edab32e93dbf13a9236a105def9", - "id": "fls_eddnwlr0rz59", - "link": "types-and-traits.html#fls_eddnwlr0rz59", - "number": "4.12.1:2" + "checksum": "063dff1709d956a48b2bb9876a5fabfda25c3ed4b35d8449d19f6163c27811c5", + "id": "fls_xn1au0blioa3", + "link": "ownership-and-deconstruction.html#fls_xn1au0blioa3", + "number": "15.2:7" + }, + { + "checksum": "4f92e492c63996bf71cb5f3ffb86c48dd7a928decb62b7ea6fa936acfbef7a78", + "id": "fls_jmcjboopvytb", + "link": "ownership-and-deconstruction.html#fls_jmcjboopvytb", + "number": "15.2:8" } ], - "title": "Recursive Types" + "title": "Initialization" }, { - "id": "fls_exe4zodlwfez", + "id": "fls_v5x85lt5ulva", "informational": false, - "link": "types-and-traits.html#type-unification", - "number": "4.12.2", + "link": "ownership-and-deconstruction.html#references", + "number": "15.3", "paragraphs": [ { - "checksum": "5df68e7a28d0bf034a911d0f1ebabc63d83df1f144975513612741de324e087a", - "id": "fls_ryvdhkgm7vzj", - "link": "types-and-traits.html#fls_ryvdhkgm7vzj", - "number": "4.12.2:1" + "checksum": "681fcd31b82b3e32670aecd9ea9b4eb8068d07a7b2cdeaa6d9fb1442eb63ed86", + "id": "fls_7x9pi2o7pee7", + "link": "ownership-and-deconstruction.html#fls_7x9pi2o7pee7", + "number": "15.3:1" }, { - "checksum": "8efb96431fa13308cf8430377b0cc06be4e7e65b313828a429bfd56b64fbb69b", - "id": "fls_67VZrx6dw68H", - "link": "types-and-traits.html#fls_67VZrx6dw68H", - "number": "4.12.2:2" + "checksum": "1a8fde4fb240539c10a88279cc4dae22433448e6531485e2124cb8558d6c828c", + "id": "fls_tsqvr3fmcel", + "link": "ownership-and-deconstruction.html#fls_tsqvr3fmcel", + "number": "15.3:2" }, { - "checksum": "2cefde97b95cdce903b2bb16f1b7f2bb843cd1947a51d99f03b6edc576e8a815", - "id": "fls_aie0tr62vhw5", - "link": "types-and-traits.html#fls_aie0tr62vhw5", - "number": "4.12.2:3" + "checksum": "f967a5d91406384965a89b7d358d856f9b96ad76e062a2aa0cb0b238c5053633", + "id": "fls_ev4a82fdhwr8", + "link": "ownership-and-deconstruction.html#fls_ev4a82fdhwr8", + "number": "15.3:3" }, { - "checksum": "a1074c8cfb8f682fa21dc2aa4c3d2b500a67a107b90901fd69a32bd3ea7f487d", - "id": "fls_3U7Ue6Xzuv9M", - "link": "types-and-traits.html#fls_3U7Ue6Xzuv9M", - "number": "4.12.2:4" + "checksum": "bf9feda03dfdc034cc70417fab5c0fb8c43e76acb709672e810538a059178ac6", + "id": "fls_cckf6dtkgwb4", + "link": "ownership-and-deconstruction.html#fls_cckf6dtkgwb4", + "number": "15.3:4" }, { - "checksum": "930299ef90063f38b5762819de8b999c9d6d47c338fb35b2fea53ba6308b392c", - "id": "fls_tIiw5WkTRNf7", - "link": "types-and-traits.html#fls_tIiw5WkTRNf7", - "number": "4.12.2:5" + "checksum": "71b66b39ac5995dd5408a51d900c9e4a4c0b3f6de90df9f32eeaf0c9f79a747a", + "id": "fls_8kqb8754e6p4", + "link": "ownership-and-deconstruction.html#fls_8kqb8754e6p4", + "number": "15.3:5" }, { - "checksum": "f1514e494d536e424cc53a6d932b2f0efeae9c0c1c9d38cdff61bcb32368c8be", - "id": "fls_EoEbldkKBQW0", - "link": "types-and-traits.html#fls_EoEbldkKBQW0", - "number": "4.12.2:6" + "checksum": "0f5c895a4ac62ecc6ec04b6282dff022b535c2a19bd75b50defdbb28493da899", + "id": "fls_v69rptdjao42", + "link": "ownership-and-deconstruction.html#fls_v69rptdjao42", + "number": "15.3:6" }, { - "checksum": "9ce44d0db04150b232d3c66c01e88cb2a18d590a22685dfff432835fbeb60264", - "id": "fls_16ZDp8PaKi5P", - "link": "types-and-traits.html#fls_16ZDp8PaKi5P", - "number": "4.12.2:7" + "checksum": "f0435a5375c7104a39cbb51a31167f29e447129fae4e42da7755fd5b0d546300", + "id": "fls_vg9h6tz6z37w", + "link": "ownership-and-deconstruction.html#fls_vg9h6tz6z37w", + "number": "15.3:7" }, { - "checksum": "ca8ea0205df206f0258c062e6a5214044e0cea21feb89cea071059c7e56920ea", - "id": "fls_pGRLTACDvzv2", - "link": "types-and-traits.html#fls_pGRLTACDvzv2", - "number": "4.12.2:8" + "checksum": "94a208b99cd67ebc20f0f3baf1d225f9f8e7bd033c2196e35b0a01a1aa4c092d", + "id": "fls_wcf5mxrzbujn", + "link": "ownership-and-deconstruction.html#fls_wcf5mxrzbujn", + "number": "15.3:8" }, { - "checksum": "5856553d7c3dc3037b3f61286c79224e0a36daaa9f1fc4dbb8cfa32293193d99", - "id": "fls_fTy3FVt0fK9g", - "link": "types-and-traits.html#fls_fTy3FVt0fK9g", - "number": "4.12.2:9" + "checksum": "d346ee1754805482153eb4b0ca31367a70b20b0e6b21c40c6d681b5c9b3409cc", + "id": "fls_fckoj1jh5mrc", + "link": "ownership-and-deconstruction.html#fls_fckoj1jh5mrc", + "number": "15.3:9" }, { - "checksum": "35b4e67328db231c512e327ce5efb8ce05016ea1da017f138f262e2e2a227d6d", - "id": "fls_7IsrfUoPXSZU", - "link": "types-and-traits.html#fls_7IsrfUoPXSZU", - "number": "4.12.2:10" + "checksum": "e8deafe60392b60bd424d7aa0309647dbf4ed1e7325348f23f15970de9f05ebf", + "id": "fls_hqxsuyn285he", + "link": "ownership-and-deconstruction.html#fls_hqxsuyn285he", + "number": "15.3:10" }, { - "checksum": "324899ccd2204072a372947b966fee99e3024465753dcf20070c2c483dac6cff", - "id": "fls_Hb95CPyUpCmc", - "link": "types-and-traits.html#fls_Hb95CPyUpCmc", - "number": "4.12.2:11" + "checksum": "8d9978a993a2da848a7a6a9e23d189173372b34e01fa6d81ba7a78e75f621064", + "id": "fls_i1ny0k726a4a", + "link": "ownership-and-deconstruction.html#fls_i1ny0k726a4a", + "number": "15.3:11" }, { - "checksum": "b9d38d54656612478233c4d69d338dbc29e2284cc03c851ecf007157faabf4d9", - "id": "fls_jEZVWlfVPevb", - "link": "types-and-traits.html#fls_jEZVWlfVPevb", - "number": "4.12.2:12" + "checksum": "7ed63b28b2b7484eed35560cff9dd3420475b6cf99b8b7e943fe944ad33957d9", + "id": "fls_eT1hnLOx6vxk", + "link": "ownership-and-deconstruction.html#fls_eT1hnLOx6vxk", + "number": "15.3:12" + } + ], + "title": "References" + }, + { + "id": "fls_a14slch83hzn", + "informational": false, + "link": "ownership-and-deconstruction.html#borrowing", + "number": "15.4", + "paragraphs": [ + { + "checksum": "de2ae1e7180e12235906678b7ee4195bc10b9f11839f8d171f9c875c94521eca", + "id": "fls_c02flohk54pc", + "link": "ownership-and-deconstruction.html#fls_c02flohk54pc", + "number": "15.4:1" }, { - "checksum": "e1774f66d7ec71606db10f7d2ea8bb346f4838230ccab723b87cc20531f9c9e8", - "id": "fls_nKcqFo7yIDBe", - "link": "types-and-traits.html#fls_nKcqFo7yIDBe", - "number": "4.12.2:13" + "checksum": "437a4bdfe9c8e90e143c24e1ca0e4cb8226d0c5ca7f02bc701a25d5adc9b50c0", + "id": "fls_j9kof0px3l7s", + "link": "ownership-and-deconstruction.html#fls_j9kof0px3l7s", + "number": "15.4:2" }, { - "checksum": "fca446cea9b227ba6edbbd60b3d07b861687b7e6735b5d9430aad156d0c00cac", - "id": "fls_jkaiBnApJAtt", - "link": "types-and-traits.html#fls_jkaiBnApJAtt", - "number": "4.12.2:14" + "checksum": "35970b8df578395e654434eed83f9a06ec63a12c4bbf7e89bdfb97e21a7b9620", + "id": "fls_zepwytjwy049", + "link": "ownership-and-deconstruction.html#fls_zepwytjwy049", + "number": "15.4:3" }, { - "checksum": "5c234eaaaf625cadcba23e792b9c70b2a5d5a4bf733d3d1b874363f6cb4b4c23", - "id": "fls_dhksyjrvx9a", - "link": "types-and-traits.html#fls_dhksyjrvx9a", - "number": "4.12.2:15" + "checksum": "25fd85d527ce1e92367c4336524809c097742c03ed948860ca2f57d950ed5846", + "id": "fls_nordokzfy36d", + "link": "ownership-and-deconstruction.html#fls_nordokzfy36d", + "number": "15.4:4" }, { - "checksum": "71c2ef4eca01d8d2398c416f696fa413d2aa384ac63d110cf9c02fee44394c17", - "id": "fls_hf0cfkrmt655", - "link": "types-and-traits.html#fls_hf0cfkrmt655", - "number": "4.12.2:16" + "checksum": "3b7581ecddc33582b4cf58929333c27e97df213d56721343355ed7008eff2fa2", + "id": "fls_bjf3futso849", + "link": "ownership-and-deconstruction.html#fls_bjf3futso849", + "number": "15.4:5" }, { - "checksum": "688edd3c20f0031139a4a191d039bbc1cb4b3cf4e71953f32e59ca93e9e1eb50", - "id": "fls_k9dag68qpe93", - "link": "types-and-traits.html#fls_k9dag68qpe93", - "number": "4.12.2:17" + "checksum": "9d0d7ecaba0ada2895a59198323192c4c0562b596b94bea2c49be0968c22b934", + "id": "fls_yfmy4v5zlgw9", + "link": "ownership-and-deconstruction.html#fls_yfmy4v5zlgw9", + "number": "15.4:6" }, { - "checksum": "000140bda99fc03cb461ae82fc44f00553e759bdefc4024a743226869bb556d9", - "id": "fls_m6d9qj9q9u1i", - "link": "types-and-traits.html#fls_m6d9qj9q9u1i", - "number": "4.12.2:18" + "checksum": "20c3b044f90c2789882b18d6e87d0e3e641507a2d6af5c0de0ae5274181a7d06", + "id": "fls_jv18y618j2s3", + "link": "ownership-and-deconstruction.html#fls_jv18y618j2s3", + "number": "15.4:7" }, { - "checksum": "e80dd4b7ce61eb512368a598539097c63700b957a79797c7632b07f1140b827a", - "id": "fls_gg3x25qvymmq", - "link": "types-and-traits.html#fls_gg3x25qvymmq", - "number": "4.12.2:19" + "checksum": "1acb26f55ea7c004ebe759dee3d3aa058f514f0dc242d2f980413f9e5269d8e9", + "id": "fls_g4i0jb27iryr", + "link": "ownership-and-deconstruction.html#fls_g4i0jb27iryr", + "number": "15.4:8" }, { - "checksum": "f24aed54e03e3a739813f0003dfe5ba61269ea0b335234f058a75c2258a3a969", - "id": "fls_ni296ev8x9v9", - "link": "types-and-traits.html#fls_ni296ev8x9v9", - "number": "4.12.2:20" + "checksum": "f6b207663eafd42b6f7e8445b48751f5e9f3ca1c278b649e94105c3b0c9d95cb", + "id": "fls_kky9ufexrvaw", + "link": "ownership-and-deconstruction.html#fls_kky9ufexrvaw", + "number": "15.4:9" }, { - "checksum": "ae9c6921bc2c5d6daea20516afa9d8caa4b58b39286ba4971eebcfd1ab4c0cd8", - "id": "fls_i1m41c4wkfc0", - "link": "types-and-traits.html#fls_i1m41c4wkfc0", - "number": "4.12.2:21" + "checksum": "cbca8f374f46d1ef54e4cf6beede54e85406b5090edddcbec4fdff0d1c91b6cf", + "id": "fls_o5oq4jfswr4q", + "link": "ownership-and-deconstruction.html#fls_o5oq4jfswr4q", + "number": "15.4:10" }, { - "checksum": "e6f42ea036f99fbe3b1fb90f2cd2e17a9acf5ca1116043eca60f80c79867be2c", - "id": "fls_mpq64eal9jo3", - "link": "types-and-traits.html#fls_mpq64eal9jo3", - "number": "4.12.2:22" + "checksum": "7f48612f428ed200a7e825eccbdeb3e446b9610271bd51fd922132fbe878f65c", + "id": "fls_MT69AGRXH9pS_", + "link": "ownership-and-deconstruction.html#fls_MT69AGRXH9pS_", + "number": "15.4:11" }, { - "checksum": "00d3c7f7b35c66e24e4a10adce31978ab8ad1bd234a231761dcb86dd442da7b3", - "id": "fls_kcr8npsmy0e5", - "link": "types-and-traits.html#fls_kcr8npsmy0e5", - "number": "4.12.2:23" + "checksum": "958c02a18095324946b8dff0ea0a0e73b7cf7e255dffeaad921362798fa774a8", + "id": "fls_hyl4bdjbuzbw", + "link": "ownership-and-deconstruction.html#fls_hyl4bdjbuzbw", + "number": "15.4:12" }, { - "checksum": "195eb65f16abf0575d17f2b1be1087b9e206a49ffe16d3ffdef28cd89e788366", - "id": "fls_kq3lv1zbangz", - "link": "types-and-traits.html#fls_kq3lv1zbangz", - "number": "4.12.2:24" + "checksum": "72159700271717da9f616eff159a367b6f9b0637825319d7dec05001f401a601", + "id": "fls_pu19i4sj6yg0", + "link": "ownership-and-deconstruction.html#fls_pu19i4sj6yg0", + "number": "15.4:13" }, { - "checksum": "1d9c72ecb4ac5ea7fc4fc3d2d75b5180c4f4a47db55dce93873e90df42e46743", - "id": "fls_so2cgqmawlm7", - "link": "types-and-traits.html#fls_so2cgqmawlm7", - "number": "4.12.2:25" + "checksum": "358d780a2a3e8a51b742e63c460cc75632190978e72b9815081a4a81aa80761e", + "id": "fls_kxws4zmaahj6", + "link": "ownership-and-deconstruction.html#fls_kxws4zmaahj6", + "number": "15.4:14" }, { - "checksum": "408947791758b2ad8dcbc3ecdcf9c5f4c8644d67bdbff00b110458292dce4e83", - "id": "fls_vsax8w6y794m", - "link": "types-and-traits.html#fls_vsax8w6y794m", - "number": "4.12.2:26" + "checksum": "d898ade965c0b2fab9cda5bc5233816f4058b485ac65840643912b375625ae78", + "id": "fls_kup2ou22nwyl", + "link": "ownership-and-deconstruction.html#fls_kup2ou22nwyl", + "number": "15.4:15" + }, + { + "checksum": "cc4a5686c478a89047c0820552383801bd383016256838e3863695f994f8b314", + "id": "fls_8q5ly4x104ai", + "link": "ownership-and-deconstruction.html#fls_8q5ly4x104ai", + "number": "15.4:16" }, { - "checksum": "0c359ddaaaf4bb7ecdeda5b1ed77c19699bd50b53c7c9c180ab91fec01d5755a", - "id": "fls_1j1wc3uxs7h6", - "link": "types-and-traits.html#fls_1j1wc3uxs7h6", - "number": "4.12.2:27" + "checksum": "fb76d5f2ecb1ceb8b3aa709c4729fa2ce46d3bf98262aa34f8d39dcdabc2aa10", + "id": "fls_yhchu2bpil4m", + "link": "ownership-and-deconstruction.html#fls_yhchu2bpil4m", + "number": "15.4:17" }, { - "checksum": "6fed9a60e8fbe91b884ebe5950327802bbd098730e114fffad4de5c82403ecfe", - "id": "fls_9dpea9ty0c2l", - "link": "types-and-traits.html#fls_9dpea9ty0c2l", - "number": "4.12.2:28" + "checksum": "03dd78205c60d4db34d2c0df7ae70512ef3146fca9448710dd95263fcd3f6d2b", + "id": "fls_568o7nyihndd", + "link": "ownership-and-deconstruction.html#fls_568o7nyihndd", + "number": "15.4:18" }, { - "checksum": "8c89ff60eff543cbe33d8f313577bcc491d151988e3e14ec4a7c5f681d0fa8e2", - "id": "fls_42oj1ekjihq1", - "link": "types-and-traits.html#fls_42oj1ekjihq1", - "number": "4.12.2:29" + "checksum": "5812abb90fe17f8f0b9ec36241547534230a80cfd63db34d650ad71f90d18b2e", + "id": "fls_Z2n7EJiLY5CT", + "link": "ownership-and-deconstruction.html#fls_Z2n7EJiLY5CT", + "number": "15.4:19" }, { - "checksum": "c50b057136169daf940732bafe752d5fe2c443eb42159455d87b433a0de3e55a", - "id": "fls_gebpqqqvvklf", - "link": "types-and-traits.html#fls_gebpqqqvvklf", - "number": "4.12.2:30" + "checksum": "f183cb055a44d89fb2eb1b2848aa4536e06650cf6344a523690b83a29d829f61", + "id": "fls_ibi9mqGeHNLp", + "link": "ownership-and-deconstruction.html#fls_ibi9mqGeHNLp", + "number": "15.4:20" }, { - "checksum": "66ba5454612352fff011f2c5f16752b6851b3f672fadb2f8c15fb1c70eddb384", - "id": "fls_i221hm7rssik", - "link": "types-and-traits.html#fls_i221hm7rssik", - "number": "4.12.2:31" + "checksum": "fea10cfdc65f1e4d588fe2818f275ba47fdfcddd88877c316ca57791ba5de14f", + "id": "fls_f9we73i8vwq3", + "link": "ownership-and-deconstruction.html#fls_f9we73i8vwq3", + "number": "15.4:21" }, { - "checksum": "d54f897d06ae679945464622ed0a82379a5c0b60452ea600983b97b69902bbe5", - "id": "fls_74cug5zfv2wv", - "link": "types-and-traits.html#fls_74cug5zfv2wv", - "number": "4.12.2:32" + "checksum": "3e31ed577d258e6cdf238a0549751e8b896d9c2cb6ae51fc176c88803e14822f", + "id": "fls_w5bjgaov8w60", + "link": "ownership-and-deconstruction.html#fls_w5bjgaov8w60", + "number": "15.4:22" }, { - "checksum": "c9cabad366daa8e46a924d711447aac73dc93e60621905f3877aa65fc1a0724b", - "id": "fls_keezxl8v4snf", - "link": "types-and-traits.html#fls_keezxl8v4snf", - "number": "4.12.2:33" + "checksum": "c316a6c432e9e7d33fde24e96df550a3e74333f7b65cd38e5a7f799997d5db11", + "id": "fls_gbqizu6gu6kk", + "link": "ownership-and-deconstruction.html#fls_gbqizu6gu6kk", + "number": "15.4:23" }, { - "checksum": "104f0c1ee57173d019b23e0728dc5b6fb9bc04ec70b7666e5348f7dd366b45ed", - "id": "fls_wz2etmkpvxed", - "link": "types-and-traits.html#fls_wz2etmkpvxed", - "number": "4.12.2:34" + "checksum": "2a70cecfdc326c846957ef22ba6e0c76d8a4facbba9982e01c2fc53517b16b2b", + "id": "fls_ovkkxeybumvt", + "link": "ownership-and-deconstruction.html#fls_ovkkxeybumvt", + "number": "15.4:24" }, { - "checksum": "43358ae1fdf6051cf8ba48651a94f4d6b6e62ab05031e0b0e384a83724822952", - "id": "fls_rmqcbb5ja4va", - "link": "types-and-traits.html#fls_rmqcbb5ja4va", - "number": "4.12.2:35" + "checksum": "6a5074176f0372826a30df9614dbe4e54b3e06eab80a1ce84b7b9d7c94e3740d", + "id": "fls_7Kzu0qXiBUCb", + "link": "ownership-and-deconstruction.html#fls_7Kzu0qXiBUCb", + "number": "15.4:25" }, { - "checksum": "db5b765c1b3a6fcb40b42cccb46ae92a512f298b4eca532573060af03314af81", - "id": "fls_uu8je75y5pss", - "link": "types-and-traits.html#fls_uu8je75y5pss", - "number": "4.12.2:36" - }, + "checksum": "4478129ff7c47921151d2dcf57a362bba167ad848f1379667903982fe9676594", + "id": "fls_VDHBP4oAoqT5", + "link": "ownership-and-deconstruction.html#fls_VDHBP4oAoqT5", + "number": "15.4:26" + } + ], + "title": "Borrowing" + }, + { + "id": "fls_77scxuomlbgs", + "informational": false, + "link": "ownership-and-deconstruction.html#passing-conventions", + "number": "15.5", + "paragraphs": [ { - "checksum": "f397d8cf8b8c7c3fbcc901785345c36b908996d3a6bd6464e4e20cc108523a56", - "id": "fls_oksjiq3nmq2k", - "link": "types-and-traits.html#fls_oksjiq3nmq2k", - "number": "4.12.2:37" + "checksum": "ff08808a01947415fb2eb38d55e453078c88c4c344cfdd584e2fcd09ce24ec29", + "id": "fls_fvwx2ufeyzcs", + "link": "ownership-and-deconstruction.html#fls_fvwx2ufeyzcs", + "number": "15.5:1" }, { - "checksum": "81ebbf38df3455de1ee80a6f1a2f412c8878762eb6b05285f16ed47c4a857592", - "id": "fls_52ymp79ert2", - "link": "types-and-traits.html#fls_52ymp79ert2", - "number": "4.12.2:38" + "checksum": "aeefde9a0ac87118b21744a83a1a7e010b3cdd7a6d484f2225c2909cc9256ef6", + "id": "fls_h2pgsij1rbms", + "link": "ownership-and-deconstruction.html#fls_h2pgsij1rbms", + "number": "15.5:2" }, { - "checksum": "3318b943515a14fe6dbf55e7ec47613c778490dee645221932ecbb4716e25074", - "id": "fls_g2u1dfhphrrg", - "link": "types-and-traits.html#fls_g2u1dfhphrrg", - "number": "4.12.2:39" + "checksum": "632e60b88c4c64ddd9b7398bf3476151bc5701ad1b4a760220667139cf7bb56b", + "id": "fls_yx2knbby70fy", + "link": "ownership-and-deconstruction.html#fls_yx2knbby70fy", + "number": "15.5:3" }, { - "checksum": "7726d6710a5611f30a40c98174045b829d3f80a6b8d962dae4f64b76e83045a4", - "id": "fls_2xgq66qp3h95", - "link": "types-and-traits.html#fls_2xgq66qp3h95", - "number": "4.12.2:40" + "checksum": "f8d36262285cdd2b4bef51b17a98103be0501e5e38a087145322a3fd9713f59c", + "id": "fls_6ul3f6v0foma", + "link": "ownership-and-deconstruction.html#fls_6ul3f6v0foma", + "number": "15.5:4" }, { - "checksum": "fd91364bed19d0afa9d73fb3c1658c025a696b873269013f813ec189d9214416", - "id": "fls_5dh8c5gg0hmk", - "link": "types-and-traits.html#fls_5dh8c5gg0hmk", - "number": "4.12.2:41" + "checksum": "1f5f58ea8bff9c3f06d08ec8804d370315b1c1190ffc8f6354263a3d92fe64a7", + "id": "fls_3ztdz02efeoc", + "link": "ownership-and-deconstruction.html#fls_3ztdz02efeoc", + "number": "15.5:5" }, { - "checksum": "6a5c53a38ac6502579f67680736ebeff29b1a0a8a697f84d17e10c795d1d4b7d", - "id": "fls_ismr7wwvek4q", - "link": "types-and-traits.html#fls_ismr7wwvek4q", - "number": "4.12.2:42" + "checksum": "e1dedaef11fca8fa7e09c7906b87ca804da421ff7b5ada8a981d024d16ae5c6f", + "id": "fls_konzgoybhfqm", + "link": "ownership-and-deconstruction.html#fls_konzgoybhfqm", + "number": "15.5:6" }, { - "checksum": "b27ba69adda5e65b3928999f9748285a16bee769d0115e2a1aeb237b6e4dbc3a", - "id": "fls_x9933rjecrna", - "link": "types-and-traits.html#fls_x9933rjecrna", - "number": "4.12.2:43" + "checksum": "e84118fd70dabb3e58425bef7f6dff825e6a847042faa9129285c5a1ed911a36", + "id": "fls_4bnbv7mqod57", + "link": "ownership-and-deconstruction.html#fls_4bnbv7mqod57", + "number": "15.5:7" }, { - "checksum": "e08654b77f30bda8b01a6f943f5f80454a4b1b139153afc6307396967f705b66", - "id": "fls_mizmcykgdisb", - "link": "types-and-traits.html#fls_mizmcykgdisb", - "number": "4.12.2:44" + "checksum": "97a3445c9540e7ad4cee9865d34bc4dd2fa75219dffd54e92dded06b85c59541", + "id": "fls_3xk3p1unbjy5", + "link": "ownership-and-deconstruction.html#fls_3xk3p1unbjy5", + "number": "15.5:8" }, { - "checksum": "72049284c54176fc4fb1c1c255561eb7d7942a71235fa68202ca2bb3d8b79613", - "id": "fls_paoh0wttde2z", - "link": "types-and-traits.html#fls_paoh0wttde2z", - "number": "4.12.2:45" + "checksum": "8c23071abb8829ee058cb57ae16a5f81b804ff2c1d55f7ec813bdf648a0e5ae8", + "id": "fls_vk1xhvdaakh0", + "link": "ownership-and-deconstruction.html#fls_vk1xhvdaakh0", + "number": "15.5:9" }, { - "checksum": "5d19ac9384672776d1ee638f44540b560d0f10f40289e94044bc9fa144c252a9", - "id": "fls_akko4dmp4nkw", - "link": "types-and-traits.html#fls_akko4dmp4nkw", - "number": "4.12.2:46" + "checksum": "5fc750214c56a9e1435062359732e69a801b4413b5f235c73614e41074c91c13", + "id": "fls_vveEJn7lngT8", + "link": "ownership-and-deconstruction.html#fls_vveEJn7lngT8", + "number": "15.5:10" }, { - "checksum": "138bb93829e388cb6e4f30afcc6d8172dbc34098c101c23ea3024f033ed853c7", - "id": "fls_8gldjjxbyyb4", - "link": "types-and-traits.html#fls_8gldjjxbyyb4", - "number": "4.12.2:47" + "checksum": "ce6808e7d0727241eb783b7bdfb02998d6e5865d3d0d97dee49053512f5a3943", + "id": "fls_gq35gqagw35", + "link": "ownership-and-deconstruction.html#fls_gq35gqagw35", + "number": "15.5:11" }, { - "checksum": "35b9f5170166cb82091a22696f9f5551aeeb153b146a6a8cc6fd0ec0f69ba070", - "id": "fls_8jad1ztcuxha", - "link": "types-and-traits.html#fls_8jad1ztcuxha", - "number": "4.12.2:48" + "checksum": "994382b24fcb146882e480cba7da6ddcbbb55a473b76161b1167a520daba8fde", + "id": "fls_cfqzpmnfeh8h", + "link": "ownership-and-deconstruction.html#fls_cfqzpmnfeh8h", + "number": "15.5:12" }, { - "checksum": "474c0b6fc42aad94745322806f94d0baac7f6f82466ba9b20bb757aeaf8f0245", - "id": "fls_j3w9ap9zaqud", - "link": "types-and-traits.html#fls_j3w9ap9zaqud", - "number": "4.12.2:49" + "checksum": "754c9ebda067292c99d20bc43c433c797e386d1c651bf1cd0d37d42596ad9b86", + "id": "fls_go9gdlk5d3km", + "link": "ownership-and-deconstruction.html#fls_go9gdlk5d3km", + "number": "15.5:13" }, { - "checksum": "f77f51ab20e49154362e7e30356fa9048d737311587b745a4c109b6adfbd0774", - "id": "fls_yvllot5qnc4s", - "link": "types-and-traits.html#fls_yvllot5qnc4s", - "number": "4.12.2:50" + "checksum": "1f27a382873eeb1cbf8a680f1f4445841e31a6ca70791b7e72d1f0064c2e99e4", + "id": "fls_459xx6febmf0", + "link": "ownership-and-deconstruction.html#fls_459xx6febmf0", + "number": "15.5:14" }, { - "checksum": "eab384e3f2e76b1ac0fe9ec7ac7cf230e58b0f6069105b253a18e5a57cf0f62c", - "id": "fls_hza5n5eb18ta", - "link": "types-and-traits.html#fls_hza5n5eb18ta", - "number": "4.12.2:51" + "checksum": "794dbaeb036da65d453284a372c0a58b55b03099b8433c2c1acea02cf3ebfd44", + "id": "fls_3xyq50abdiv6", + "link": "ownership-and-deconstruction.html#fls_3xyq50abdiv6", + "number": "15.5:15" }, { - "checksum": "a08b517ccebf64dfb28aba3e871021511369da1be5e56cff2589a3924b3b9d1b", - "id": "fls_ww16urcjrj6i", - "link": "types-and-traits.html#fls_ww16urcjrj6i", - "number": "4.12.2:52" + "checksum": "2f24b0e12326e4ad6c1cff5ea009d0cc6878e98b4f8f16fd738a89a432461c62", + "id": "fls_7kcx3u8gvl1d", + "link": "ownership-and-deconstruction.html#fls_7kcx3u8gvl1d", + "number": "15.5:16" }, { - "checksum": "89b48d93f4666e6e771a2628649e81f8d1f2de995e07defb06fff0282cffe991", - "id": "fls_bnp6or49voxp", - "link": "types-and-traits.html#fls_bnp6or49voxp", - "number": "4.12.2:53" + "checksum": "45a7939e44809197b647eab86a5bbb791bbd9b7b492d99ff134fd6b1b35bd6f6", + "id": "fls_i4hrifsb9msr", + "link": "ownership-and-deconstruction.html#fls_i4hrifsb9msr", + "number": "15.5:17" }, { - "checksum": "203ad82032af14af279f43593fcd8fccfc929c8078778a6de310ca6d1f78e9f8", - "id": "fls_hdo4c849q3lo", - "link": "types-and-traits.html#fls_hdo4c849q3lo", - "number": "4.12.2:54" + "checksum": "242ee9281937fc599be5d057484f99ff16cac54d4dedfc9817b957bbec3eac6c", + "id": "fls_7tadh1zel0fc", + "link": "ownership-and-deconstruction.html#fls_7tadh1zel0fc", + "number": "15.5:18" }, { - "checksum": "94dbd24c5170da391b4fd6bf809cc9f11e71dac5c7534862f9d4dcbae162ccb1", - "id": "fls_w9dx5h7m31sj", - "link": "types-and-traits.html#fls_w9dx5h7m31sj", - "number": "4.12.2:55" + "checksum": "162688f5929bce2f33343b1dcf6263ceb3ca10d03307199ad9ab3e4ec746aaf9", + "id": "fls_ywt328hcieka", + "link": "ownership-and-deconstruction.html#fls_ywt328hcieka", + "number": "15.5:19" + } + ], + "title": "Passing Conventions" + }, + { + "id": "fls_4jiw35pan7vn", + "informational": false, + "link": "ownership-and-deconstruction.html#destruction", + "number": "15.6", + "paragraphs": [ + { + "checksum": "b715c62ba79445fc86b207de20ae057aeb387fa46fc4a9f8b5d99105c88061e7", + "id": "fls_e7ucq87s806d", + "link": "ownership-and-deconstruction.html#fls_e7ucq87s806d", + "number": "15.6:1" } ], - "title": "Type Unification" + "title": "Destruction" }, { - "id": "fls_dw33yt5g6m0k", + "id": "fls_u2mzjgiwbkz0", "informational": false, - "link": "types-and-traits.html#type-coercion", - "number": "4.12.3", + "link": "ownership-and-deconstruction.html#destructors", + "number": "15.7", "paragraphs": [ { - "checksum": "1c125d2f970819ac01d209699beabb24a85dcf91316e31ca2e951e98dc4a5f2e", - "id": "fls_w5pjcj9qmgbv", - "link": "types-and-traits.html#fls_w5pjcj9qmgbv", - "number": "4.12.3:1" + "checksum": "58a815029e5c5abf31dbdc0ae371a884c35141c19fa227ecd118d8ec5c7977c3", + "id": "fls_9m0gszdle0qb", + "link": "ownership-and-deconstruction.html#fls_9m0gszdle0qb", + "number": "15.7:1" }, { - "checksum": "b3d69f435190dd57fd86cebc434e29483825d6b25b8f60120e3477fef36b44cd", - "id": "fls_5v0n2a32bk95", - "link": "types-and-traits.html#fls_5v0n2a32bk95", - "number": "4.12.3:2" + "checksum": "99e01849a304562c74b7705792700bbb8f792f332352a99fbdf6a23f3f2749a0", + "id": "fls_4nkzidytpi6", + "link": "ownership-and-deconstruction.html#fls_4nkzidytpi6", + "number": "15.7:2" }, { - "checksum": "b18d8bddfde5ee8721d48f3d414b5a55dfb48c98bf03d2412a1ab54ae0f93c30", - "id": "fls_j3kbaf43sgpj", - "link": "types-and-traits.html#fls_j3kbaf43sgpj", - "number": "4.12.3:3" + "checksum": "780a665cd5b58bccb729342e33dea24cd6f074a7109cee63ca9412cf67d69b2c", + "id": "fls_wzuwapjqtyyy", + "link": "ownership-and-deconstruction.html#fls_wzuwapjqtyyy", + "number": "15.7:3" }, { - "checksum": "4e13d35d1de469cd6adacba64653a8dc3a8a58eb96060d9dc2fa3c06a4248a7f", - "id": "fls_wxrugvlazy6v", - "link": "types-and-traits.html#fls_wxrugvlazy6v", - "number": "4.12.3:4" + "checksum": "25fab97c21fc263acb73e4f9c015d9ebb7c96ec0f09fdfa35e4550aed472dde1", + "id": "fls_gfvm70iqu1l4", + "link": "ownership-and-deconstruction.html#fls_gfvm70iqu1l4", + "number": "15.7:4" }, { - "checksum": "dcd00eff58f1f029b5a359bbdfebe7a52a9dda64e77847b71c38aa9ee577082b", - "id": "fls_bhzmble1itog", - "link": "types-and-traits.html#fls_bhzmble1itog", - "number": "4.12.3:5" + "checksum": "e33f94694a42a7f211aaec4bd688778421eba9168af369f075a51652a55c55d8", + "id": "fls_l2xkdjeydqtx", + "link": "ownership-and-deconstruction.html#fls_l2xkdjeydqtx", + "number": "15.7:5" }, { - "checksum": "2849e3f04859c16c3f699d20cb59e697e2d5ea761ef414a8e9f6a4bfebdad8a2", - "id": "fls_eu4bt3dw1b8c", - "link": "types-and-traits.html#fls_eu4bt3dw1b8c", - "number": "4.12.3:6" + "checksum": "b88a85aa7e4b1891ac3ed4ca23a4b92b4e035715e966e1f848f1e21427d23845", + "id": "fls_bync24y6gp93", + "link": "ownership-and-deconstruction.html#fls_bync24y6gp93", + "number": "15.7:6" }, { - "checksum": "14a2ebfc0dc84e45abcd936047fdf03858f8cfa416f4c711cdb673c3029f2a1d", - "id": "fls_apstt4elv2k7", - "link": "types-and-traits.html#fls_apstt4elv2k7", - "number": "4.12.3:7" + "checksum": "ce0fe2c8975789b3ccac42978e94c8cf22385f6f021e809465af734f19bbe334", + "id": "fls_jzancf72i95f", + "link": "ownership-and-deconstruction.html#fls_jzancf72i95f", + "number": "15.7:7" }, { - "checksum": "853017badc80ef7625757aa8b68a0cbf76baed161093698d90b35416d3ff25a5", - "id": "fls_sp794uzfiofr", - "link": "types-and-traits.html#fls_sp794uzfiofr", - "number": "4.12.3:8" + "checksum": "4695a93955bba74f12080fa0db5a0ad04dfa0e9127756fcc2b3dead95ad24f77", + "id": "fls_gjn2jnsal9gs", + "link": "ownership-and-deconstruction.html#fls_gjn2jnsal9gs", + "number": "15.7:8" }, { - "checksum": "1a7266235db01fc19f566dc6970836b039c783ca94c394b07d6f46f45a327bae", - "id": "fls_xfqny6bwzsu9", - "link": "types-and-traits.html#fls_xfqny6bwzsu9", - "number": "4.12.3:9" + "checksum": "27ce20568cf31821f40322d7cbca37c6e7f9a154497206aa2cd66da3c71e14fe", + "id": "fls_ol2w2292frfi", + "link": "ownership-and-deconstruction.html#fls_ol2w2292frfi", + "number": "15.7:9" }, { - "checksum": "b49cdf0f2fb74d20d8bf9bd22739caf559f092b37874cc469a1fc11796b0f3d7", - "id": "fls_u0e42y7nvn7e", - "link": "types-and-traits.html#fls_u0e42y7nvn7e", - "number": "4.12.3:10" + "checksum": "3a5360261dcb8e8292661839009fff3c71ef4a3d1c9f45a2d85f861f4b707310", + "id": "fls_6ii5o68vuymj", + "link": "ownership-and-deconstruction.html#fls_6ii5o68vuymj", + "number": "15.7:10" }, { - "checksum": "e94e4fd5d3c8124476595c77485d16348c94894082857326c18e6414d7e2ead0", - "id": "fls_p8hp5y506nam", - "link": "types-and-traits.html#fls_p8hp5y506nam", - "number": "4.12.3:11" + "checksum": "2b1fcd39b5b78562ea6e359c8e98307553d5ffc8a1c9188166ffd9fc296d0a8d", + "id": "fls_sup43es8ps8r", + "link": "ownership-and-deconstruction.html#fls_sup43es8ps8r", + "number": "15.7:11" }, { - "checksum": "973dbaecfc13fb4ab65e325a94d44a45fc9c953dda41203951b7614d02670864", - "id": "fls_fjc9xev8rcu6", - "link": "types-and-traits.html#fls_fjc9xev8rcu6", - "number": "4.12.3:12" + "checksum": "622d773bd119d86de850502b8e92eee57b581476e14eff7478ba5d558629c86f", + "id": "fls_y9q0eqr865b3", + "link": "ownership-and-deconstruction.html#fls_y9q0eqr865b3", + "number": "15.7:12" }, { - "checksum": "8c78c768eeb8f1a02a7d182fed23128f8bc7bbf0ddc102ba2da4c71a08098ee9", - "id": "fls_n1kh3z8d4q8y", - "link": "types-and-traits.html#fls_n1kh3z8d4q8y", - "number": "4.12.3:13" + "checksum": "3df5eb307218abb09b95ebf9bab92992011b11bb1c5d1e246c3160e5a71afd94", + "id": "fls_kdqng6eovxns", + "link": "ownership-and-deconstruction.html#fls_kdqng6eovxns", + "number": "15.7:13" }, { - "checksum": "f631f3eaa473d36dc8b906892fb02d2afdfcc6ac8b36a88720bb9cbe268707cf", - "id": "fls_dgoypa3hcxc0", - "link": "types-and-traits.html#fls_dgoypa3hcxc0", - "number": "4.12.3:14" + "checksum": "a023dc4d0c98b62c2d9576d70e51108a0fdb29ba3f14769c7d87ab09d1f0938a", + "id": "fls_ag249y74jg6c", + "link": "ownership-and-deconstruction.html#fls_ag249y74jg6c", + "number": "15.7:14" }, { - "checksum": "af9ac95087820df12145d4b1bcdc5c2aefdfe7e0661d4ee8ebce4897d946cc54", - "id": "fls_h8dkehit8rza", - "link": "types-and-traits.html#fls_h8dkehit8rza", - "number": "4.12.3:15" + "checksum": "69b3fa403d23f715b291788890e24ebd09e6b009e67a21e5edf9c25f29e037eb", + "id": "fls_tw36n3g32a0y", + "link": "ownership-and-deconstruction.html#fls_tw36n3g32a0y", + "number": "15.7:15" }, { - "checksum": "71e522755dbc28be6cd7a324a6a6454114e8e0244de6bbff1a5f0cb0ccb88cd2", - "id": "fls_z00wtlna6grk", - "link": "types-and-traits.html#fls_z00wtlna6grk", - "number": "4.12.3:16" + "checksum": "9a3ba3981279a98242813d19ecc21dcce387bb841f1be9398442ba10e7d9addd", + "id": "fls_fmn33zhorkf", + "link": "ownership-and-deconstruction.html#fls_fmn33zhorkf", + "number": "15.7:16" + } + ], + "title": "Destructors" + }, + { + "id": "fls_rm4ncoopcdvj", + "informational": false, + "link": "ownership-and-deconstruction.html#drop-scopes", + "number": "15.8", + "paragraphs": [ + { + "checksum": "fa365174cbf5682b2addb5a47523a886be22f15c45e88cb5d01ef13a92c0d9b2", + "id": "fls_7uav7vkcv4pz", + "link": "ownership-and-deconstruction.html#fls_7uav7vkcv4pz", + "number": "15.8:1" }, { - "checksum": "3e2e8a463e825e0de103643132d6ac576478740d745d5bfada48884bf6a73f0f", - "id": "fls_rfjdh79k0wou", - "link": "types-and-traits.html#fls_rfjdh79k0wou", - "number": "4.12.3:17" + "checksum": "7fcf965dcbfb27fdef8959d59a625da3fa44a0d931095edf51cd4b5db75eb01c", + "id": "fls_txvxrn6wbyql", + "link": "ownership-and-deconstruction.html#fls_txvxrn6wbyql", + "number": "15.8:2" }, { - "checksum": "760728def307e32ecd93826afc55eb5b39477adab8e2fdd6b8d2c3445e9c6c6b", - "id": "fls_e3lgrtqb7jwe", - "link": "types-and-traits.html#fls_e3lgrtqb7jwe", - "number": "4.12.3:18" + "checksum": "aa4f1d2f9b7ef3586caec5b558b58e35cb2b8cd615ba21adcf9234ba7283bec9", + "id": "fls_n6y6brm6pghr", + "link": "ownership-and-deconstruction.html#fls_n6y6brm6pghr", + "number": "15.8:3" }, { - "checksum": "b9ab7cd8a6ff174f7370bef719769860ab9bb78d73a887a2d266b10b9f0e356d", - "id": "fls_fwy2z11c1sji", - "link": "types-and-traits.html#fls_fwy2z11c1sji", - "number": "4.12.3:19" + "checksum": "557bd0675e3046e13b7a004ef78b8b0f946bcca78bc242cb76364e3f991f1d3a", + "id": "fls_gdh6wwvi7ci6", + "link": "ownership-and-deconstruction.html#fls_gdh6wwvi7ci6", + "number": "15.8:4" }, { - "checksum": "34586dbc5392132b773e0f3aa90c23b3f4079ca79bedb9d108b354e0c15a9584", - "id": "fls_aujb44849tq1", - "link": "types-and-traits.html#fls_aujb44849tq1", - "number": "4.12.3:20" + "checksum": "89634ecd6c544d1fbf8a649d790b5173d2a09803e97525c89f9d827df73ab265", + "id": "fls_owqk2fcpvc4s", + "link": "ownership-and-deconstruction.html#fls_owqk2fcpvc4s", + "number": "15.8:5" }, { - "checksum": "b048851b08b58ee5809efccef3bf5a7a5207344b662eba5162de24e194352b83", - "id": "fls_p3ym3ycrnd5m", - "link": "types-and-traits.html#fls_p3ym3ycrnd5m", - "number": "4.12.3:21" + "checksum": "62c1b4c8906b7867a552ab0c166e92d1673c4e6c996c6e82c55fb18155b337a5", + "id": "fls_ckh8wkq0y5ja", + "link": "ownership-and-deconstruction.html#fls_ckh8wkq0y5ja", + "number": "15.8:6" }, { - "checksum": "1b30164bd90c0dbbcb47c87803c0841dfe9d3d772e1a34c7a9ccc4a4933b2c10", - "id": "fls_jmo42qgix5uw", - "link": "types-and-traits.html#fls_jmo42qgix5uw", - "number": "4.12.3:22" + "checksum": "e18cce725d7e3e2f64db15d95adeb42a7a9e3d806e422bcad663e4734ed8ebab", + "id": "fls_2zwwnzepgmje", + "link": "ownership-and-deconstruction.html#fls_2zwwnzepgmje", + "number": "15.8:7" }, { - "checksum": "c1dda665688283c6458e52d3c3a8e7387bab58a818e1c1b871485a8e7d6c068c", - "id": "fls_tbt4236igdzb", - "link": "types-and-traits.html#fls_tbt4236igdzb", - "number": "4.12.3:23" + "checksum": "06a9f847c5fd97acf184ee400bbbe296accf5d480e6cef5d1766f5fef9270c17", + "id": "fls_vlbx5ukw5c8l", + "link": "ownership-and-deconstruction.html#fls_vlbx5ukw5c8l", + "number": "15.8:8" }, { - "checksum": "e789228b8563894bad59c2a9870a4324c5fde44cc790f00e4a45a0b465c8316a", - "id": "fls_7ri4jk2dydfn", - "link": "types-and-traits.html#fls_7ri4jk2dydfn", - "number": "4.12.3:24" + "checksum": "7478e15bd141efde6f849fddd3e660a3198328261280e11802f707445106483f", + "id": "fls_d5yg6w8gv6aq", + "link": "ownership-and-deconstruction.html#fls_d5yg6w8gv6aq", + "number": "15.8:9" }, { - "checksum": "fc7f331b6089c5bd8e347504d88e62078d5c4c846a9ccf65eaeedd8ffc094c48", - "id": "fls_6r3kn0nk5b8o", - "link": "types-and-traits.html#fls_6r3kn0nk5b8o", - "number": "4.12.3:25" + "checksum": "2210651d12709f733124d5831bdb32f27f192f93fa3be518eb6b7334a6531966", + "id": "fls_qidma4fpkhb0", + "link": "ownership-and-deconstruction.html#fls_qidma4fpkhb0", + "number": "15.8:10" }, { - "checksum": "61b8468e184c14f1902fbd43e6492e9b4640ddad4ae0a33a731901b147214f58", - "id": "fls_ulcdetwp6x96", - "link": "types-and-traits.html#fls_ulcdetwp6x96", - "number": "4.12.3:26" + "checksum": "04f68fcba1cc880a1b5acaefe6abf4f93cac7e0150747463062aef3aecedc14f", + "id": "fls_1o9ye6cwoyiq", + "link": "ownership-and-deconstruction.html#fls_1o9ye6cwoyiq", + "number": "15.8:11" }, { - "checksum": "e233c03b13e1a8e36e57cca5a910344a93904dc508dbe3d5823adb57c58fdaea", - "id": "fls_2uv1r0gni1fk", - "link": "types-and-traits.html#fls_2uv1r0gni1fk", - "number": "4.12.3:27" + "checksum": "aa403db4f036453bba3271f4509a2302488c5fd4205bfdc4f54cde71db726013", + "id": "fls_16htxf824xbk", + "link": "ownership-and-deconstruction.html#fls_16htxf824xbk", + "number": "15.8:12" + }, + { + "checksum": "070c24d985ad07d4a8affc27d166c94f9beaee7f6c83f82c5249ef04eb845c8a", + "id": "fls_lbsfhg42yiqy", + "link": "ownership-and-deconstruction.html#fls_lbsfhg42yiqy", + "number": "15.8:13" + }, + { + "checksum": "3f43cdcc50a1a53af990b4beeb71262819034dd32a82fcb54484c052b19b44c6", + "id": "fls_5m3u3k6f00bd", + "link": "ownership-and-deconstruction.html#fls_5m3u3k6f00bd", + "number": "15.8:14" }, { - "checksum": "fde902a0dcf7245891be85fe02b53a36f549f906b5621552b11c7c0e83e5b3ad", - "id": "fls_sf0c3fbx8z57", - "link": "types-and-traits.html#fls_sf0c3fbx8z57", - "number": "4.12.3:28" + "checksum": "38a763a575ed36ac0f33c0e4c97708f46f5634a6bfca712737225d732788d27f", + "id": "fls_m86ljncnmo7j", + "link": "ownership-and-deconstruction.html#fls_m86ljncnmo7j", + "number": "15.8:15" }, { - "checksum": "2c0ca29375ecee34ce0749c8579610946e907e1988ceded9635c2d2ab4bc44ec", - "id": "fls_SYnFJBhi0IWj", - "link": "types-and-traits.html#fls_SYnFJBhi0IWj", - "number": "4.12.3:29" + "checksum": "05382bcb6a5dca3a3b33debf11b819bbb3b2b29f4559e6beeaad692d688c87c8", + "id": "fls_bewcu5xceu8i", + "link": "ownership-and-deconstruction.html#fls_bewcu5xceu8i", + "number": "15.8:16" }, { - "checksum": "a80ed17718eb533324e24a46fc43672df7763fc96f271b8bccd3fab1d963312b", - "id": "fls_QB4c6FNKxaPl", - "link": "types-and-traits.html#fls_QB4c6FNKxaPl", - "number": "4.12.3:30" + "checksum": "c38a81f8df72f87ccafba9e41d742d2b2de00acfe77e463a03ec5d0e96082258", + "id": "fls_iHHhlxCiqNWZ", + "link": "ownership-and-deconstruction.html#fls_iHHhlxCiqNWZ", + "number": "15.8:17" }, { - "checksum": "dd7bc2c44f433ffd630cffd6c4386dd7fbd5fa2ce7379e469299b9dc5c179d09", - "id": "fls_iiiu2q7pym4p", - "link": "types-and-traits.html#fls_iiiu2q7pym4p", - "number": "4.12.3:31" + "checksum": "08316305ae1b30302790dfc45803df2f527f449747eb34b2a91b407beec878b4", + "id": "fls_swXXkbZGLPKa", + "link": "ownership-and-deconstruction.html#fls_swXXkbZGLPKa", + "number": "15.8:18" }, { - "checksum": "8e785eaf45c0dfef77ee6ff7b317a82aaf46b49c84f4c2ecdc296ee62568c727", - "id": "fls_jte6n2js32af", - "link": "types-and-traits.html#fls_jte6n2js32af", - "number": "4.12.3:32" + "checksum": "770bb15fa73a6c965d591c00604c440c7de7c3e3257c6f0946cf7be4e1cb394b", + "id": "fls_vrqgac634wpr", + "link": "ownership-and-deconstruction.html#fls_vrqgac634wpr", + "number": "15.8:19" }, { - "checksum": "a9d2363147bd3d1382fb2792473844fa7abd6d6fb6aa67371d82a7ede1452d39", - "id": "fls_20pvqqayzqra", - "link": "types-and-traits.html#fls_20pvqqayzqra", - "number": "4.12.3:33" + "checksum": "a35aa796d1ab80a5df343d39e3e12923a8a46fd0df82325bf334669986e7d03a", + "id": "fls_fnvr5w2wzxns", + "link": "ownership-and-deconstruction.html#fls_fnvr5w2wzxns", + "number": "15.8:20" }, { - "checksum": "65c7ca810e8b17133d894175777f2b3b7bc290fcd174dc128bc978c1bbf10464", - "id": "fls_j8rcy0xvd155", - "link": "types-and-traits.html#fls_j8rcy0xvd155", - "number": "4.12.3:34" + "checksum": "67a942cc52a66f9f90a3a9131beae16808a17f3f4d7d49fdb08ff42f734ce302", + "id": "fls_1vZFa5mDEbXW", + "link": "ownership-and-deconstruction.html#fls_1vZFa5mDEbXW", + "number": "15.8:21" }, { - "checksum": "1af2a2bba8399300cc1e205f0f7fee9618593ec4a2537211d6ba2bac7ca92379", - "id": "fls_wuka4uyo3oj7", - "link": "types-and-traits.html#fls_wuka4uyo3oj7", - "number": "4.12.3:35" + "checksum": "1dec6a051b1fff3a1bdeae59f4b0c13eae973e22d62993017cf9176ae6c41ef8", + "id": "fls_3jtU8grRaMM9", + "link": "ownership-and-deconstruction.html#fls_3jtU8grRaMM9", + "number": "15.8:22" }, { - "checksum": "8322e0c9269a64e591836ee24ecb8d377ce3093892b78053bacd698ed55a8962", - "id": "fls_w15yo8yvuxq3", - "link": "types-and-traits.html#fls_w15yo8yvuxq3", - "number": "4.12.3:36" + "checksum": "92013afe5f273d886cf33357afa293c5145b38dd0ea03a2758611998c0ac0b32", + "id": "fls_8r39duatupxw", + "link": "ownership-and-deconstruction.html#fls_8r39duatupxw", + "number": "15.8:23" }, { - "checksum": "dcbd46a0126cc58845c6cb40ab6afc0aea44dac093a6f13bfa4fcf14b4b395fe", - "id": "fls_7aw3ifbvfgbd", - "link": "types-and-traits.html#fls_7aw3ifbvfgbd", - "number": "4.12.3:37" + "checksum": "c3e4aa156bda861e6ef22a6fa15d0e213a08ac8a395097b3bae8fb744bcac564", + "id": "fls_2peq1tihglnr", + "link": "ownership-and-deconstruction.html#fls_2peq1tihglnr", + "number": "15.8:24" }, { - "checksum": "89cc90acc65cd37645feae640026726577f4f4f0349a887ff00a451479f45b0a", - "id": "fls_cnkth59djwgl", - "link": "types-and-traits.html#fls_cnkth59djwgl", - "number": "4.12.3:38" + "checksum": "1f13a26446ed0aea0e199436fd0adb97cab249490f3e88c061ca7f09229125e7", + "id": "fls_il09n7sq0a3k", + "link": "ownership-and-deconstruction.html#fls_il09n7sq0a3k", + "number": "15.8:25" }, { - "checksum": "afc1ce7f88cf8abf3164653eb9fd348bd326ebc15ebf3037c655e1c0494c89ad", - "id": "fls_4wbk7pqj010i", - "link": "types-and-traits.html#fls_4wbk7pqj010i", - "number": "4.12.3:39" + "checksum": "942aadd6edb16dc7e80b6f2cd3392f324ea9d00f9e61794c8fa5ec6b65b30d82", + "id": "fls_s1wbqld139gz", + "link": "ownership-and-deconstruction.html#fls_s1wbqld139gz", + "number": "15.8:26" }, { - "checksum": "cdd6b9592887af36d0772fde53ca3091c4a6bf70805ffbb04a1dbb16ef4d92ff", - "id": "fls_47u0039t0l8f", - "link": "types-and-traits.html#fls_47u0039t0l8f", - "number": "4.12.3:40" + "checksum": "750908f479d13e69946a538f5a81e56c3d71d08e5c2493c6129c77568fe3de66", + "id": "fls_asvuef2pc3m0", + "link": "ownership-and-deconstruction.html#fls_asvuef2pc3m0", + "number": "15.8:27" }, { - "checksum": "cefece0d02fa276fe12b8d7fa02401d8fa6a73dd827f6c8960ef8097b6ff375f", - "id": "fls_bmh6g3jju7eq", - "link": "types-and-traits.html#fls_bmh6g3jju7eq", - "number": "4.12.3:41" + "checksum": "690427f07bce841d8ea70b8cb0864491ae88e1b7d4cfcf9ca2fb45657c8c0d87", + "id": "fls_560437qmeqtr", + "link": "ownership-and-deconstruction.html#fls_560437qmeqtr", + "number": "15.8:28" }, { - "checksum": "698fb05a30487c47e16c1814bf5989a6201253e9f222c440fd59e3d8cfabbede", - "id": "fls_da4w32rsrwxc", - "link": "types-and-traits.html#fls_da4w32rsrwxc", - "number": "4.12.3:42" + "checksum": "96c9a3b88a3cf78a0260b534ac8bac2c2676692e6504f28abb95658a7c723ea2", + "id": "fls_8cunkfc6x24q", + "link": "ownership-and-deconstruction.html#fls_8cunkfc6x24q", + "number": "15.8:29" }, { - "checksum": "8333e75b01db4b4778f0b23b7e145da87834bc47ac828831f781b14f6b7830c1", - "id": "fls_zi5311z1w7re", - "link": "types-and-traits.html#fls_zi5311z1w7re", - "number": "4.12.3:43" + "checksum": "41cab1feee08b6f4c71f9ce9cb18a539787287a0d87320b8477673fd090b1221", + "id": "fls_n108lvc4otoc", + "link": "ownership-and-deconstruction.html#fls_n108lvc4otoc", + "number": "15.8:30" }, { - "checksum": "3ef2c70df3fbc39e3d8fe5bbe8c0e1adbf015005169243e1455458fed8ded361", - "id": "fls_zst5pa29rpt", - "link": "types-and-traits.html#fls_zst5pa29rpt", - "number": "4.12.3:44" + "checksum": "d23e4c8eeb0eed8bb71e11637e19abe752abf0f59454434c187efb281833b269", + "id": "fls_ptk6yibqyfzi", + "link": "ownership-and-deconstruction.html#fls_ptk6yibqyfzi", + "number": "15.8:31" }, { - "checksum": "f4b1e50283ac71505aad22e917efae5d7d1d0798dbdf1fbe9d80f65bb04514a7", - "id": "fls_agw1aej616vf", - "link": "types-and-traits.html#fls_agw1aej616vf", - "number": "4.12.3:45" + "checksum": "571f4508156e008bb63439e1b7abe647297d7fbafe515b8c2a3a24456a52e99b", + "id": "fls_dltmd8e8c5ia", + "link": "ownership-and-deconstruction.html#fls_dltmd8e8c5ia", + "number": "15.8:32" + } + ], + "title": "Drop Scopes" + }, + { + "id": "fls_5eima0pd31c0", + "informational": false, + "link": "ownership-and-deconstruction.html#drop-scope-extension", + "number": "15.8.1", + "paragraphs": [ + { + "checksum": "5c3d236f194a8a669a999473090570b17a9c5b62ef5f44c8025bcba89e97b82f", + "id": "fls_kflqez2mtbit", + "link": "ownership-and-deconstruction.html#fls_kflqez2mtbit", + "number": "15.8.1:1" }, { - "checksum": "bb81f4f7bb91b154fff3ff42309dea91e61e5a2ced8910f2543d14f89bf79a19", - "id": "fls_tnbga5dl4gz8", - "link": "types-and-traits.html#fls_tnbga5dl4gz8", - "number": "4.12.3:46" + "checksum": "3261611ac54de3039f960d9c6a30c641626dd062081332cd0f8cb2e0db746c2f", + "id": "fls_xjw82bujm148", + "link": "ownership-and-deconstruction.html#fls_xjw82bujm148", + "number": "15.8.1:2" }, { - "checksum": "b95cf2362fe2b684e6e861bef2924e960fcd813879dc2455367f8f620a534ff5", - "id": "fls_yoreux8tn65x", - "link": "types-and-traits.html#fls_yoreux8tn65x", - "number": "4.12.3:47" + "checksum": "95c5c42ea8af76e5ec13e3556a9f4104b818bf056900164658866e0c491b17bb", + "id": "fls_965wt48ooqyw", + "link": "ownership-and-deconstruction.html#fls_965wt48ooqyw", + "number": "15.8.1:3" }, { - "checksum": "5fca7b2b2864b7504bace27cb6390c5d3d01591a0d2cffda65c64fab8cd2c085", - "id": "fls_r11shke69uu6", - "link": "types-and-traits.html#fls_r11shke69uu6", - "number": "4.12.3:48" + "checksum": "5034d75dc865e69bdf2efb3059a8f9cf91eff07283969d9f524cbfabd137b3c7", + "id": "fls_r8nt0zp8dnyp", + "link": "ownership-and-deconstruction.html#fls_r8nt0zp8dnyp", + "number": "15.8.1:4" }, { - "checksum": "39bf09cd84e3badf0caafcecd1fc51f3d0e38645e4d70876f6313e478f238928", - "id": "fls_ky7ykpufb95t", - "link": "types-and-traits.html#fls_ky7ykpufb95t", - "number": "4.12.3:49" + "checksum": "cab57995b7b7f326223f4f7bb819e8eea5a8ad676cdbed06ce58b66817c6a3c9", + "id": "fls_3ycn4u1fe9h", + "link": "ownership-and-deconstruction.html#fls_3ycn4u1fe9h", + "number": "15.8.1:5" }, { - "checksum": "215a6f01fd8ef7a0ce0ca1e6aa8dbb82270d88d2f159c6bfe8c8aefe79a45c21", - "id": "fls_8kvme0u4u8r6", - "link": "types-and-traits.html#fls_8kvme0u4u8r6", - "number": "4.12.3:50" + "checksum": "3e3ddee5e272d848e4a7f855e0958e6cae76731b52ed7b3755bede5d03752f5f", + "id": "fls_YeeZWqTdMivX", + "link": "ownership-and-deconstruction.html#fls_YeeZWqTdMivX", + "number": "15.8.1:6" }, { - "checksum": "096c4e463b9f21a7f6f450c919b54b1d2c68b8b6e4496fe0dae2feda9e403c36", - "id": "fls_rl9yrdfwnu03", - "link": "types-and-traits.html#fls_rl9yrdfwnu03", - "number": "4.12.3:51" + "checksum": "b73020efe316a10a7ebe19452ab0c49d32e022305cbdc0ffeffe39f1f1c49ce4", + "id": "fls_wyzau8hhq74d", + "link": "ownership-and-deconstruction.html#fls_wyzau8hhq74d", + "number": "15.8.1:7" }, { - "checksum": "32a6c25c6e1124e082617948626bcbb07992d4275f54df0c25e6744748e4d53c", - "id": "fls_iqtmhn8flws7", - "link": "types-and-traits.html#fls_iqtmhn8flws7", - "number": "4.12.3:52" + "checksum": "9866d8c9d89c6ce74866c072f17480e836646226238ffb97d01caa057d3c433b", + "id": "fls_ju7cpftd8r2g", + "link": "ownership-and-deconstruction.html#fls_ju7cpftd8r2g", + "number": "15.8.1:8" }, { - "checksum": "275c8b1744879cf91aaf7305f7ba2d9a59d6efce2a635c736da0131f3f9880ed", - "id": "fls_sr8d5har4s03", - "link": "types-and-traits.html#fls_sr8d5har4s03", - "number": "4.12.3:53" + "checksum": "65a8e01501aa93c5c0390794552d8464ba66252c1f16ef70cebbe188a818aeb6", + "id": "fls_gjd1ow3l7swe", + "link": "ownership-and-deconstruction.html#fls_gjd1ow3l7swe", + "number": "15.8.1:9" }, { - "checksum": "e07e12484817e4c6487e1204a1f9510e836727735508f4ef691226c46e64bf6a", - "id": "fls_92pwnd1xbp5r", - "link": "types-and-traits.html#fls_92pwnd1xbp5r", - "number": "4.12.3:54" + "checksum": "0eeb96ccaefbfba629a6270d8f8c4fb6932a85dcaba82547b73663bb5d4fb954", + "id": "fls_Rj9zhVutfQod", + "link": "ownership-and-deconstruction.html#fls_Rj9zhVutfQod", + "number": "15.8.1:10" }, { - "checksum": "335cbd4c223a88ecd2be44961d7a6ce553d0352af438c9f034095d6c8978c103", - "id": "fls_bWHQIL4DSN4S", - "link": "types-and-traits.html#fls_bWHQIL4DSN4S", - "number": "4.12.3:55" + "checksum": "8a0dd3acb9fbefddfa8ada43770d247ab7c282b9a44c8d16ad53d22d93837c77", + "id": "fls_oodpp3LpXC13", + "link": "ownership-and-deconstruction.html#fls_oodpp3LpXC13", + "number": "15.8.1:11" }, { - "checksum": "8fed0bd1fa0bfbbe3fd11a53d62ef209fd7e09e5b74a6ad1f1b86277f57d1d86", - "id": "fls_ju4ypa5ysga0", - "link": "types-and-traits.html#fls_ju4ypa5ysga0", - "number": "4.12.3:56" - } - ], - "title": "Type Coercion" - }, - { - "id": "fls_wsfw8xf3vnil", - "informational": false, - "link": "types-and-traits.html#structural-equality", - "number": "4.12.4", - "paragraphs": [ + "checksum": "a107c1e8191264e8fe4459fcca34d39259cfdb2efd2fd252b46b3b5f14007bb7", + "id": "fls_xGThCPoTUSAi", + "link": "ownership-and-deconstruction.html#fls_xGThCPoTUSAi", + "number": "15.8.1:12" + }, { - "checksum": "4f72ed3d4dc04653669e57b2d2f00cb3b728c967aca05c3d7e142f41c86239fa", - "id": "fls_uVTpA7gbLCYX", - "link": "types-and-traits.html#fls_uVTpA7gbLCYX", - "number": "4.12.4:1" + "checksum": "f9d27231592868802964cc99b81ef283fb6e96cd3abae622cbd859e5d1dd4caa", + "id": "fls_iqw0d1l1lj3i", + "link": "ownership-and-deconstruction.html#fls_iqw0d1l1lj3i", + "number": "15.8.1:13" }, { - "checksum": "644c0bc93e9153b1614ccf90b7c10dc639fb525098e57bb2ef0188049521cf07", - "id": "fls_2DZAP6JJjJ9h", - "link": "types-and-traits.html#fls_2DZAP6JJjJ9h", - "number": "4.12.4:2" + "checksum": "592addbb57be3cb76f33b59e4e1956090c29e9a0d89327fabc53e40e6c62ef21", + "id": "fls_eeaJtK4w5gVK", + "link": "ownership-and-deconstruction.html#fls_eeaJtK4w5gVK", + "number": "15.8.1:14" }, { - "checksum": "ebe3f4bdb59589737e2a8f2587d31e540b885474c990132b77755e7fe3118e79", - "id": "fls_emcNJzl2tHSA", - "link": "types-and-traits.html#fls_emcNJzl2tHSA", - "number": "4.12.4:3" + "checksum": "ac60d872c2d76afebdf1240a5b1a41539175dcd095ba1374d768e8bce3e15b83", + "id": "fls_aq01wjpkxhq9", + "link": "ownership-and-deconstruction.html#fls_aq01wjpkxhq9", + "number": "15.8.1:15" }, { - "checksum": "9adcd87a7fd208d21fe66dfbd16bfc8617403dbc76b33595031feae169ce3659", - "id": "fls_HpWSAfaTA1Dz", - "link": "types-and-traits.html#fls_HpWSAfaTA1Dz", - "number": "4.12.4:4" + "checksum": "46590c95c001116b2b295d3fc9fa40d5606614d0db1cfd11751f0a52118c3ef7", + "id": "fls_VDPi1dJzJMUb", + "link": "ownership-and-deconstruction.html#fls_VDPi1dJzJMUb", + "number": "15.8.1:16" }, { - "checksum": "2996c6bd3a3dcde83041a19b7e0ff0b4645e49e8373faa312d7d231d80add18e", - "id": "fls_5RcnETrW6f9m", - "link": "types-and-traits.html#fls_5RcnETrW6f9m", - "number": "4.12.4:5" + "checksum": "d24a178200c6d509f3d2649dd64705b91fb52709df2446bfa5b4e8fa8ad62f32", + "id": "fls_elGH7HSawMRb", + "link": "ownership-and-deconstruction.html#fls_elGH7HSawMRb", + "number": "15.8.1:17" }, { - "checksum": "b32c20afa5097b044cad383b2ddafd39d592f035043bc0e89d5c5fc9036d89a5", - "id": "fls_jMeWhn4sNTPF", - "link": "types-and-traits.html#fls_jMeWhn4sNTPF", - "number": "4.12.4:6" + "checksum": "0baddc916a2237fc255082bc2e1dedb989c57b2c82b53885416321f4058213e5", + "id": "fls_29y59x8bmw75", + "link": "ownership-and-deconstruction.html#fls_29y59x8bmw75", + "number": "15.8.1:18" }, { - "checksum": "70b221698272f53f393baae8c1c140264a658dcc9849120449671981eb75f27c", - "id": "fls_hBFlaUrrhqZ6", - "link": "types-and-traits.html#fls_hBFlaUrrhqZ6", - "number": "4.12.4:7" + "checksum": "42a669613202095a2133d66d495e0e881e4d7819d202aae70dc008b2a1f19724", + "id": "fls_subo2w7ln43q", + "link": "ownership-and-deconstruction.html#fls_subo2w7ln43q", + "number": "15.8.1:19" } ], - "title": "Structural Equality" + "title": "Drop Scope Extension" }, { - "id": "fls_omaq7psg83n3", + "id": "fls_afafmafz4hf2", "informational": false, - "link": "types-and-traits.html#interior-mutability", - "number": "4.12.5", + "link": "ownership-and-deconstruction.html#drop-order", + "number": "15.9", "paragraphs": [ { - "checksum": "9aa56a43b546255945ab60a08d9ae4aa90b591af22e8b4c1b7dc0c4f9af83598", - "id": "fls_khy2e23i9o7z", - "link": "types-and-traits.html#fls_khy2e23i9o7z", - "number": "4.12.5:1" + "checksum": "6d998923ce960019bd73240feb0f4f1e3e9d705bbedbaf213c73707899dfd504", + "id": "fls_n6o1xzjiz8cv", + "link": "ownership-and-deconstruction.html#fls_n6o1xzjiz8cv", + "number": "15.9:1" }, { - "checksum": "d05a898c3b49c36accca96ad2625158f39bce97c30d6fb95823df4e3533194bd", - "id": "fls_sWiU26n2xS3r", - "link": "types-and-traits.html#fls_sWiU26n2xS3r", - "number": "4.12.5:2" - } - ], - "title": "Interior Mutability" - }, - { - "id": "fls_mcxf9y5u66sz", - "informational": false, - "link": "types-and-traits.html#visible-emptiness", - "number": "4.12.6", - "paragraphs": [ + "checksum": "0c3b33caf3b9c961df39107ec74da60de4eb20ab99798df119cb2faed79efe15", + "id": "fls_jwofws3022ar", + "link": "ownership-and-deconstruction.html#fls_jwofws3022ar", + "number": "15.9:2" + }, { - "checksum": "7e42d74e3da8f7f9b6eda796375e6051781a54907535ccc6ff3cc13c1bdb3c5e", - "id": "fls_SD4yUEQ9hHa3", - "link": "types-and-traits.html#fls_SD4yUEQ9hHa3", - "number": "4.12.6:1" + "checksum": "0c98ebb3d2ca90465f8ced9ebdf2d3743e1a59cb248f2af3e3e5653cf2ca76b7", + "id": "fls_W2S2FrkuedYC", + "link": "ownership-and-deconstruction.html#fls_W2S2FrkuedYC", + "number": "15.9:3" }, { - "checksum": "c13454d3b8091be8c50bced264293b1579b676e9283537b215d3ab27a25e1f5c", - "id": "fls_GeoneCP5TYwf", - "link": "types-and-traits.html#fls_GeoneCP5TYwf", - "number": "4.12.6:2" + "checksum": "14fdcee19609abf8aeed5f7e20952a848faecfba256dd405eca3cc49a24b338f", + "id": "fls_a5tmilqxdb6f", + "link": "ownership-and-deconstruction.html#fls_a5tmilqxdb6f", + "number": "15.9:4" }, { - "checksum": "79afbe5d444146ae944d95b5b002388b71a46be184cbc0c1ab2a7353ff938587", - "id": "fls_A2W4v53ihTGx", - "link": "types-and-traits.html#fls_A2W4v53ihTGx", - "number": "4.12.6:3" + "checksum": "08645dd3230efc38b718c5ab02247a8a4fc278d67fea4fd1d696c57033eff69e", + "id": "fls_g07zq3n55094", + "link": "ownership-and-deconstruction.html#fls_g07zq3n55094", + "number": "15.9:5" }, { - "checksum": "ee8c6b869e019c12fa0288994c7d95c6f96f7a4b0006a608f6df7f213035da77", - "id": "fls_AXOtKdSQR4AF", - "link": "types-and-traits.html#fls_AXOtKdSQR4AF", - "number": "4.12.6:4" + "checksum": "717f7f256a61d53de344e5a6553b5529ee34e430a67efc99ceab0162e8631a2a", + "id": "fls_zQGkVGWIzMQ7", + "link": "ownership-and-deconstruction.html#fls_zQGkVGWIzMQ7", + "number": "15.9:6" }, { - "checksum": "dbd52754db7e99e4d5eb10dddfecc9249069d7ed364a9a98bde8982c3263f039", - "id": "fls_ZfnYORORz40y", - "link": "types-and-traits.html#fls_ZfnYORORz40y", - "number": "4.12.6:5" + "checksum": "8da2ed90a465a8e65f20c991391be8fcdc1d7b7d35ec5e06dfef767d9b68cae4", + "id": "fls_K8whhUwAUVqR", + "link": "ownership-and-deconstruction.html#fls_K8whhUwAUVqR", + "number": "15.9:7" }, { - "checksum": "895a35389d5c60d90a8ab6c05a54fb1f409957ecb73275f13a7f0624e190b6f9", - "id": "fls_TE2GBnZX2YFW", - "link": "types-and-traits.html#fls_TE2GBnZX2YFW", - "number": "4.12.6:6" + "checksum": "df8e0a488a8a05da58f4656abffbbd5eb240a8a0ff05293049814a5fdf5acb4b", + "id": "fls_NpCrm4t03oQg", + "link": "ownership-and-deconstruction.html#fls_NpCrm4t03oQg", + "number": "15.9:8" }, { - "checksum": "d3cf9e0f74330b48fcaab97dc1eff8c8615a748c34a894590a2ec023e1a25b71", - "id": "fls_GNFzLMH8RBUC", - "link": "types-and-traits.html#fls_GNFzLMH8RBUC", - "number": "4.12.6:7" + "checksum": "4b4f51cb30705d0cc7632e1f0e4132697ef1cdbe4cb781a401e1baf0ec958fba", + "id": "fls_3i348l3pbtrx", + "link": "ownership-and-deconstruction.html#fls_3i348l3pbtrx", + "number": "15.9:9" }, { - "checksum": "946a22061cba769276e82b5bb8702007f5fb27a16e9935f35ead0c741219ca5b", - "id": "fls_fSNrRsgzLd0E", - "link": "types-and-traits.html#fls_fSNrRsgzLd0E", - "number": "4.12.6:8" + "checksum": "7dca09bc4bd21d1c460e1631dceb6a2e06827b223efdf7b8728a52a26de3d225", + "id": "fls_oe8l81y0wnao", + "link": "ownership-and-deconstruction.html#fls_oe8l81y0wnao", + "number": "15.9:10" }, { - "checksum": "1f521b54c1dc2d800998bde6191d77887e653d81e4ffb5009f1bb23903423e3d", - "id": "fls_R65LmGTvSlIm", - "link": "types-and-traits.html#fls_R65LmGTvSlIm", - "number": "4.12.6:9" + "checksum": "61f14bc12e0d1c55c7aa99cf8c4e500d1effa786d1f50cc8ac8463693abeb1fa", + "id": "fls_4sgca9wcl8h0", + "link": "ownership-and-deconstruction.html#fls_4sgca9wcl8h0", + "number": "15.9:11" }, { - "checksum": "07c752400b608840bb4af64f8adad7fafef68d2a16ef58430bd1c677a633204c", - "id": "fls_yiPv3wKYkSRl", - "link": "types-and-traits.html#fls_yiPv3wKYkSRl", - "number": "4.12.6:10" + "checksum": "2ac52c80f7a45d49874b8b452b7d5541e0200bef9c38d6f5b32c0e20bf9296b3", + "id": "fls_a2m4ibzhgupa", + "link": "ownership-and-deconstruction.html#fls_a2m4ibzhgupa", + "number": "15.9:12" }, { - "checksum": "e534f969047e49d4603cc8e39b563a82cafefe659dace5e02c2b5e02e558b550", - "id": "fls_S9QL6yVF5LFI", - "link": "types-and-traits.html#fls_S9QL6yVF5LFI", - "number": "4.12.6:11" - } - ], - "title": "Visible Emptiness" - }, - { - "id": "fls_lv7w7aalpwm5", - "informational": false, - "link": "types-and-traits.html#type-inference", - "number": "4.12.7", - "paragraphs": [ + "checksum": "5cbdd388b3d5de9f7fcb63733ebb916504fce634b415f2fcd87d3683cdcb52c6", + "id": "fls_go3bvd23vzi9", + "link": "ownership-and-deconstruction.html#fls_go3bvd23vzi9", + "number": "15.9:13" + }, { - "checksum": "0733340fbfcaed4e1531c4bbd915b1e25c8a7f93c5258f26dc010a4fe224801a", - "id": "fls_h8sedxew0d4u", - "link": "types-and-traits.html#fls_h8sedxew0d4u", - "number": "4.12.7:1" + "checksum": "095cc1d51b861087a618aa1e4e9fc0377405a71c6d4d02c44502cfbfb34e962e", + "id": "fls_7rwo0he8x143", + "link": "ownership-and-deconstruction.html#fls_7rwo0he8x143", + "number": "15.9:14" }, { - "checksum": "26ecf6194c3f105ef5dfdc03e0b71e4a1c8160edb540c06c1056e324e136e1e1", - "id": "fls_ybvrhh96fc7y", - "link": "types-and-traits.html#fls_ybvrhh96fc7y", - "number": "4.12.7:2" + "checksum": "beaea22f979a7a281290ec83d8d0ac0786a9365b64f973a51a5c48539df32310", + "id": "fls_43yqlxjr3a10", + "link": "ownership-and-deconstruction.html#fls_43yqlxjr3a10", + "number": "15.9:15" }, { - "checksum": "a923067c037b043e99981b0ca0ebdb47199c07b67e084d5a7bc220e594b133e1", - "id": "fls_EWBilpepaDcX", - "link": "types-and-traits.html#fls_EWBilpepaDcX", - "number": "4.12.7:3" + "checksum": "b0246b982f33561e6c330d7de3f8c8d00db491873c9db1eda52117c58981edbc", + "id": "fls_a7lsq2kkzkk4", + "link": "ownership-and-deconstruction.html#fls_a7lsq2kkzkk4", + "number": "15.9:16" }, { - "checksum": "f0ad52d5ba7639c36c518c3720cb06c3552839e842057b6d3ee4ba143ab4be1d", - "id": "fls_NYSzcvf5nQpi", - "link": "types-and-traits.html#fls_NYSzcvf5nQpi", - "number": "4.12.7:4" + "checksum": "50a6c72b40cc9859cd38ff5a24c35677fdd68698fea0be3c96f0f0c910021008", + "id": "fls_dhfIPP4yR3Tt", + "link": "ownership-and-deconstruction.html#fls_dhfIPP4yR3Tt", + "number": "15.9:17" }, { - "checksum": "f9c9598c2dd792f8150bbc7db9744da31f5c54d1398f56477bc718402556dab3", - "id": "fls_htLp5J5ObgNh", - "link": "types-and-traits.html#fls_htLp5J5ObgNh", - "number": "4.12.7:5" + "checksum": "11982a419dd799bae7bb0dc8cec5d03b09cc19e27def8ff9c62098ebfb3d06c3", + "id": "fls_zxFM7EoE2Xq8", + "link": "ownership-and-deconstruction.html#fls_zxFM7EoE2Xq8", + "number": "15.9:18" }, { - "checksum": "20eabdd38eacdb13f67b19026f6ad726531238743b530c057d28a339a5e5db77", - "id": "fls_cPlCLGCcl7EK", - "link": "types-and-traits.html#fls_cPlCLGCcl7EK", - "number": "4.12.7:6" + "checksum": "61083f1dbe74584643880f67bbc58f8821e4a546ebd5064b350c5e383546b22b", + "id": "fls_093YxG6YXQz2", + "link": "ownership-and-deconstruction.html#fls_093YxG6YXQz2", + "number": "15.9:19" }, { - "checksum": "ddd1c8f0bf52f83dc4686954218b0f4cd13dfd720d985efc7e5e7cd1fb987375", - "id": "fls_KphY5qHev0Dc", - "link": "types-and-traits.html#fls_KphY5qHev0Dc", - "number": "4.12.7:7" + "checksum": "9e8d95121910f51f870a1f2ecaa4380de9bec07541f9e0432cb14042f6428351", + "id": "fls_gNWXh61ZXXt8", + "link": "ownership-and-deconstruction.html#fls_gNWXh61ZXXt8", + "number": "15.9:20" + } + ], + "title": "Drop Order" + } + ], + "title": "Ownership and Destruction" + }, + { + "informational": false, + "link": "types-and-traits.html", + "sections": [ + { + "id": "fls_vgb6ev541b2r", + "informational": false, + "link": "types-and-traits.html", + "number": "4", + "paragraphs": [], + "title": "Types and Traits" + }, + { + "id": "fls_kwsbxmqntrnl", + "informational": false, + "link": "types-and-traits.html#types", + "number": "4.1", + "paragraphs": [ + { + "checksum": "0f3224c81474a43ebf7a037ebb226c3b28b450e79a50de818459bb6d93a03b4d", + "id": "fls_4rhjpdu4zfqj", + "link": "types-and-traits.html#fls_4rhjpdu4zfqj", + "number": "4.1:1" }, { - "checksum": "50c6910a7fc8c1b07b987b8c629d5666817afcdfd7a77af3e544b2df055354eb", - "id": "fls_67Bf5kR5OtYW", - "link": "types-and-traits.html#fls_67Bf5kR5OtYW", - "number": "4.12.7:8" - }, + "checksum": "d7948a2adf90ff0fa015b4f90967c0801c9271d1aaf7f17e291db8a682522035", + "id": "fls_0yaYKnFrJkhG", + "link": "types-and-traits.html#fls_0yaYKnFrJkhG", + "number": "4.1:2" + } + ], + "title": "Types" + }, + { + "id": "fls_963gsjp2jas2", + "informational": true, + "link": "types-and-traits.html#type-classification", + "number": "4.2", + "paragraphs": [ { - "checksum": "cd59d75b8786aa2152a00a74d15960f972ee7b9efd9ed6ea6be282ff7bb0393f", - "id": "fls_sAS3vQpkjksr", - "link": "types-and-traits.html#fls_sAS3vQpkjksr", - "number": "4.12.7:9" + "checksum": "23b6212015d6348a703cb5be2c0d3c0f812bfb0ece7139873025d578f50472c1", + "id": "fls_c4xe3pkn0n3o", + "link": "types-and-traits.html#fls_c4xe3pkn0n3o", + "number": "4.2:1" }, { - "checksum": "6f383a50f25c7293a36c28b2eb734e84299ba6ad3b35e8d967d2b84845ccebc4", - "id": "fls_Sowatt1V988J", - "link": "types-and-traits.html#fls_Sowatt1V988J", - "number": "4.12.7:10" + "checksum": "e9bace1e5151ffdeeff57cb5069120c81735b17734fa05f4c4075277f6e081ad", + "id": "fls_69zyas59o8ff", + "link": "types-and-traits.html#fls_69zyas59o8ff", + "number": "4.2:2" }, { - "checksum": "30466e79ab50a4ce686cad40eea4346e303925d4a01f36024720a13e48a89b4f", - "id": "fls_A1NVYkepoaMk", - "link": "types-and-traits.html#fls_A1NVYkepoaMk", - "number": "4.12.7:11" + "checksum": "40842ff24d030ea0ccb5075ecd6ec1492cd9d9c98e960ed9563cfe03bb7e2487", + "id": "fls_65hcyqizo1da", + "link": "types-and-traits.html#fls_65hcyqizo1da", + "number": "4.2:3" }, { - "checksum": "6051ca9ecfe04a062d5a946c136ee02de33353f5ed963a07c287899720961af0", - "id": "fls_J77VeAlD8rsv", - "link": "types-and-traits.html#fls_J77VeAlD8rsv", - "number": "4.12.7:12" + "checksum": "cb6e139cbd46fe7899e08fd9f0c9a48a4bccedd925c0a7bcf78f8babca270948", + "id": "fls_zge99l49az8w", + "link": "types-and-traits.html#fls_zge99l49az8w", + "number": "4.2:4" }, { - "checksum": "aa67d478f734851497c9592cfcce1e63452ca21e44b8f258ca57c2fd00174c93", - "id": "fls_P9uiamxA4HE3", - "link": "types-and-traits.html#fls_P9uiamxA4HE3", - "number": "4.12.7:13" + "checksum": "e8e0cdfb96e0c3ecba3291745e9a1299586805ca7b8b0aecf19b7d32f235e8f7", + "id": "fls_vizoconv3ir", + "link": "types-and-traits.html#fls_vizoconv3ir", + "number": "4.2:5" }, { - "checksum": "6dd1a00950a9e651a699734e82b1208e81181f5202936ac1f1028fa651d02a27", - "id": "fls_5d4hw3gj4w4n", - "link": "types-and-traits.html#fls_5d4hw3gj4w4n", - "number": "4.12.7:14" + "checksum": "25f59f09cdc29f543ccae977ee66e0ee9b0fee1d55202cfa983eabe4f00b16a3", + "id": "fls_ne6bgnh1eyrj", + "link": "types-and-traits.html#fls_ne6bgnh1eyrj", + "number": "4.2:6" }, { - "checksum": "b6a171c627874b9918c71b9cf66fa1fae6c874bb3359b2d6cba89379886281a9", - "id": "fls_qlovdtcj1v1b", - "link": "types-and-traits.html#fls_qlovdtcj1v1b", - "number": "4.12.7:15" + "checksum": "00ac1f2e576d6d3d14dcb244d363bf14795ddfee222c420efa28a171c91598f2", + "id": "fls_jvj8l8366kl2", + "link": "types-and-traits.html#fls_jvj8l8366kl2", + "number": "4.2:7" }, { - "checksum": "38de11a4893adbf77df8542f9ecbe28d5ee3b2986a809383ce4e0bf694d0a106", - "id": "fls_Z5gKFjZW5rRA", - "link": "types-and-traits.html#fls_Z5gKFjZW5rRA", - "number": "4.12.7:16" + "checksum": "65b9ca746467c1af24795a49cf507e5a0494d59801db4665214cf5d87fb169db", + "id": "fls_eek1jn1rwjh9", + "link": "types-and-traits.html#fls_eek1jn1rwjh9", + "number": "4.2:8" }, { - "checksum": "7dd786d9014c1823ba3515b3ea9f68d750d2db96ee522b6f4546aa39d4f678cf", - "id": "fls_vYvumjTQH9Xg", - "link": "types-and-traits.html#fls_vYvumjTQH9Xg", - "number": "4.12.7:17" + "checksum": "a23c0b697c2b2e686673f2956a5b9c81a3369d2a7897c67840ba014cc59c625d", + "id": "fls_s0aduyvz4i7f", + "link": "types-and-traits.html#fls_s0aduyvz4i7f", + "number": "4.2:9" }, { - "checksum": "d8578c25c7dd2694e72bf1900ec0de0b6ea38280d2f75db22a3f5249f98fb764", - "id": "fls_QaGKt99CmvF6", - "link": "types-and-traits.html#fls_QaGKt99CmvF6", - "number": "4.12.7:18" + "checksum": "b59affa079dfb0e436f28a313ff4e96d3831d765ff0063eb18342f6e749c79a7", + "id": "fls_zb5e79ai7w5i", + "link": "types-and-traits.html#fls_zb5e79ai7w5i", + "number": "4.2:10" }, { - "checksum": "53bb50d4c95b2e725abb10288ad64df42968237c86530bd3bee7f867075fab87", - "id": "fls_RJIeW597XRvS", - "link": "types-and-traits.html#fls_RJIeW597XRvS", - "number": "4.12.7:19" + "checksum": "4eb758fbe24b08d1aee74c97d4f8541b0dbcb444784a6569c21d17094fb49fb1", + "id": "fls_yjp19vt46asy", + "link": "types-and-traits.html#fls_yjp19vt46asy", + "number": "4.2:11" }, { - "checksum": "229d0d5b05eb5ff42588ebd5ac4c95a095a29d82ff3f26152bebfe73e02bbc3e", - "id": "fls_veG2D64fIXvo", - "link": "types-and-traits.html#fls_veG2D64fIXvo", - "number": "4.12.7:20" + "checksum": "1e5dc2e803a63fd05b7dec479ef1d0add6eea160c45fe290772c02fd26bc89d9", + "id": "fls_xflj5df6upc7", + "link": "types-and-traits.html#fls_xflj5df6upc7", + "number": "4.2:12" }, { - "checksum": "f5d2a97e84c341b7cc97283f2c6fff644af2eaa3f18136ae71b93c5ee9b56241", - "id": "fls_uvvn4usfsbhr", - "link": "types-and-traits.html#fls_uvvn4usfsbhr", - "number": "4.12.7:21" + "checksum": "b85bfd2e2cfc13a7fc78a87c008d62c82d235fb2d2d7e4d188df4acda7e8aac5", + "id": "fls_u43jnp9jnw29", + "link": "types-and-traits.html#fls_u43jnp9jnw29", + "number": "4.2:13" }, { - "checksum": "dec24d875b5094ddba99326af168b3189c2c89ffe2f65919c91d6df57ae7013a", - "id": "fls_gDalJm1XS0mi", - "link": "types-and-traits.html#fls_gDalJm1XS0mi", - "number": "4.12.7:22" + "checksum": "454e730423cb4d7f069989e61ff7aa256e85e03a5c1e7b29e64e627e192c47e1", + "id": "fls_lric8bf631nw", + "link": "types-and-traits.html#fls_lric8bf631nw", + "number": "4.2:14" }, { - "checksum": "2fba60344712b36c7e3d85a11370baa6cf316b8b2a345d1b4b3f0dd61383b631", - "id": "fls_7ov36fpd9mwe", - "link": "types-and-traits.html#fls_7ov36fpd9mwe", - "number": "4.12.7:23" + "checksum": "e821be0746413ff9f5096aec87f2ce2d467ea6cb27fb68fa64c8a116c0207238", + "id": "fls_98djh9avlqc0", + "link": "types-and-traits.html#fls_98djh9avlqc0", + "number": "4.2:15" }, { - "checksum": "6f3816132ffe28efaf427fe33b1433ad594067052bda3f57277c1451893dbbc3", - "id": "fls_3hv3wxkhjjp1", - "link": "types-and-traits.html#fls_3hv3wxkhjjp1", - "number": "4.12.7:24" + "checksum": "fe0aac8860ab0c5746c0c07b373e29983a82fc0ebd52f6dd2630ace413a9fabb", + "id": "fls_b3ymsm8dmo4", + "link": "types-and-traits.html#fls_b3ymsm8dmo4", + "number": "4.2:16" }, { - "checksum": "605267afb8b9036c0eee2345a972043cff7c21380f985f12caaba2f7d30aee0a", - "id": "fls_bXQ63GYYDuMp", - "link": "types-and-traits.html#fls_bXQ63GYYDuMp", - "number": "4.12.7:25" + "checksum": "c36d1f9962298745f389857334fa11b8218d392e0116702a80ad091719503e13", + "id": "fls_9x5atvhdq0j2", + "link": "types-and-traits.html#fls_9x5atvhdq0j2", + "number": "4.2:17" }, { - "checksum": "fbdb2b0ff1c5aa038413a78be7e9a6ff41c0612db0648417cc31a20a682f98a0", - "id": "fls_JryXiKBIFvF3", - "link": "types-and-traits.html#fls_JryXiKBIFvF3", - "number": "4.12.7:26" + "checksum": "e46c85f680edc5614a12b9207c497460b0042af968c92f40fa5ab637e14571a2", + "id": "fls_n5rgqgnxk9to", + "link": "types-and-traits.html#fls_n5rgqgnxk9to", + "number": "4.2:18" }, { - "checksum": "59854f2b212e191c9c0c639c1bb9caad6bdf38f48c95024fb3a49ae93818cd30", - "id": "fls_rvj3XspFZ1u3", - "link": "types-and-traits.html#fls_rvj3XspFZ1u3", - "number": "4.12.7:27" + "checksum": "eaf6ddda440baca7044641c33a4f8d9d84b75648732ea1f9dc8bbb44641bbb7a", + "id": "fls_s7ndqc5sizdy", + "link": "types-and-traits.html#fls_s7ndqc5sizdy", + "number": "4.2:19" }, { - "checksum": "8ccd07581891d7328efac206e6e8a5952ef3b60a40fbf3b2b43182fc636b4fb8", - "id": "fls_6GrNr2izovRN", - "link": "types-and-traits.html#fls_6GrNr2izovRN", - "number": "4.12.7:28" + "checksum": "64386326aff45271fd69d938c23b27ac3d34103a3520b4b76a78cf860663636b", + "id": "fls_jrohsv7hx7yw", + "link": "types-and-traits.html#fls_jrohsv7hx7yw", + "number": "4.2:20" }, { - "checksum": "0557c8604fc016c9a1c036a9f02b61c95d2b7d1d6a0043aea89868044534e8f4", - "id": "fls_9dSltJ6U98Fo", - "link": "types-and-traits.html#fls_9dSltJ6U98Fo", - "number": "4.12.7:29" + "checksum": "da5f5e5fcdb5726c1a0dabc99f8770f5e3fbae1459f5833ff33532110822caf6", + "id": "fls_1kg1mknf4yx7", + "link": "types-and-traits.html#fls_1kg1mknf4yx7", + "number": "4.2:21" }, { - "checksum": "1f73592f04b242864c2f82f5c27c59de5ca04bb1db0fb55e1634cf3d074eff81", - "id": "fls_v5dWGuBKvQSJ", - "link": "types-and-traits.html#fls_v5dWGuBKvQSJ", - "number": "4.12.7:30" + "checksum": "22c62b9e8996f6fbd50fec2e75e53a84a790744792c0aa40834de29215569f84", + "id": "fls_bw8zutjcteki", + "link": "types-and-traits.html#fls_bw8zutjcteki", + "number": "4.2:22" }, { - "checksum": "849706ce4bb64a8cb1a45280565a1bd62f3e35ac9862270ec8aabd354c93aa06", - "id": "fls_SZgixDCAx6PQ", - "link": "types-and-traits.html#fls_SZgixDCAx6PQ", - "number": "4.12.7:31" + "checksum": "b0d2ad5646194c0d8310df4d3333f7afb79755cea464d60a3fbda29273383ef6", + "id": "fls_nqezuc9u6wpn", + "link": "types-and-traits.html#fls_nqezuc9u6wpn", + "number": "4.2:23" }, { - "checksum": "dbbbf92cb2589a9aaaf11ef14b6aeb380d37d5f6b741d4cb2c5b1fa2e5a1aecb", - "id": "fls_XYY1U9h9HlAa", - "link": "types-and-traits.html#fls_XYY1U9h9HlAa", - "number": "4.12.7:32" + "checksum": "f1a647d67b3a801d2ab2b7a315771e5d26e67d2662bb331181a88afa5c152bc1", + "id": "fls_lh52q6f6snfh", + "link": "types-and-traits.html#fls_lh52q6f6snfh", + "number": "4.2:24" }, { - "checksum": "4b63ff86116c425fe2cddd007442d0c03073678cf0cdc42b62c7224845aaa0e3", - "id": "fls_1rnssw39aRWn", - "link": "types-and-traits.html#fls_1rnssw39aRWn", - "number": "4.12.7:33" + "checksum": "9ca4aa25f308f26f33fb8ec4fe5dec350c1d8526c6675053e5be2719e863556f", + "id": "fls_qqg0uixrd1a4", + "link": "types-and-traits.html#fls_qqg0uixrd1a4", + "number": "4.2:25" }, { - "checksum": "8e96bbc341a6b1de1d507f569d8d34e94e53b067964a454afed96b6b4bdb7d05", - "id": "fls_aYJaZXcOVVyk", - "link": "types-and-traits.html#fls_aYJaZXcOVVyk", - "number": "4.12.7:34" + "checksum": "d8cca7522f47f0479ce66bd850d2931ae4f7c4c1d4b198e4aad88f9b90a1ac07", + "id": "fls_b8ecqp2argmn", + "link": "types-and-traits.html#fls_b8ecqp2argmn", + "number": "4.2:26" }, { - "checksum": "24c8209ffc45df2596e294640bba2aa7b42f5686d8a0e98b7f5df152a82815d5", - "id": "fls_X8kLC7JwiF0A", - "link": "types-and-traits.html#fls_X8kLC7JwiF0A", - "number": "4.12.7:35" + "checksum": "21c1d49526e5ca8d23c874a230759bb497548ab543740fce1095850fdc4fcf63", + "id": "fls_m5vtcars8aga", + "link": "types-and-traits.html#fls_m5vtcars8aga", + "number": "4.2:27" }, { - "checksum": "e434416bfa8e8dffc8e6c1164cd594db7a1eb893254a271421442b4a889a0d70", - "id": "fls_ZFQhOxO3jpby", - "link": "types-and-traits.html#fls_ZFQhOxO3jpby", - "number": "4.12.7:36" + "checksum": "753ab4b03e9c965a6fffd92d30e2769c5a63a344bfb1b7e76971ac8499be82d8", + "id": "fls_lw38557rqikt", + "link": "types-and-traits.html#fls_lw38557rqikt", + "number": "4.2:28" }, { - "checksum": "49ca000ce5a71f5ae340a653f432992078bb8394c14aaba4e946158942b0ea9e", - "id": "fls_l1G52a0qqEes", - "link": "types-and-traits.html#fls_l1G52a0qqEes", - "number": "4.12.7:37" + "checksum": "0dd8bcf2a274d471aaa36ab380441dd3b6ca39976303324ddd118e50013d02dd", + "id": "fls_jxn63ow9xby3", + "link": "types-and-traits.html#fls_jxn63ow9xby3", + "number": "4.2:29" }, { - "checksum": "6c39d9fa3aff34c355cc05dcaefa243378bdafc12a5968b7239c801d4ad37d50", - "id": "fls_Tx4Sx4Qy8y2d", - "link": "types-and-traits.html#fls_Tx4Sx4Qy8y2d", - "number": "4.12.7:38" + "checksum": "1fa312839ea42e8a1bbcaecbc879efed22264c8ac233c6cfe5339ac2db24a2b3", + "id": "fls_a81tweobvm0p", + "link": "types-and-traits.html#fls_a81tweobvm0p", + "number": "4.2:30" + } + ], + "title": "Type Classification" + }, + { + "id": "fls_id66vnaqw0zt", + "informational": false, + "link": "types-and-traits.html#scalar-types", + "number": "4.3", + "paragraphs": [], + "title": "Scalar Types" + }, + { + "id": "fls_tiqp1gxf116z", + "informational": false, + "link": "types-and-traits.html#bool-type", + "number": "4.3.1", + "paragraphs": [ + { + "checksum": "7c27f15ca1c523ef1db5071f2a63bedb21181cf04aa94d9878250ca53b434971", + "id": "fls_h5994su1yft3", + "link": "types-and-traits.html#fls_h5994su1yft3", + "number": "4.3.1:1" }, { - "checksum": "1819eb47223394c7033f3abd693ab921b11aff33067751eb8cd6863b755d3f61", - "id": "fls_Nbdth8N0PSRq", - "link": "types-and-traits.html#fls_Nbdth8N0PSRq", - "number": "4.12.7:39" + "checksum": "930adef3b1506fb337f66675f973e54769ff8fca1db32c838183ef22a2422029", + "id": "fls_v8atmrwz6wzk", + "link": "types-and-traits.html#fls_v8atmrwz6wzk", + "number": "4.3.1:2" }, { - "checksum": "74689e53776dd9f93adcf731cfd28c8cf305612f2ba7174d640713d10f582686", - "id": "fls_hISRWZUuqE4Q", - "link": "types-and-traits.html#fls_hISRWZUuqE4Q", - "number": "4.12.7:40" + "checksum": "9858c13d3708357c30873cd608d2b3383dcc3035f0dd687ef3896541dd1695fd", + "id": "fls_iye7ho2ynyhn", + "link": "types-and-traits.html#fls_iye7ho2ynyhn", + "number": "4.3.1:3" }, { - "checksum": "27a406335ebbb1fd6b5a75a0757865b493c7c597c042bfc555c7188be7f10991", - "id": "fls_ygi1ACJ0RkfS", - "link": "types-and-traits.html#fls_ygi1ACJ0RkfS", - "number": "4.12.7:41" + "checksum": "ac65b3d93d3994e5d0068317e02275db13c2c1913d48494bb2de79b24ec72eec", + "id": "fls_7nd5tixyqir8", + "link": "types-and-traits.html#fls_7nd5tixyqir8", + "number": "4.3.1:4" }, { - "checksum": "588b531763a50551cc01572cee3f92331ffeca722aee95bdcef6579dc6a1df49", - "id": "fls_97Fxlv2KN6QF", - "link": "types-and-traits.html#fls_97Fxlv2KN6QF", - "number": "4.12.7:42" + "checksum": "1d12935dda46819ad41ab1cb8f82b49105c7771bd832109bde39a210a366c23f", + "id": "fls_w2dzqq54fjhb", + "link": "types-and-traits.html#fls_w2dzqq54fjhb", + "number": "4.3.1:5" }, { - "checksum": "13a1275180d2c104a792fff30db7a7ca08c5753e22d660c88f6f8fd5714a2ce6", - "id": "fls_hzXqj6YT1mFr", - "link": "types-and-traits.html#fls_hzXqj6YT1mFr", - "number": "4.12.7:43" + "checksum": "b94216bb8df77b4a82ab94c901933b3b6d6d065364960315dab51522e1c8c31e", + "id": "fls_ufmd38hi9t9y", + "link": "types-and-traits.html#fls_ufmd38hi9t9y", + "number": "4.3.1:6" }, { - "checksum": "f1e4f473dd23e3b9cf44076d9391ecb3e1b4acd8b5e43e81c022030fc8ee42e9", - "id": "fls_Kv0dzoMODtdy", - "link": "types-and-traits.html#fls_Kv0dzoMODtdy", - "number": "4.12.7:44" + "checksum": "99e00eb99e3b71db0f644227f19ee709ef82e2c6f386deaa36c2a399f7979685", + "id": "fls_5allcjkjnon2", + "link": "types-and-traits.html#fls_5allcjkjnon2", + "number": "4.3.1:7" }, { - "checksum": "5d1d8604e42079634aa8e88ee40d69c6321bb9e01bccd24ed821e193b76cb937", - "id": "fls_5v6TR7oqOwFM", - "link": "types-and-traits.html#fls_5v6TR7oqOwFM", - "number": "4.12.7:45" + "checksum": "09b53e4da86723e2bd2011d45d92331dab43e0172cfef5543bb7c74a77d1fc62", + "id": "fls_3bibysz95ktn", + "link": "types-and-traits.html#fls_3bibysz95ktn", + "number": "4.3.1:8" }, { - "checksum": "676707c30188643f9aee026a919dfa809da219d59e53f2bdc47e3e89d5e5c973", - "id": "fls_Gwx0Kfx68DXL", - "link": "types-and-traits.html#fls_Gwx0Kfx68DXL", - "number": "4.12.7:46" + "checksum": "6cb77addba9281730832f2bbc4ddd2082c3530a2c17878646b47db6c10dd80cf", + "id": "fls_fxq19dqtmifj", + "link": "types-and-traits.html#fls_fxq19dqtmifj", + "number": "4.3.1:9" }, { - "checksum": "f8723a3646aa5ab3733bf3c732510697a8de3a235b3982cf3dd70d352f129ede", - "id": "fls_J6ydUCCJp1Sn", - "link": "types-and-traits.html#fls_J6ydUCCJp1Sn", - "number": "4.12.7:47" + "checksum": "98f3e91912ee93fd291d169f31739e7c2c7b38d644d27336dfff508ebb7dfb99", + "id": "fls_drhpcwoblcux", + "link": "types-and-traits.html#fls_drhpcwoblcux", + "number": "4.3.1:10" }, { - "checksum": "6488fc5972643c8005b9ff665490a63292c4f336c2a0a5236c0539a65816b2d6", - "id": "fls_FSQqHs8T4bUx", - "link": "types-and-traits.html#fls_FSQqHs8T4bUx", - "number": "4.12.7:48" + "checksum": "8a7e7470fb69b8816a48fa5e95512a5456665e561c4134f050a155f985eca72b", + "id": "fls_v86qrsqcs3nd", + "link": "types-and-traits.html#fls_v86qrsqcs3nd", + "number": "4.3.1:11" }, { - "checksum": "fc18f70656fef1ef9b09d7ca8f249620b596fb1b8c70efba58e3a5a678b21213", - "id": "fls_0HHC1iOk5dwz", - "link": "types-and-traits.html#fls_0HHC1iOk5dwz", - "number": "4.12.7:49" + "checksum": "1c7470e26077ea7a073dc2a1445c678484af7588bb3235586c9091f71603cc7e", + "id": "fls_dd49lb2k3erc", + "link": "types-and-traits.html#fls_dd49lb2k3erc", + "number": "4.3.1:12" }, { - "checksum": "27509db5c82720088c62ec3e141b026c1e322a7186cb994b8a0d5e924203e03e", - "id": "fls_QZWTS0Giy3I3", - "link": "types-and-traits.html#fls_QZWTS0Giy3I3", - "number": "4.12.7:50" + "checksum": "32464bf4444f9dc7d6b1679119e45d5351fa00d79dea5e7ad487d3ddf76fc881", + "id": "fls_t6ef5x4x5poi", + "link": "types-and-traits.html#fls_t6ef5x4x5poi", + "number": "4.3.1:13" }, { - "checksum": "b5d0949443935576123b18cca27070bb53af6c5002605c2aec5715f19a860365", - "id": "fls_KJsIu1lgVZxP", - "link": "types-and-traits.html#fls_KJsIu1lgVZxP", - "number": "4.12.7:51" + "checksum": "bd7ead8e9ea1b7683e87bfdc7788222d47c759c66c031ef374dda05805dbd6d3", + "id": "fls_kqtgjgn1hqrj", + "link": "types-and-traits.html#fls_kqtgjgn1hqrj", + "number": "4.3.1:14" }, { - "checksum": "e676b83659cdf96c2848aaad17275d4f1455977a2a1ec7bd5732238283eb9382", - "id": "fls_KRQxdSav1KBA", - "link": "types-and-traits.html#fls_KRQxdSav1KBA", - "number": "4.12.7:52" + "checksum": "9a544d8e0daca2681dbcfedf72121255fea14ab0944581480d57fba0890b95fe", + "id": "fls_ws15ilzf8n6z", + "link": "types-and-traits.html#fls_ws15ilzf8n6z", + "number": "4.3.1:15" }, { - "checksum": "0bf1dd0f944f4720a843ee0ff984ac3d257f69cc6ba1b5214da858e40de1d6c3", - "id": "fls_Mcpwyvz47SoG", - "link": "types-and-traits.html#fls_Mcpwyvz47SoG", - "number": "4.12.7:53" + "checksum": "f3145e61ced027d25036f3200ca5bbb0618bc0477cb9b3603a1413652b3ac917", + "id": "fls_ni4mgq3mouek", + "link": "types-and-traits.html#fls_ni4mgq3mouek", + "number": "4.3.1:16" }, { - "checksum": "18ee894162cc21546a88f2a251a9f6bfe341f49b0bdb784abc72aba91b0d6ae6", - "id": "fls_34AQ9g7xhdUj", - "link": "types-and-traits.html#fls_34AQ9g7xhdUj", - "number": "4.12.7:54" + "checksum": "ac4e3a21890e3221845618cc38dbace293fd0e68d017770aa5e7e78e2f5ab08e", + "id": "fls_6c9ax4qsr1gy", + "link": "types-and-traits.html#fls_6c9ax4qsr1gy", + "number": "4.3.1:17" }, { - "checksum": "8842ea78c118edaa940f0f0073d6e7c3290bc1f164964dd50de8eb727650dc48", - "id": "fls_4ZT35povCL04", - "link": "types-and-traits.html#fls_4ZT35povCL04", - "number": "4.12.7:55" + "checksum": "2385dd82b0f0349c33f8b5af00d47e3440494e1faa8b63553050feb2a8459e67", + "id": "fls_sqcgvpr4egtx", + "link": "types-and-traits.html#fls_sqcgvpr4egtx", + "number": "4.3.1:18" }, { - "checksum": "8b10910fa086c26e1e94705d9398a51c68e7a3c6a9a4578d4fc204abc3be6d40", - "id": "fls_62OcWZaVN9hh", - "link": "types-and-traits.html#fls_62OcWZaVN9hh", - "number": "4.12.7:56" + "checksum": "441b5d0aa5e6c402c6beb7fde327f5827525511f230577ba7c6fe5a18d28539c", + "id": "fls_9ys0itbp4okd", + "link": "types-and-traits.html#fls_9ys0itbp4okd", + "number": "4.3.1:19" }, { - "checksum": "7412650a46a74061d81ffa512d2179f23b1edf74b3872464cc7f6b596caaaadd", - "id": "fls_st9onPgDrc8y", - "link": "types-and-traits.html#fls_st9onPgDrc8y", - "number": "4.12.7:57" + "checksum": "9480e4048cd08398ae0f13cb516a29613f3e742d3d99bc779578398b92272c24", + "id": "fls_b46gbyid15zx", + "link": "types-and-traits.html#fls_b46gbyid15zx", + "number": "4.3.1:20" }, { - "checksum": "82e588ccaea063d84f7b82b1242d7802cd1df6d675a27ce6e2bd69459a9c21ab", - "id": "fls_F999gqcBfff9", - "link": "types-and-traits.html#fls_F999gqcBfff9", - "number": "4.12.7:58" + "checksum": "166b43ef343f45896926b2fbb09d952a0c23d942c2a113f0860ffe979b8437b3", + "id": "fls_f8ag276ecbze", + "link": "types-and-traits.html#fls_f8ag276ecbze", + "number": "4.3.1:21" }, { - "checksum": "ebc67189c8cb991506019fe72864d2436c9245d633826ceebaf4b8277518858e", - "id": "fls_Esa4ST7lLp8T", - "link": "types-and-traits.html#fls_Esa4ST7lLp8T", - "number": "4.12.7:59" + "checksum": "ca76c9fea33319ebf91566f9a4e1cadfeadce5282e098010c5ef67471d744f45", + "id": "fls_twwjcrcfirdi", + "link": "types-and-traits.html#fls_twwjcrcfirdi", + "number": "4.3.1:22" }, { - "checksum": "5052100ae827ec33641989b63bb47e384a21c161e2fb0240e543ae9120df54ab", - "id": "fls_9GDElCkL1UbH", - "link": "types-and-traits.html#fls_9GDElCkL1UbH", - "number": "4.12.7:60" + "checksum": "a2d98732aced3240075311e8a7738d2601f8a1e817a29a0b1d2d0ada3525abea", + "id": "fls_wovu7330vdrq", + "link": "types-and-traits.html#fls_wovu7330vdrq", + "number": "4.3.1:23" }, { - "checksum": "806a8a9c4c2a0fa926f161ee08b989bacce0c50dc99d9f154417a5c2d8731adb", - "id": "fls_0eATa6RtDNtA", - "link": "types-and-traits.html#fls_0eATa6RtDNtA", - "number": "4.12.7:61" + "checksum": "d3df0e58549b33e4a804eced95611bbf74505e40e62fb919b895a104ee2cdd93", + "id": "fls_7xopdco6iy74", + "link": "types-and-traits.html#fls_7xopdco6iy74", + "number": "4.3.1:24" }, { - "checksum": "bb3507010c18cd505e9adb4b8c8a562d3ee002bee23c005dfbd3e702c9f232f8", - "id": "fls_q1JZZMxqWXCk", - "link": "types-and-traits.html#fls_q1JZZMxqWXCk", - "number": "4.12.7:62" + "checksum": "5689110f0fe899e7c5dff9675885b2c735444de21ad35f3a1ba69dc76fb327b6", + "id": "fls_nb5cb6en2p5w", + "link": "types-and-traits.html#fls_nb5cb6en2p5w", + "number": "4.3.1:25" }, { - "checksum": "526646cd5391985fdbc176a363fbee22aff06d3523b542342418b6e8503f0e22", - "id": "fls_hH58ftCxBYzm", - "link": "types-and-traits.html#fls_hH58ftCxBYzm", - "number": "4.12.7:63" + "checksum": "699e51364da08d56388376c014ee12beee95aeae6302a1234dc8c567774a82e4", + "id": "fls_gd28wfcfs2pv", + "link": "types-and-traits.html#fls_gd28wfcfs2pv", + "number": "4.3.1:26" }, { - "checksum": "c22d990803ea4ee571303d09a964df44019da966c6d9a8cba436ae48cc9437c9", - "id": "fls_aiSI99pbAYqT", - "link": "types-and-traits.html#fls_aiSI99pbAYqT", - "number": "4.12.7:64" + "checksum": "1a3ae601414d01796aaf6b322e02874830121fe0ab9b9bf076606a5329772a19", + "id": "fls_67a7p57nzbul", + "link": "types-and-traits.html#fls_67a7p57nzbul", + "number": "4.3.1:27" }, { - "checksum": "57584da05d3dc1e8f0c830d364af56f2bb21b7f6a8a932fd90cc629d6f3de8c6", - "id": "fls_zRdxowO4eDMN", - "link": "types-and-traits.html#fls_zRdxowO4eDMN", - "number": "4.12.7:65" + "checksum": "581c5941548d5821d9a034095690bb3c131fc9ba9dffcc9a2e85aee1b8d8cd88", + "id": "fls_cq0qunw51m94", + "link": "types-and-traits.html#fls_cq0qunw51m94", + "number": "4.3.1:28" }, { - "checksum": "67e11b06e517777ca3b49bb1108497bbf9f41dd3f7a6c03b9d584e8cd270105d", - "id": "fls_IoceMi7HfqsK", - "link": "types-and-traits.html#fls_IoceMi7HfqsK", - "number": "4.12.7:66" + "checksum": "d2b050f0a32abdd45281470a08c3d6427a82b2db396f1dc7ff325370895077b6", + "id": "fls_o1e4tnh7v3db", + "link": "types-and-traits.html#fls_o1e4tnh7v3db", + "number": "4.3.1:29" }, { - "checksum": "6a0bf506b4c1329bf0fd129c93e06a2ee1d0d06fcc05379daa9446f90b84030e", - "id": "fls_lDkPMB5UI58B", - "link": "types-and-traits.html#fls_lDkPMB5UI58B", - "number": "4.12.7:67" + "checksum": "2e6d782fc4fad2d1e3f018b424d77847ffab9b7df0a4aa34218e81df37554d42", + "id": "fls_6vnv3ygisjr", + "link": "types-and-traits.html#fls_6vnv3ygisjr", + "number": "4.3.1:30" }, { - "checksum": "37e26fff3428ba52d2fe60fe551dfdbf498213a5d36c669cda5b1014a30cad85", - "id": "fls_JKZHF3ZDHshw", - "link": "types-and-traits.html#fls_JKZHF3ZDHshw", - "number": "4.12.7:68" + "checksum": "b50023ba7c3a0f6e5d60727a63cc05db279f39c522363df22b0dd73fe324e5ff", + "id": "fls_s6m9abmmtc9i", + "link": "types-and-traits.html#fls_s6m9abmmtc9i", + "number": "4.3.1:31" }, { - "checksum": "f672813590c7e0fa2c72a85763bf792b275fbea9cd74a4607794cb8f9ac171f2", - "id": "fls_rT6zpG3cYhaF", - "link": "types-and-traits.html#fls_rT6zpG3cYhaF", - "number": "4.12.7:69" + "checksum": "3737ba54b255bd74767127bd9ba5b403b1b0aa6d139fffa616bf524ed612b7e0", + "id": "fls_s19vu65z96y5", + "link": "types-and-traits.html#fls_s19vu65z96y5", + "number": "4.3.1:32" }, { - "checksum": "8fece354838a30ef4ea331708e12dc54c63518c2e2b4a4e072f377569a53b15f", - "id": "fls_UFMyHzk6ucsT", - "link": "types-and-traits.html#fls_UFMyHzk6ucsT", - "number": "4.12.7:70" + "checksum": "cb35c3709e468c678e9fd298ff75cab85223c9b69520bb115ece6c07f023f163", + "id": "fls_2d4aqspw0wlt", + "link": "types-and-traits.html#fls_2d4aqspw0wlt", + "number": "4.3.1:33" }, { - "checksum": "bde009024b27bfe5ac2165324303983849b296af03f1d09a999b6ea05b2e33f1", - "id": "fls_hPZmcfQiNasT", - "link": "types-and-traits.html#fls_hPZmcfQiNasT", - "number": "4.12.7:71" + "checksum": "d296305e4aa3dbcbb0a1effad25118ecc1395dc6b1b33bb86459f96b9531fd3e", + "id": "fls_msjo2zd67zn1", + "link": "types-and-traits.html#fls_msjo2zd67zn1", + "number": "4.3.1:34" }, { - "checksum": "18a9afceee9c153ebb83180b204571112295c6c883a85c1be27d367890c63aa6", - "id": "fls_rgC3Iea5p9Kr", - "link": "types-and-traits.html#fls_rgC3Iea5p9Kr", - "number": "4.12.7:72" + "checksum": "1b458f804645a74fb0e0cc1aa355732c67172fd9e75c6ea28177303787c8e662", + "id": "fls_w1oti03tm1y6", + "link": "types-and-traits.html#fls_w1oti03tm1y6", + "number": "4.3.1:35" }, { - "checksum": "81163aef39eafdb0708b75f24ba80cb5182c0ef48bed2d3cf8feba5b9bef6f95", - "id": "fls_5jDBPymVKzDv", - "link": "types-and-traits.html#fls_5jDBPymVKzDv", - "number": "4.12.7:73" + "checksum": "a86bf07935fb2c1b2bb4874fe3bcea6b86ba3cf8ed709399288fd302b122b3ef", + "id": "fls_9gqd7eevbknt", + "link": "types-and-traits.html#fls_9gqd7eevbknt", + "number": "4.3.1:36" }, { - "checksum": "546efd775e9d74760187551b78d221ef19b4646f33a34b4867f7a7f270798d70", - "id": "fls_f21GNntBOxaz", - "link": "types-and-traits.html#fls_f21GNntBOxaz", - "number": "4.12.7:74" + "checksum": "a6a7931eaf2965dfdac9021f4d8a09873dfdc6abf5f148f661c3ec557f154dcd", + "id": "fls_r4o2rmhqg4br", + "link": "types-and-traits.html#fls_r4o2rmhqg4br", + "number": "4.3.1:37" }, { - "checksum": "a5590b6dee25c97033f7305a0e6f936e9da7265aea3829f4c2e7c58e4d7884a5", - "id": "fls_NpwLzJJH9cGw", - "link": "types-and-traits.html#fls_NpwLzJJH9cGw", - "number": "4.12.7:75" + "checksum": "b64f4f41ff39543e9a12b4c7c0a292db35c6c2a3b0ae33722cf7b5ec71852ce1", + "id": "fls_1n7p6ij1dpm", + "link": "types-and-traits.html#fls_1n7p6ij1dpm", + "number": "4.3.1:38" }, { - "checksum": "a041f706bb15d0161c35bfcae72a7de2e0b4e11a3e262fb649508ed8ada51bd7", - "id": "fls_56J8BlLOuvr4", - "link": "types-and-traits.html#fls_56J8BlLOuvr4", - "number": "4.12.7:76" + "checksum": "ed886b0d040a0415b87015806dc7a838d193bb1e227659410f6314a41feff113", + "id": "fls_4x27kfiodb8", + "link": "types-and-traits.html#fls_4x27kfiodb8", + "number": "4.3.1:39" }, { - "checksum": "69715763744f3c7fed67ba5ddc98229c812a0800cf36b28ee12f43fb82bf9a42", - "id": "fls_jK2pIVxOmtJ8", - "link": "types-and-traits.html#fls_jK2pIVxOmtJ8", - "number": "4.12.7:77" + "checksum": "865ad088e423aa6b12fff0c2f48ac1e92557389ec93b6582e7643d28c1707ed5", + "id": "fls_me6bf9m2ypt", + "link": "types-and-traits.html#fls_me6bf9m2ypt", + "number": "4.3.1:40" }, { - "checksum": "aac5b083447e8a2e273adb8cb2fcc744493827cedc47c65b0b3baedbd7b9743b", - "id": "fls_fjV22WcosNnt", - "link": "types-and-traits.html#fls_fjV22WcosNnt", - "number": "4.12.7:78" + "checksum": "24889b443c8a3583df4a999169a461d56a81737c8776d003ea3e7968c2670737", + "id": "fls_2j659ns8wop4", + "link": "types-and-traits.html#fls_2j659ns8wop4", + "number": "4.3.1:41" }, { - "checksum": "330e26cc6685cb1dfa82a84d2178bf60165a81473d6eed85a81862c931f9a731", - "id": "fls_h3OVuCdsKPhV", - "link": "types-and-traits.html#fls_h3OVuCdsKPhV", - "number": "4.12.7:79" + "checksum": "7b78ae0a4fa328edf20bd1ee52ca050c8a7913beeafc6215a0e8f6cbbab2a236", + "id": "fls_d09l2rl0161l", + "link": "types-and-traits.html#fls_d09l2rl0161l", + "number": "4.3.1:42" }, { - "checksum": "1a0181d42cf641e33221d4f38b0852492555c4ac8e15be51f143586d6b985c1b", - "id": "fls_be2djziKJw3I", - "link": "types-and-traits.html#fls_be2djziKJw3I", - "number": "4.12.7:80" - }, + "checksum": "938539d9e20602a05fe699126091fadcead0ba21c69cbe4a271eb3dc3c3ed250", + "id": "fls_2sd39mj05mb9", + "link": "types-and-traits.html#fls_2sd39mj05mb9", + "number": "4.3.1:43" + } + ], + "title": "Bool Type" + }, + { + "id": "fls_wrvjizrqf3po", + "informational": false, + "link": "types-and-traits.html#char-type", + "number": "4.3.2", + "paragraphs": [ { - "checksum": "ec677f1076b9878644693b68b12fa4f88ab646f06cf6b311b260e4da1befb4b5", - "id": "fls_nHt0LVSiwTB3", - "link": "types-and-traits.html#fls_nHt0LVSiwTB3", - "number": "4.12.7:81" + "checksum": "de3e486bd6bfec35ccbc18c244a07b0400fb7afad866d64ad99471d9b5fb3d51", + "id": "fls_vnwbs0exbwcn", + "link": "types-and-traits.html#fls_vnwbs0exbwcn", + "number": "4.3.2:1" }, { - "checksum": "2a6207f713e77bf4b36f34f67ee44602ec627d4216450a3fd15002b80806184f", - "id": "fls_sLCBZ3vG1AWs", - "link": "types-and-traits.html#fls_sLCBZ3vG1AWs", - "number": "4.12.7:82" + "checksum": "f9760034f4a5a74b8badb11d87b6f5ef432d7e91e80802425e89bbbbe5a20476", + "id": "fls_juysxea25owj", + "link": "types-and-traits.html#fls_juysxea25owj", + "number": "4.3.2:2" + } + ], + "title": "Char Type" + }, + { + "id": "fls_qwljwqr07slp", + "informational": false, + "link": "types-and-traits.html#numeric-types", + "number": "4.3.3", + "paragraphs": [], + "title": "Numeric Types" + }, + { + "id": "fls_b4xporvr64s", + "informational": false, + "link": "types-and-traits.html#floating-point-types", + "number": "4.3.3.1", + "paragraphs": [ + { + "checksum": "9f2ef3098e38ced762fc72ca6a4bc1dec698a33084b9c778e96312d3ffe46bf1", + "id": "fls_30yny2xb9b6b", + "link": "types-and-traits.html#fls_30yny2xb9b6b", + "number": "4.3.3.1:1" }, { - "checksum": "7bfe1b4191cc568f4965ea2ae5ca690ed51993b7fc4ec6bee03da8ea64653fb7", - "id": "fls_mCISAdm7sjRs", - "link": "types-and-traits.html#fls_mCISAdm7sjRs", - "number": "4.12.7:83" + "checksum": "cec08d9131666b39b8d3688a587aa88db5e36d49b4c55c6fa925a6d90e9338b8", + "id": "fls_yqflrq9s6p6n", + "link": "types-and-traits.html#fls_yqflrq9s6p6n", + "number": "4.3.3.1:2" }, { - "checksum": "6b0b0bc2f95b49ae5e152da970bce05f896dbf296e9728753d1d673ae72cb4c7", - "id": "fls_Fv8fj9R8prUV", - "link": "types-and-traits.html#fls_Fv8fj9R8prUV", - "number": "4.12.7:84" + "checksum": "f9a65c44f2a9e6e74c497676bea2fa3f5c8baa37891736f84802dbe50e0758d1", + "id": "fls_nuFAwLHOdQBx", + "link": "types-and-traits.html#fls_nuFAwLHOdQBx", + "number": "4.3.3.1:3" + } + ], + "title": "Floating Point Types" + }, + { + "id": "fls_3qnpv2z7yjil", + "informational": false, + "link": "types-and-traits.html#integer-types", + "number": "4.3.3.2", + "paragraphs": [ + { + "checksum": "c91bb9a77a65e19d420bace1cde89e8b2bd3a6b0a5554c2e2d6c5d4b2b8a6a45", + "id": "fls_cokwseo3nnr", + "link": "types-and-traits.html#fls_cokwseo3nnr", + "number": "4.3.3.2:1" }, { - "checksum": "84a7593ed63e9466545fae000a156e8e448806170dea5f772cb868f68f526def", - "id": "fls_QDWVv2nTufX7", - "link": "types-and-traits.html#fls_QDWVv2nTufX7", - "number": "4.12.7:85" + "checksum": "49357a257118ef34e9a0f670c27a41b0d29de7f40242b56aed6fcbd777d0759b", + "id": "fls_vk1skn6ek36u", + "link": "types-and-traits.html#fls_vk1skn6ek36u", + "number": "4.3.3.2:2" }, { - "checksum": "4e795302ddd133846579329661e9e97b490f86e981444546b66b4dd9022abb89", - "id": "fls_O2r51Xrmmj38", - "link": "types-and-traits.html#fls_O2r51Xrmmj38", - "number": "4.12.7:86" + "checksum": "78b32c0312f4dd50cbc257f858487dcb75f68d6bdf006abab97290c56ce8d417", + "id": "fls_iikexw8ps6mk", + "link": "types-and-traits.html#fls_iikexw8ps6mk", + "number": "4.3.3.2:3" }, { - "checksum": "4d4e567b4ab8ca5f4a1fb1c8c3d8df91c67fb770bd4a4cace6b5edb67fac789c", - "id": "fls_b96Zca6oFn82", - "link": "types-and-traits.html#fls_b96Zca6oFn82", - "number": "4.12.7:87" + "checksum": "b24ffd36ee78bac00ba6eef084b0bbaf5b5915c937f0c8172e3068776977c8c1", + "id": "fls_cavasxxlgs7g", + "link": "types-and-traits.html#fls_cavasxxlgs7g", + "number": "4.3.3.2:4" }, { - "checksum": "c4b02ef7e1b298b5a276082e81cd8daa172fdc32e30770b173dd4a6e774155ba", - "id": "fls_07AIc06bGnZt", - "link": "types-and-traits.html#fls_07AIc06bGnZt", - "number": "4.12.7:88" + "checksum": "9ad8b2b92eaa9597eb9af4e33da92fe59cbffaa97c890e6086435f557e43fb50", + "id": "fls_7sx92xsjx3pl", + "link": "types-and-traits.html#fls_7sx92xsjx3pl", + "number": "4.3.3.2:5" }, { - "checksum": "402fa34cae898d54c45a23952960408a04d5d8d41b0f81ab618f87b26930cb88", - "id": "fls_A36NBOl1FTCb", - "link": "types-and-traits.html#fls_A36NBOl1FTCb", - "number": "4.12.7:89" + "checksum": "b1e3f5d5efc7e3aacb83d9a6a8f313f9e28d458979266baee253365b8454376a", + "id": "fls_q9f95uet7gq4", + "link": "types-and-traits.html#fls_q9f95uet7gq4", + "number": "4.3.3.2:6" }, { - "checksum": "a7a2ae8190de32bf5aee8dd116623acd4efd8254bbc6c440a4722d6acb532232", - "id": "fls_h3mmmIBR72kV", - "link": "types-and-traits.html#fls_h3mmmIBR72kV", - "number": "4.12.7:90" + "checksum": "a9f676afe50447b4889e6b3ba228a3314e819d297389ab57b60c289c43192d76", + "id": "fls_yjb3kzijd19v", + "link": "types-and-traits.html#fls_yjb3kzijd19v", + "number": "4.3.3.2:7" }, { - "checksum": "2937ec9b056891de7aab2b57810eba0528ecc27fc3e8075a1f852903bb9b994a", - "id": "fls_8edzBBIo7jF7", - "link": "types-and-traits.html#fls_8edzBBIo7jF7", - "number": "4.12.7:91" + "checksum": "278b33ab9e405eba53e40103ea42d106398186c9d15ba8ee1995c7a728b563d3", + "id": "fls_75lntwhg20l", + "link": "types-and-traits.html#fls_75lntwhg20l", + "number": "4.3.3.2:8" }, { - "checksum": "4db1070a4a0b985d421c7c4686bb275d07c92be932852e255c6f80818a9a9a35", - "id": "fls_lUg26vFuSePP", - "link": "types-and-traits.html#fls_lUg26vFuSePP", - "number": "4.12.7:92" + "checksum": "9dec417eca723a851afba4b1fbf40615b81b0b680eaadd85d45cecd136fda1a3", + "id": "fls_p2shoji3xg5a", + "link": "types-and-traits.html#fls_p2shoji3xg5a", + "number": "4.3.3.2:9" }, { - "checksum": "397cf84d1d20fefbdb6416f126bc1fedb49f880fa10fa4a885aad6fa92e26167", - "id": "fls_21ay7EUUUmhx", - "link": "types-and-traits.html#fls_21ay7EUUUmhx", - "number": "4.12.7:93" + "checksum": "b4b087216df02d090a94bd841759e6bcd8283ef3c137cfe09f138ae17ddd5071", + "id": "fls_fsyt05u9y4sl", + "link": "types-and-traits.html#fls_fsyt05u9y4sl", + "number": "4.3.3.2:10" }, { - "checksum": "93b5392211c91ee8d504d9868a7d68ac136546dcdbbad5d24d3f577f2c88e489", - "id": "fls_8VgAhOgDOk0y", - "link": "types-and-traits.html#fls_8VgAhOgDOk0y", - "number": "4.12.7:94" + "checksum": "f3bf5c08e43865c62017a10e1b7306084527a18b37767d4c100314a6e0c1c2f6", + "id": "fls_p9ffvtajr832", + "link": "types-and-traits.html#fls_p9ffvtajr832", + "number": "4.3.3.2:11" }, { - "checksum": "7f686eb11bcf39ea892c2d0e96ad372bdc601edb69025cd3e0629e638394539d", - "id": "fls_OVVY9CE0pGtJ", - "link": "types-and-traits.html#fls_OVVY9CE0pGtJ", - "number": "4.12.7:95" + "checksum": "ec17bdab7274d1767c59acd310b20c0c01f2da8a8b7716c404d17f1cc07484ba", + "id": "fls_j6xan9f8udw7", + "link": "types-and-traits.html#fls_j6xan9f8udw7", + "number": "4.3.3.2:12" }, { - "checksum": "569bfa594da4cd446bcaad60663a04566c36afc4f5ad3d236f098f50b66fb41d", - "id": "fls_FojOvB6l3lAh", - "link": "types-and-traits.html#fls_FojOvB6l3lAh", - "number": "4.12.7:96" + "checksum": "fd5107a41862e15e8bf69245f1ca0a14bd39123490c3f4b6af8e4a980f4ec4ea", + "id": "fls_4t39p3ibkzu7", + "link": "types-and-traits.html#fls_4t39p3ibkzu7", + "number": "4.3.3.2:13" }, { - "checksum": "f42fbda3021b2099c9a16bf8df1231e066450bcc017fefa22f943451c146644b", - "id": "fls_CVfHkJq1PixR", - "link": "types-and-traits.html#fls_CVfHkJq1PixR", - "number": "4.12.7:97" + "checksum": "3c6952dc4550a4388ae5861a5f0f6e10a791a23bc1f697d783dbac6b3281a6ec", + "id": "fls_egfoxke0lzje", + "link": "types-and-traits.html#fls_egfoxke0lzje", + "number": "4.3.3.2:14" }, { - "checksum": "f38861e12ba15b26d22b39bced0e85caf806900ac14d4b691b9bfdc04c56006c", - "id": "fls_0RZ7w0YqmzE3", - "link": "types-and-traits.html#fls_0RZ7w0YqmzE3", - "number": "4.12.7:98" + "checksum": "27d4b5e93c8a23fea7497ff03639ed73f72387be79afe5bb2ef004898a152b78", + "id": "fls_4c4qpel1tbqs", + "link": "types-and-traits.html#fls_4c4qpel1tbqs", + "number": "4.3.3.2:15" }, { - "checksum": "030b457cddbacd3c66ddde1079b1b179afde8cd0c3147430bfce82fddff4d95e", - "id": "fls_YppNCEPMYqWJ", - "link": "types-and-traits.html#fls_YppNCEPMYqWJ", - "number": "4.12.7:99" + "checksum": "adc4ea56adb80d9c82a72320b251471c3e69a20eb1fca729a0ba24c65f115ede", + "id": "fls_t9oyfmgqka6u", + "link": "types-and-traits.html#fls_t9oyfmgqka6u", + "number": "4.3.3.2:16" + } + ], + "title": "Integer Types" + }, + { + "id": "fls_fbchw64p6n2x", + "informational": false, + "link": "types-and-traits.html#sequence-types", + "number": "4.4", + "paragraphs": [], + "title": "Sequence Types" + }, + { + "id": "fls_uj0kpjwyld60", + "informational": false, + "link": "types-and-traits.html#array-types", + "number": "4.4.1", + "paragraphs": [ + { + "checksum": "a0e28c81206aaca44d668b56c00cdb06b2afcb1957637817f895be1919625591", + "id": "fls_fx7b3qv3ghca", + "link": "types-and-traits.html#fls_fx7b3qv3ghca", + "number": "4.4.1:1" }, { - "checksum": "4eb0f7cb1d0475f4762ea3ac14eea3f50eec51fce9d2cd082fa76a05b372905d", - "id": "fls_SZmiJjI43fQL", - "link": "types-and-traits.html#fls_SZmiJjI43fQL", - "number": "4.12.7:100" + "checksum": "3842e56226791bfaa21a704608441353c9476f08a8ad83197c785ec4b705b6f6", + "id": "fls_pkts1p2dnxo", + "link": "types-and-traits.html#fls_pkts1p2dnxo", + "number": "4.4.1:2" }, { - "checksum": "7b85215a3effc40177a6c31907cde3fa3a7d61b57ff6c0c02e88d5e9ba01bd8e", - "id": "fls_ipWTrhF4xakC", - "link": "types-and-traits.html#fls_ipWTrhF4xakC", - "number": "4.12.7:101" + "checksum": "11446d2cbdde9d3d9a2a4c6206fbe529ac4257c999d03b3d6cddd6fbae17eefd", + "id": "fls_imr2jx6cbuzq", + "link": "types-and-traits.html#fls_imr2jx6cbuzq", + "number": "4.4.1:3" }, { - "checksum": "ba56f2bf9c4b4756691a32ecda47282715bc88ff46cf80ae2f7484635b80aa2c", - "id": "fls_zgRFKbfdI6ro", - "link": "types-and-traits.html#fls_zgRFKbfdI6ro", - "number": "4.12.7:102" + "checksum": "101671326f28fb3bee73ae610842cfdd0b7e1f09d04855a7041d29d72688b48d", + "id": "fls_r8nqxry2dlww", + "link": "types-and-traits.html#fls_r8nqxry2dlww", + "number": "4.4.1:4" }, { - "checksum": "0cb1d7de2f135ef45509098cda1f169336b740fa28b6108348f05ec0f32936ce", - "id": "fls_z2UkZX5Qdmws", - "link": "types-and-traits.html#fls_z2UkZX5Qdmws", - "number": "4.12.7:103" + "checksum": "c944934ba19e6c4ebe12cd9453b9f0747ab302ecb654be487cbd6b29567cefc9", + "id": "fls_9vjijqi9w8wn", + "link": "types-and-traits.html#fls_9vjijqi9w8wn", + "number": "4.4.1:5" + } + ], + "title": "Array Types" + }, + { + "id": "fls_vpbikb73dw4k", + "informational": false, + "link": "types-and-traits.html#slice-types", + "number": "4.4.2", + "paragraphs": [ + { + "checksum": "1dec4d96c2383bcd59d32a561f257ee89281b2f6079093fa3efe7c807374bfdc", + "id": "fls_ftvua2hlvr08", + "link": "types-and-traits.html#fls_ftvua2hlvr08", + "number": "4.4.2:1" }, { - "checksum": "35723ad98d3805267108e83cb916ccc229ff4ccba59f3dc700de834c2734089b", - "id": "fls_U46IXItEKMCB", - "link": "types-and-traits.html#fls_U46IXItEKMCB", - "number": "4.12.7:104" + "checksum": "c1765e314370989e27d28ca48d5189af4457e08b3395ee284192a5060a4dd3b7", + "id": "fls_acgtczhk8ci0", + "link": "types-and-traits.html#fls_acgtczhk8ci0", + "number": "4.4.2:2" }, { - "checksum": "2b0d9332d54179fcf153daa53495e5dcc69f8f1fc6183007301ea8d263db458f", - "id": "fls_TAJ3JJwIeDbQ", - "link": "types-and-traits.html#fls_TAJ3JJwIeDbQ", - "number": "4.12.7:105" + "checksum": "fa8054851ecf8587aa441b48e5bc8cc1e660b60f0c0542b45df2390c5f7fcea4", + "id": "fls_5gl67ftc3m21", + "link": "types-and-traits.html#fls_5gl67ftc3m21", + "number": "4.4.2:3" }, { - "checksum": "233f2aa19661d54999f5b48a7b7ceb27a0590d0f09de50a48967716f0de48064", - "id": "fls_VrpaTruoBwtF", - "link": "types-and-traits.html#fls_VrpaTruoBwtF", - "number": "4.12.7:106" + "checksum": "12398a33d4062f70cdd542b837f7158f3015de99e28242ccb150779a45e440da", + "id": "fls_nsny832ap4v1", + "link": "types-and-traits.html#fls_nsny832ap4v1", + "number": "4.4.2:4" } ], - "title": "Type Inference" + "title": "Slice Types" }, { - "id": "fls_85vx1qfa061i", + "id": "fls_4agmmu5al6gt", "informational": false, - "link": "types-and-traits.html#traits", - "number": "4.13", + "link": "types-and-traits.html#str-type", + "number": "4.4.3", "paragraphs": [ { - "checksum": "d7212257397d1d14621b886a1829ea8f8c8386cdaccfbfbe7f70c9321f85dff2", - "id": "fls_tani6lesan9u", - "link": "types-and-traits.html#fls_tani6lesan9u", - "number": "4.13:1" + "checksum": "4741170fc77c3caa889d2b61c37ca8bf858103e8dfb471f5259bf3977733f2d3", + "id": "fls_wlnoq1qoq2kr", + "link": "types-and-traits.html#fls_wlnoq1qoq2kr", + "number": "4.4.3:1" }, { - "checksum": "4dc696ed6dbb85f0cd265718a2213f335ef1caf861c1e2858c1e895b91bd05d8", - "id": "fls_PiAR1B26SoZV", - "link": "types-and-traits.html#fls_PiAR1B26SoZV", - "number": "4.13:2" + "checksum": "b76cf1e29c346895c7a64d1ebecd1285879329d64c6a566ec2b62a40874a44bf", + "id": "fls_1xa6fas6laha", + "link": "types-and-traits.html#fls_1xa6fas6laha", + "number": "4.4.3:2" }, { - "checksum": "853f1c4b6645e1f5b164185c45a96a2de4e6ddc84851ab72105e660ab292f120", - "id": "fls_Y28596CVBzDG", - "link": "types-and-traits.html#fls_Y28596CVBzDG", - "number": "4.13:3" + "checksum": "c7d00bb480fddeed611bf8a4b29accfc6ba24e9843f57af35f84b92b3d91af55", + "id": "fls_yu7r2077n9m7", + "link": "types-and-traits.html#fls_yu7r2077n9m7", + "number": "4.4.3:3" }, { - "checksum": "9e95c1f9c8e8b1d57a5759c5453c5cb606c711e191df6d096a42e1f8e45b154c", - "id": "fls_AdbbUZZgMEsQ", - "link": "types-and-traits.html#fls_AdbbUZZgMEsQ", - "number": "4.13:4" - }, + "checksum": "31fd188232e88dbd814ef19206f91c486077f1d7d40d1682c48152055d1131ee", + "id": "fls_wacoqrtzvrwu", + "link": "types-and-traits.html#fls_wacoqrtzvrwu", + "number": "4.4.3:4" + } + ], + "title": "Str Type" + }, + { + "id": "fls_4ckl3n2ko3i4", + "informational": false, + "link": "types-and-traits.html#tuple-types", + "number": "4.4.4", + "paragraphs": [ { - "checksum": "c6afddf3086e634c55738c917ada6174289ada7dde046b2ad1f620e70d67f5e9", - "id": "fls_I9JaKZelMiby", - "link": "types-and-traits.html#fls_I9JaKZelMiby", - "number": "4.13:5" + "checksum": "0e048e80d2703d29c830283c91c9363981bcaef3c5713dc5917214cdc99e0a32", + "id": "fls_bn7wmf681ngt", + "link": "types-and-traits.html#fls_bn7wmf681ngt", + "number": "4.4.4:1" }, { - "checksum": "b94347bbd83933a75718cc03b215a9d769aa3d6f969c54d90c57fb5f932c1fc9", - "id": "fls_CYtxPjK3zq2T", - "link": "types-and-traits.html#fls_CYtxPjK3zq2T", - "number": "4.13:6" + "checksum": "f90d6cbe2f05a1690b19b882378e0c5577f7f70fd8a6b6c0a9ec01d3c19d14b2", + "id": "fls_s9a36zsrfqew", + "link": "types-and-traits.html#fls_s9a36zsrfqew", + "number": "4.4.4:2" + } + ], + "title": "Tuple Types" + }, + { + "id": "fls_wdec78luqh5b", + "informational": false, + "link": "types-and-traits.html#abstract-data-types", + "number": "4.5", + "paragraphs": [], + "title": "Abstract Data Types" + }, + { + "id": "fls_szibmtfv117b", + "informational": false, + "link": "types-and-traits.html#enum-types", + "number": "4.5.1", + "paragraphs": [ + { + "checksum": "41a04bcc279a52fe4a0d644f08c7974945044713d151a621e622736caa79f834", + "id": "fls_gbdd37seqoab", + "link": "types-and-traits.html#fls_gbdd37seqoab", + "number": "4.5.1:1" }, { - "checksum": "3f9a02739cb12bc30e822ddfcc0c32cfa2a7ba100fca18cf4393b8a82f73ba75", - "id": "fls_ytn5cdonytyn", - "link": "types-and-traits.html#fls_ytn5cdonytyn", - "number": "4.13:7" + "checksum": "6ec5fbcdfa7bc0e2e0fa597521d52d44f4cdb850f944944654faecd563781897", + "id": "fls_il9a1olqmu38", + "link": "types-and-traits.html#fls_il9a1olqmu38", + "number": "4.5.1:2" }, { - "checksum": "850630ec9e0b341a370fae1c5d0089a6199d8a2d37d472dcd8ff712543d65358", - "id": "fls_vucd1u38sq7i", - "link": "types-and-traits.html#fls_vucd1u38sq7i", - "number": "4.13:8" + "checksum": "b933c4ceea18830211cb015864f5ffc6aa7466256320ccd3eaefe96a2ad46495", + "id": "fls_wQTFwl88VujQ", + "link": "types-and-traits.html#fls_wQTFwl88VujQ", + "number": "4.5.1:3" }, { - "checksum": "61a7ac2c1a5f76feec6326da351d7821d5d885c3ddf66d460a3cf531b46961da", - "id": "fls_kyr81mi01me2", - "link": "types-and-traits.html#fls_kyr81mi01me2", - "number": "4.13:9" + "checksum": "8fadd9f05034847f538c7ea8c1f61165df9bb4c87e711d4b4a4cfc7ea2d2aa76", + "id": "fls_g5qle7xzaoif", + "link": "types-and-traits.html#fls_g5qle7xzaoif", + "number": "4.5.1:4" }, { - "checksum": "e23a38fedc7034747afe6208416e064bfa435ac281aad0629ac7f4ea812f2435", - "id": "fls_YynbrIceKmsJ", - "link": "types-and-traits.html#fls_YynbrIceKmsJ", - "number": "4.13:10" + "checksum": "9018d5bb55a9ad10867addcd1bc986fa5e8cd4c111f3cb348c42f11f7343d886", + "id": "fls_t4yeovFm83Wo", + "link": "types-and-traits.html#fls_t4yeovFm83Wo", + "number": "4.5.1:5" }, { - "checksum": "a35062dc24915937215d9047a1f414ade029557a2c46169a0c9a79cc96db0d02", - "id": "fls_Bd4HwdrRuXMm", - "link": "types-and-traits.html#fls_Bd4HwdrRuXMm", - "number": "4.13:11" + "checksum": "dbf85d1dbaa9e65e9ee411a69098c8ff2c3a6b0a9372e68ed8ea0f1f7e0e87bc", + "id": "fls_hp5frc752dam", + "link": "types-and-traits.html#fls_hp5frc752dam", + "number": "4.5.1:6" }, { - "checksum": "df1a3afdefc49c32fcd12cc2c3c586b0e8bf3ba1b02491b649c096e25e4a3c1d", - "id": "fls_UzfG5ic8PUIH", - "link": "types-and-traits.html#fls_UzfG5ic8PUIH", - "number": "4.13:12" + "checksum": "d3209c56e335f945e3a200cfdae47cd6e4386e6a13052c36cd96cb27f665ee36", + "id": "fls_pijczoq4k9ij", + "link": "types-and-traits.html#fls_pijczoq4k9ij", + "number": "4.5.1:7" }, { - "checksum": "a585409a841ee7361e6fb14891ca557214c6bde16ccd359302549b407e7a771c", - "id": "fls_02D6ku4Sd6yL", - "link": "types-and-traits.html#fls_02D6ku4Sd6yL", - "number": "4.13:13" + "checksum": "2a4978216847213cc9cc89c486697babf78f3a02b2df4a408a19dc42fcd37c62", + "id": "fls_x7nh42on06bg", + "link": "types-and-traits.html#fls_x7nh42on06bg", + "number": "4.5.1:8" }, { - "checksum": "b612eb84031e77cb9d34f88c555e0396ba5722ba4729a356e68d8b3fb33b3058", - "id": "fls_RLFIzQeAPhG6", - "link": "types-and-traits.html#fls_RLFIzQeAPhG6", - "number": "4.13:14" + "checksum": "f7fc7e37e48e1d9beec7400be5000d3f97bbaf12e49da3f3db7b8478895128d1", + "id": "fls_duqbzvpuehvv", + "link": "types-and-traits.html#fls_duqbzvpuehvv", + "number": "4.5.1:9" }, { - "checksum": "8b15c2b4a5b8923f6ec42c9dab38eed453f1bbfdbea8094465a5124ff0ec2e5f", - "id": "fls_avSxO0LEka2x", - "link": "types-and-traits.html#fls_avSxO0LEka2x", - "number": "4.13:15" + "checksum": "9276d9aa4b9d2a7a1fb0208d0f4b3b20336b00b99e8f85483048116ed6672925", + "id": "fls_ly183pj4fkgh", + "link": "types-and-traits.html#fls_ly183pj4fkgh", + "number": "4.5.1:10" }, { - "checksum": "b23001be74c2e310999ac798d2f614d308ee8e1e9b8b7c463397a615dcb3e025", - "id": "fls_ft8axGGOe3aL", - "link": "types-and-traits.html#fls_ft8axGGOe3aL", - "number": "4.13:16" + "checksum": "5d6b1172331de835223d74f329d2a086a76f5d59d89900a06cb5022f9e99e1c2", + "id": "fls_w7sggezgq9o4", + "link": "types-and-traits.html#fls_w7sggezgq9o4", + "number": "4.5.1:11" }, { - "checksum": "2d9fed8708df09f431fc7d1136ad692f13e72f458245e3a972715016cf03cfb6", - "id": "fls_M4EoT1NMyxJS", - "link": "types-and-traits.html#fls_M4EoT1NMyxJS", - "number": "4.13:17" + "checksum": "864ba312cb813aaa2c285d358764732ca53afa7b59f44b4a26e959f8ed5d1906", + "id": "fls_93l5o6qar5p2", + "link": "types-and-traits.html#fls_93l5o6qar5p2", + "number": "4.5.1:12" }, { - "checksum": "ee3443dc44a15bfc89d333261e8beae2340b01881208cafec32e48b372342900", - "id": "fls_Tir2kpKNP1KC", - "link": "types-and-traits.html#fls_Tir2kpKNP1KC", - "number": "4.13:18" + "checksum": "c34f5ea6b85e16a716ab014d804766dbd0f681269e22f5fb113072b3aac7b40a", + "id": "fls_t36rk3wikq28", + "link": "types-and-traits.html#fls_t36rk3wikq28", + "number": "4.5.1:13" }, { - "checksum": "9c94dc64607d1d1d3d47085d136d875d1b22bd42a1da63f644567c9afb1941da", - "id": "fls_WxHiKr8BGGvz", - "link": "types-and-traits.html#fls_WxHiKr8BGGvz", - "number": "4.13:19" + "checksum": "788a386db9bca5ece0658ad379be6e9720a7502dacc604b6b7199c62d0600a86", + "id": "fls_8ajw5trd23wi", + "link": "types-and-traits.html#fls_8ajw5trd23wi", + "number": "4.5.1:14" }, { - "checksum": "cf5ca60e94e50f04ef2036f964845c07eebcc1218b99dc55e644c63fc7e21699", - "id": "fls_mjg7yrq66hh0", - "link": "types-and-traits.html#fls_mjg7yrq66hh0", - "number": "4.13:20" + "checksum": "0e045bf46b8ce2319bc906617e1284401c1431e818f333adc0a52da4a692a36f", + "id": "fls_w9xj26ej869w", + "link": "types-and-traits.html#fls_w9xj26ej869w", + "number": "4.5.1:15" }, { - "checksum": "5791023029771fef296097ed718e040eeb95b76d6aee5549bf5621b22838e5e0", - "id": "fls_ydowwijzirmm", - "link": "types-and-traits.html#fls_ydowwijzirmm", - "number": "4.13:21" + "checksum": "b34358c9f5a38221a3128afbba7bcac38af1eb7ef2bfea390d1ddf04c523b76e", + "id": "fls_wqbuof7kxsrg", + "link": "types-and-traits.html#fls_wqbuof7kxsrg", + "number": "4.5.1:16" + }, + { + "checksum": "36a62c297e5ac360801a96f184401b9074a30dbdd36cc171b0c48cbb24144c7d", + "id": "fls_f046du2fkgr6", + "link": "types-and-traits.html#fls_f046du2fkgr6", + "number": "4.5.1:17" } ], - "title": "Traits" + "title": "Enum Types" }, { - "id": "fls_4ikc07mfrez5", + "id": "fls_9ucqbbd0s2yo", "informational": false, - "link": "types-and-traits.html#object-safety", - "number": "4.13.1", + "link": "types-and-traits.html#struct-types", + "number": "4.5.2", "paragraphs": [ { - "checksum": "a7dbfe3171723b462343aa8888eb93779e47e2cb6564c4d0b5409518c3d3d3f9", - "id": "fls_lrdki56hpc3k", - "link": "types-and-traits.html#fls_lrdki56hpc3k", - "number": "4.13.1:1" - }, - { - "checksum": "663e533be7fe2c58643b5cb29f1fafabecaebe9a67814b0c45b7339f9d6bce98", - "id": "fls_5wlltclogfkw", - "link": "types-and-traits.html#fls_5wlltclogfkw", - "number": "4.13.1:2" - }, - { - "checksum": "4f932d4a55167c3225e7142ff450c023aa8ab9c2ffd3b2a47def283f52f5f94e", - "id": "fls_droy0w5gtqaw", - "link": "types-and-traits.html#fls_droy0w5gtqaw", - "number": "4.13.1:3" - }, - { - "checksum": "7f14d5ddfe3e2040c798b3f2d8f25e11b0f81f91269a2671aba43d70a8af5063", - "id": "fls_46gd1q80c6bn", - "link": "types-and-traits.html#fls_46gd1q80c6bn", - "number": "4.13.1:4" - }, - { - "checksum": "9d15230a9c2fcfdf8a4f47962a5d4e7dcb5bb898443fee71fc2974ce3fa2df1b", - "id": "fls_kwo4cknx0yat", - "link": "types-and-traits.html#fls_kwo4cknx0yat", - "number": "4.13.1:5" - }, - { - "checksum": "8650f7fa66fca7c39de1d48ec9b57fd7ee9a3c6fbfc7e0a085c6195fe404817e", - "id": "fls_vmLLL82EQasI", - "link": "types-and-traits.html#fls_vmLLL82EQasI", - "number": "4.13.1:6" - }, - { - "checksum": "5711109d6669d3b8bf4917e6183c03969ae5401cf0c18e9b48a3ed6232f13fd1", - "id": "fls_uixekv82g2e5", - "link": "types-and-traits.html#fls_uixekv82g2e5", - "number": "4.13.1:7" - }, - { - "checksum": "e083235f159e108c388da479f4936b36dd2295363c8b91b7e278346774955c83", - "id": "fls_72tvfoemwpyy", - "link": "types-and-traits.html#fls_72tvfoemwpyy", - "number": "4.13.1:8" - }, - { - "checksum": "2617716de591dde4480a295b243e0b0c274432a579cc5786fce8def72047c033", - "id": "fls_j7nb34o87l1z", - "link": "types-and-traits.html#fls_j7nb34o87l1z", - "number": "4.13.1:9" + "checksum": "a39b4fadca9c6209a8eb00ee7b0ba2b4d03af79dbb8378379345c8836b5bc7a5", + "id": "fls_g1azfj548136", + "link": "types-and-traits.html#fls_g1azfj548136", + "number": "4.5.2:1" }, { - "checksum": "45afe066d5ef3c3b31decbde94b80e2c45c5c03be74022b5c37133f162b2544b", - "id": "fls_k1vc9vd8at92", - "link": "types-and-traits.html#fls_k1vc9vd8at92", - "number": "4.13.1:10" + "checksum": "99627103e41c406457ad5ca125407339483d91f6d8d598e7da9524f857d3c7c1", + "id": "fls_r885av95eivp", + "link": "types-and-traits.html#fls_r885av95eivp", + "number": "4.5.2:2" }, { - "checksum": "d1ce3e872cb3cdd9970e74966c197ed43066d9c21e6b962361276044a54ee9fa", - "id": "fls_kqylg31sm5wv", - "link": "types-and-traits.html#fls_kqylg31sm5wv", - "number": "4.13.1:11" + "checksum": "ddd7e64188789d0613207e30279466396e9387d4c837a9a700715c38aae1c3cb", + "id": "fls_auurdv1zvzb", + "link": "types-and-traits.html#fls_auurdv1zvzb", + "number": "4.5.2:3" }, { - "checksum": "b43aecbed94e34ebbf059fd7dd52fc451df0deec59c26167f70142669b8ca293", - "id": "fls_aer3gaur7avp", - "link": "types-and-traits.html#fls_aer3gaur7avp", - "number": "4.13.1:12" + "checksum": "943e0f1a6572313a255e14bc95a0134edbbb021f031683bfb43b344ea0342979", + "id": "fls_vce7w0904du5", + "link": "types-and-traits.html#fls_vce7w0904du5", + "number": "4.5.2:4" } ], - "title": "Object Safety" + "title": "Struct Types" }, { - "id": "fls_jeoas4n6su4", + "id": "fls_fmdn7n7s413d", "informational": false, - "link": "types-and-traits.html#trait-and-lifetime-bounds", - "number": "4.14", + "link": "types-and-traits.html#union-types", + "number": "4.5.3", "paragraphs": [ { - "checksum": "5d702214fefa6f0a53043997a796eb9c5880872b5e01081e4b326f34b335437b", - "id": "fls_5g508z6c7q5f", - "link": "types-and-traits.html#fls_5g508z6c7q5f", - "number": "4.14:1" + "checksum": "5367bfc0baaa05cd86579ae2623abf07a4ae259cb95582431afbf551136ca939", + "id": "fls_nskmnzq95yqm", + "link": "types-and-traits.html#fls_nskmnzq95yqm", + "number": "4.5.3:1" }, { - "checksum": "9d3437b59c9ab4d5061e5c413b3f9748921ef9527b71e24efda1f586edca9e1d", - "id": "fls_BqLPVaSyyXRG", - "link": "types-and-traits.html#fls_BqLPVaSyyXRG", - "number": "4.14:2" + "checksum": "2c5fb1623a74093762302c5a947e315ef8436798d09769a3a6d1f4eded4244dc", + "id": "fls_I5fN5Fmo5CyK", + "link": "types-and-traits.html#fls_I5fN5Fmo5CyK", + "number": "4.5.3:2" }, { - "checksum": "0f8fed9095b2593b2ce110d5083b1d4174edfd4d58da66a283c60dabbfa46666", - "id": "fls_grby8tmmd8sb", - "link": "types-and-traits.html#fls_grby8tmmd8sb", - "number": "4.14:3" + "checksum": "92febc08b74e580a8eccc9d1233d135ee4e59041e9ad926ad857fd36d22ce44e", + "id": "fls_1caus8ybmfli", + "link": "types-and-traits.html#fls_1caus8ybmfli", + "number": "4.5.3:3" + }, + { + "checksum": "ab71eea2a3c33b3222811c295c4dc83aba111daba6b56f8fe94c3669bbf808b7", + "id": "fls_ZJG2Q6lJYXhY", + "link": "types-and-traits.html#fls_ZJG2Q6lJYXhY", + "number": "4.5.3:4" + }, + { + "checksum": "0ce4ccb5dd76241dbc1faba186867478c49bd6a850f7c9a71d95a3a7a4e406ad", + "id": "fls_hLTnHnZuaHve", + "link": "types-and-traits.html#fls_hLTnHnZuaHve", + "number": "4.5.3:5" }, { - "checksum": "f4c5aa350fe7996e2be77424186b6514c7bdc0bed572827e1783a11b5551e07a", - "id": "fls_knut10hoz6wc", - "link": "types-and-traits.html#fls_knut10hoz6wc", - "number": "4.14:4" + "checksum": "8837c04fd5dbb01d08026d2cafaaafd8ab2287f60e984d4fb1792425c0089812", + "id": "fls_JWgSckDtN13c", + "link": "types-and-traits.html#fls_JWgSckDtN13c", + "number": "4.5.3:6" }, { - "checksum": "4faa1836d2f6f8eecc49eaa8a8b5a46d717231f2dd398d4590d48ee4e14c5986", - "id": "fls_sf6zg0ez9hbb", - "link": "types-and-traits.html#fls_sf6zg0ez9hbb", - "number": "4.14:5" + "checksum": "5959734aa5fddd303916db4c88648c4a0f7e216586b20e2ccc01f56a202afa58", + "id": "fls_sXZknxozJxtC", + "link": "types-and-traits.html#fls_sXZknxozJxtC", + "number": "4.5.3:7" }, { - "checksum": "838585a97e19cda475b2d3a30b265a5e45814e4e6fed4a7f4273c944223c6e97", - "id": "fls_vujl3fblz6x2", - "link": "types-and-traits.html#fls_vujl3fblz6x2", - "number": "4.14:6" + "checksum": "f66f5248019636eefd0a3849d4710a419847eb4da4ffab3af715a08c5c7f4c02", + "id": "fls_vgNK01SXacnx", + "link": "types-and-traits.html#fls_vgNK01SXacnx", + "number": "4.5.3:8" }, { - "checksum": "c2729c9c6fa8817169c4c6b3ee4585e3518a521decde9f3a96bfa7d4313c831a", - "id": "fls_AzuZmR9DXSQh", - "link": "types-and-traits.html#fls_AzuZmR9DXSQh", - "number": "4.14:7" + "checksum": "686f435b04adf578310fab3434b24706f77b75b4b7c9a189b81799cd033a4244", + "id": "fls_bQhh3zHAKjSu", + "link": "types-and-traits.html#fls_bQhh3zHAKjSu", + "number": "4.5.3:9" + } + ], + "title": "Union Types" + }, + { + "id": "fls_hbbek3z4wtcs", + "informational": false, + "link": "types-and-traits.html#function-types", + "number": "4.6", + "paragraphs": [], + "title": "Function Types" + }, + { + "id": "fls_xd2oxlebhs14", + "informational": false, + "link": "types-and-traits.html#closure-types", + "number": "4.6.1", + "paragraphs": [ + { + "checksum": "a13570cc647e2fe3f7daa098e9b82168dd6556c25ddc1bfb7dc12f25e9eae54e", + "id": "fls_bsykgnbatpmi", + "link": "types-and-traits.html#fls_bsykgnbatpmi", + "number": "4.6.1:1" }, { - "checksum": "05bc50eddffd3a18ed8f3377b123bb37c1b5e94af198ccfd7de28fe31d741de9", - "id": "fls_1Sm2Yq1Ow76f", - "link": "types-and-traits.html#fls_1Sm2Yq1Ow76f", - "number": "4.14:8" + "checksum": "94c7fbe22c296aee3cf9ce06a1da0aac2dca927a95d394cc715b6fe61265f90e", + "id": "fls_zfj4l8bigdg0", + "link": "types-and-traits.html#fls_zfj4l8bigdg0", + "number": "4.6.1:2" }, { - "checksum": "2329dd8b3848ee98473bcfcb998f59926c182d917d53d0b5c562e486ce7905d9", - "id": "fls_tx4uspewnk7w", - "link": "types-and-traits.html#fls_tx4uspewnk7w", - "number": "4.14:9" + "checksum": "6509cd4ac82ace29c2f9a29046a8fc164311d5de6ffc2956bb878dbbe701c889", + "id": "fls_bn0ueivujnqk", + "link": "types-and-traits.html#fls_bn0ueivujnqk", + "number": "4.6.1:3" }, { - "checksum": "88ab9938b63b54586e5a194cda9b4fd9238fdc4204039efa73640037b90e8336", - "id": "fls_5kj8bmvb8xfc", - "link": "types-and-traits.html#fls_5kj8bmvb8xfc", - "number": "4.14:10" + "checksum": "494dee99ed22dbebd0d0ac76a1fa787b15f999f68855fefcdb9cd603d22489da", + "id": "fls_u01kt5glbuz8", + "link": "types-and-traits.html#fls_u01kt5glbuz8", + "number": "4.6.1:4" }, { - "checksum": "ea8cafd5ce15c0c1f15675f652353abf992d1d606ef8a4a1543796515a9f5575", - "id": "fls_J9DEsd06Ttu9", - "link": "types-and-traits.html#fls_J9DEsd06Ttu9", - "number": "4.14:11" + "checksum": "2a45235137530e81bb8f2281cf9deca9542bed2f95dd65858edb54f18e243164", + "id": "fls_3jeootwe6ucu", + "link": "types-and-traits.html#fls_3jeootwe6ucu", + "number": "4.6.1:5" }, { - "checksum": "f2c5ee9dc74cb81bb9b1010d4971b395c10d7ae0b69878d350cb1456826c8e37", - "id": "fls_IfHRxSasGAih", - "link": "types-and-traits.html#fls_IfHRxSasGAih", - "number": "4.14:12" + "checksum": "3bd0fbff2aa919bc05fdb9a7a301baf9be62bfb0c6ebf65df45c88713582d37f", + "id": "fls_63jqtyw0rz8c", + "link": "types-and-traits.html#fls_63jqtyw0rz8c", + "number": "4.6.1:6" }, { - "checksum": "4b84d24f6f4b7c87cfc09aa1f59294dadaa53c24876e0fd68f67fc28b88830ad", - "id": "fls_K8nPGP5xbLb7", - "link": "types-and-traits.html#fls_K8nPGP5xbLb7", - "number": "4.14:13" + "checksum": "b0e41c86f561de826ef136c25682433aef7f5636c0352e3d8756137e1446ab27", + "id": "fls_3c4g9njja5s5", + "link": "types-and-traits.html#fls_3c4g9njja5s5", + "number": "4.6.1:7" }, { - "checksum": "504b10589747b39626c8009a403ca0deba66fe396ad2de9be37b123b48dc0c99", - "id": "fls_Uw6y5ZlaK6RY", - "link": "types-and-traits.html#fls_Uw6y5ZlaK6RY", - "number": "4.14:14" + "checksum": "db27fe1b97d6577e59cc0f00cdd1fab1ddfa6645da18b3a83e52010a01ff8caa", + "id": "fls_2nuhy0ujgq18", + "link": "types-and-traits.html#fls_2nuhy0ujgq18", + "number": "4.6.1:8" }, { - "checksum": "b42f5355d699fc50f184c1c645f88e77a6a11c2137fc64204d96f8c69dbcf6d1", - "id": "fls_OnyR0Wsfk7KI", - "link": "types-and-traits.html#fls_OnyR0Wsfk7KI", - "number": "4.14:15" + "checksum": "929a4c4a21afe2d35b50abb7d7250277785667e33d2692b7c52fa2b6eecd4608", + "id": "fls_5jh07heok8sy", + "link": "types-and-traits.html#fls_5jh07heok8sy", + "number": "4.6.1:9" } ], - "title": "Trait and Lifetime Bounds" + "title": "Closure Types" }, { - "id": "fls_yqcygq3y6m5j", + "id": "fls_airvr79xkcag", "informational": false, - "link": "types-and-traits.html#lifetimes", - "number": "4.14.1", + "link": "types-and-traits.html#function-item-types", + "number": "4.6.2", "paragraphs": [ { - "checksum": "89e96b4228d15b001e337309f76928a584c677698e9d8f56c3fd0bf4ef702172", - "id": "fls_nne91at3143t", - "link": "types-and-traits.html#fls_nne91at3143t", - "number": "4.14.1:1" + "checksum": "ba8be95a7ac75beeb55990710d7b232e90aaefa46fbc965fc5ee97ff250687b1", + "id": "fls_t24iojx7yc23", + "link": "types-and-traits.html#fls_t24iojx7yc23", + "number": "4.6.2:1" }, { - "checksum": "b2051972bf09f0bc6966717b054dfab460bc29d92a31477488c0f18205bac5d0", - "id": "fls_vbclxg9dq4yo", - "link": "types-and-traits.html#fls_vbclxg9dq4yo", - "number": "4.14.1:2" + "checksum": "6e4e49c145170caf512a89a36aab453064036bab3e47a5b7551d769810a454a8", + "id": "fls_sas3ahcshnrh", + "link": "types-and-traits.html#fls_sas3ahcshnrh", + "number": "4.6.2:2" }, { - "checksum": "cb7a8e0c24c9740e97f2f5acc1326c7e1f22108d641e9cc3a0ce60f0f8be917c", - "id": "fls_gcszhqg6hnva", - "link": "types-and-traits.html#fls_gcszhqg6hnva", - "number": "4.14.1:3" + "checksum": "1d99ab5849365bb230cbc4a782bb93bc919bb2f07c62b63a7f63b380e66ac908", + "id": "fls_liwnzwu1el1i", + "link": "types-and-traits.html#fls_liwnzwu1el1i", + "number": "4.6.2:3" + }, + { + "checksum": "d667ede8ab6893069841a33b2cf0e47b19b05d5fa2872b3bebec06b8852063d1", + "id": "fls_e9x4f7qxvvjv", + "link": "types-and-traits.html#fls_e9x4f7qxvvjv", + "number": "4.6.2:4" + }, + { + "checksum": "8acd4d89f0c821d4aa22815001aca5bc9e977dc1e2712743e735bcf6aed3c248", + "id": "fls_1941wid94hlg", + "link": "types-and-traits.html#fls_1941wid94hlg", + "number": "4.6.2:5" } ], - "title": "Lifetimes" + "title": "Function Item Types" }, { - "id": "fls_ikfvbeewame7", + "id": "fls_3i4ou0dq64ny", "informational": false, - "link": "types-and-traits.html#subtyping-and-variance", - "number": "4.14.2", + "link": "types-and-traits.html#indirection-types", + "number": "4.7", + "paragraphs": [], + "title": "Indirection Types" + }, + { + "id": "fls_xztr1kebz8bo", + "informational": false, + "link": "types-and-traits.html#function-pointer-types", + "number": "4.7.1", "paragraphs": [ { - "checksum": "71b5298b738eb859f8cc6f0ef4b99931284871ae39f1bdc321d865a23684d1cd", - "id": "fls_atq2cltx487m", - "link": "types-and-traits.html#fls_atq2cltx487m", - "number": "4.14.2:1" + "checksum": "276ef1476e7ceccaf33821c0dfadd47de95e12defa884eb6eb9da1bcd89311d4", + "id": "fls_v2wrytr3t04h", + "link": "types-and-traits.html#fls_v2wrytr3t04h", + "number": "4.7.1:1" }, { - "checksum": "737d3ffccd43df4591537722f4458fda52a59fc66125c7bfefb7f5640a36a8bc", - "id": "fls_df87d44kgwcv", - "link": "types-and-traits.html#fls_df87d44kgwcv", - "number": "4.14.2:2" + "checksum": "d51b172a0b8675c28c409e4d5ea5bce285ae17d936f4f4af0f5a80373b22ada7", + "id": "fls_5dd7icjcl3nt", + "link": "types-and-traits.html#fls_5dd7icjcl3nt", + "number": "4.7.1:2" }, { - "checksum": "9ad41a73cca1f1351639e50cba9344d86cdc286da8a2b7f88955ec691b73c9b7", - "id": "fls_7ex941yysuhq", - "link": "types-and-traits.html#fls_7ex941yysuhq", - "number": "4.14.2:3" + "checksum": "2d95eda82b646831bce5a240ff70e298c0a039e3ac40cb6198cb334c8e980d60", + "id": "fls_B0SMXRqQMS1E", + "link": "types-and-traits.html#fls_B0SMXRqQMS1E", + "number": "4.7.1:3" }, { - "checksum": "ddfa75fd9e0142c3c367eb0ba02a65e7221d9e86a4ddef947ebbcfa2bf8a4f60", - "id": "fls_7qud6i05ze2", - "link": "types-and-traits.html#fls_7qud6i05ze2", - "number": "4.14.2:4" + "checksum": "730f52bbaf5e2e9bd48089d7a8aa41c2e14dace0153b74d1bceb0aaf0c4d977c", + "id": "fls_hbn1l42xmr3h", + "link": "types-and-traits.html#fls_hbn1l42xmr3h", + "number": "4.7.1:4" }, { - "checksum": "23cf56704ab7dff0f1809d6e8435b4c569536305b94f1a7f05e5b58529ee4a7d", - "id": "fls_wpm0p0gtctvi", - "link": "types-and-traits.html#fls_wpm0p0gtctvi", - "number": "4.14.2:5" + "checksum": "4107a933398a84d0b8af66fd006db6a72808511b4af3e4c47b71b0dc795fc8f8", + "id": "fls_g1iYVw7upBnH", + "link": "types-and-traits.html#fls_g1iYVw7upBnH", + "number": "4.7.1:5" }, { - "checksum": "74f5a55b32bebc82b21a7e2a9db5f21b1c14e6be7e935138309af650bb1b5ee1", - "id": "fls_3rfs58i2kg6l", - "link": "types-and-traits.html#fls_3rfs58i2kg6l", - "number": "4.14.2:6" + "checksum": "5860cccedbc0ac25984e9603b83f1259c4af97cecc8084f5c140164f6865c5e1", + "id": "fls_8gpvNJfVlyaD", + "link": "types-and-traits.html#fls_8gpvNJfVlyaD", + "number": "4.7.1:6" }, { - "checksum": "1f5b98767e075851c7d070ea2843150c47227558e39872de71e84085c4a044b6", - "id": "fls_kbo3e3bosr0m", - "link": "types-and-traits.html#fls_kbo3e3bosr0m", - "number": "4.14.2:7" + "checksum": "d2541a3280f1d894821db0f1648bbadb29e2c211db75fba997f3d2832ee527c4", + "id": "fls_KcI6yK0P8Onn", + "link": "types-and-traits.html#fls_KcI6yK0P8Onn", + "number": "4.7.1:7" + }, + { + "checksum": "3a43cc40eddf138b65ff58d01174bf0f377be62e4998881c0e34b316af1831fb", + "id": "fls_52thmi9hnoks", + "link": "types-and-traits.html#fls_52thmi9hnoks", + "number": "4.7.1:8" + } + ], + "title": "Function Pointer Types" + }, + { + "id": "fls_ppd1xwve3tr7", + "informational": false, + "link": "types-and-traits.html#raw-pointer-types", + "number": "4.7.2", + "paragraphs": [ + { + "checksum": "1854f3b6844b0c2671dfc60d36c4b998358f29c120b234bec672c72aaed507cc", + "id": "fls_rpbhr0xukbx9", + "link": "types-and-traits.html#fls_rpbhr0xukbx9", + "number": "4.7.2:1" }, { - "checksum": "920a8bda29bfda122db8cdb94e70a6c5a4fd76dc0dcc4e6a47f71c256eed960a", - "id": "fls_n36p6w2a75sm", - "link": "types-and-traits.html#fls_n36p6w2a75sm", - "number": "4.14.2:8" + "checksum": "f69e8334863b9e2b7f3bf48725d48a8adf59c48c3dd802341bfd611dcc03d690", + "id": "fls_bYWfGDAQcWfA", + "link": "types-and-traits.html#fls_bYWfGDAQcWfA", + "number": "4.7.2:2" }, { - "checksum": "6b1036112f36ab96e0fba76056076e5a4b5cebf3a68ee27c25399aec62dc6107", - "id": "fls_xw7eo3us0ow4", - "link": "types-and-traits.html#fls_xw7eo3us0ow4", - "number": "4.14.2:9" + "checksum": "82bd1634fa77a9d6d3b725fb993892cad15fbe87ece6afe59b2f171f2a7721bf", + "id": "fls_8uWfFAsZeRCs", + "link": "types-and-traits.html#fls_8uWfFAsZeRCs", + "number": "4.7.2:3" }, { - "checksum": "cf9cf2053b0da4ee4b6530a47cadc73a560b803d10402cde9767cf463797e626", - "id": "fls_qc6jma5g9vpn", - "link": "types-and-traits.html#fls_qc6jma5g9vpn", - "number": "4.14.2:10" + "checksum": "6bfba3dbe307a3b99ae68a7d7ad6ee97648893f1aaa0a786262bff92bea33aa5", + "id": "fls_hrum767l6dte", + "link": "types-and-traits.html#fls_hrum767l6dte", + "number": "4.7.2:4" }, { - "checksum": "c31d4d2d2e461e81ba0e0d047ad24e21fe792abb8e791776c1087a6004460b67", - "id": "fls_hpiiwxzg16rj", - "link": "types-and-traits.html#fls_hpiiwxzg16rj", - "number": "4.14.2:11" - }, + "checksum": "9a80943af437a0fd5e67c5bd656fadb3ce16e6d7b59218c8685ae1e0a2c4d9a4", + "id": "fls_k6ues2936pjq", + "link": "types-and-traits.html#fls_k6ues2936pjq", + "number": "4.7.2:5" + } + ], + "title": "Raw Pointer Types" + }, + { + "id": "fls_142vncdktbin", + "informational": false, + "link": "types-and-traits.html#reference-types", + "number": "4.7.3", + "paragraphs": [ { - "checksum": "8b26cdf579eba2baed79aeec149c7a4d4277036b4004f66485b0ecade29cd5ad", - "id": "fls_aspdlqluwh9w", - "link": "types-and-traits.html#fls_aspdlqluwh9w", - "number": "4.14.2:12" + "checksum": "e9a8f32c3bfd274934e7c471016afe023d42728464a9c6ad6934f0738cf79a22", + "id": "fls_twhq24s8kchh", + "link": "types-and-traits.html#fls_twhq24s8kchh", + "number": "4.7.3:1" }, { - "checksum": "cfe83442e8b9e2c97e23204f4e49cab1e49d103de5746c223fe4e5a2f4d47494", - "id": "fls_8ohuze7hqtc1", - "link": "types-and-traits.html#fls_8ohuze7hqtc1", - "number": "4.14.2:13" + "checksum": "15503795396e6eb08752e159af664fcaf59c4c7eb0d754480fbef0251d74b28d", + "id": "fls_w4NbA7WhZfR2", + "link": "types-and-traits.html#fls_w4NbA7WhZfR2", + "number": "4.7.3:2" }, { - "checksum": "5ef125fa78302d1bd0ab1c9c6ce402e84343ce65094c6dbcaa49f0bd9aad81f8", - "id": "fls_7pkqgxabojkn", - "link": "types-and-traits.html#fls_7pkqgxabojkn", - "number": "4.14.2:14" + "checksum": "989727897539b6eef42387c5f240b13d4ab9aa9f269a111e510879ada1ae0960", + "id": "fls_ie0avzljmxfm", + "link": "types-and-traits.html#fls_ie0avzljmxfm", + "number": "4.7.3:3" }, { - "checksum": "d8c93934a67920a9c041b06a6f58c10bca90f62627301c61c512c531cf54b8cb", - "id": "fls_ln9pqd4xu5e", - "link": "types-and-traits.html#fls_ln9pqd4xu5e", - "number": "4.14.2:15" + "checksum": "fef244abbffea76d85673c8b768c80e3998096122c5887ffacd05a346939ff67", + "id": "fls_15zdiqsm1q3p", + "link": "types-and-traits.html#fls_15zdiqsm1q3p", + "number": "4.7.3:4" }, { - "checksum": "9cb44289a5c32876d21e093980654ac08ef05f7b857ee5acd5f952af4fe5882d", - "id": "fls_z4jo3ojkcu9v", - "link": "types-and-traits.html#fls_z4jo3ojkcu9v", - "number": "4.14.2:16" + "checksum": "823004ffa0528651765ee5a2a23b26f7a33b4255a564f742c3923781a6b70102", + "id": "fls_csdjfwczlzfd", + "link": "types-and-traits.html#fls_csdjfwczlzfd", + "number": "4.7.3:5" }, { - "checksum": "cf5c8e900ca37acad0bc171bfa0c6b714f1d23d6b8cb493c3038690fdb8ae839", - "id": "fls_67w6yslr3e25", - "link": "types-and-traits.html#fls_67w6yslr3e25", - "number": "4.14.2:17" + "checksum": "19c8db7f4aaa3f7224c336d60e3c57d17ef21d93e5ececa9fbb1d09175936b0f", + "id": "fls_GUZuiST7ucib", + "link": "types-and-traits.html#fls_GUZuiST7ucib", + "number": "4.7.3:6" }, { - "checksum": "288d8383e4a918a8df537e25a4154df2c81df5a683c07e73b4544c524b308ed0", - "id": "fls_ojal3qytkqql", - "link": "types-and-traits.html#fls_ojal3qytkqql", - "number": "4.14.2:18" + "checksum": "2784f42b85409ac61fe875e01d21162c086f0411280475dac703bf1a2ae98fc8", + "id": "fls_vaas9kns4zo6", + "link": "types-and-traits.html#fls_vaas9kns4zo6", + "number": "4.7.3:7" }, { - "checksum": "cfc97b21efeb749f2447127d8a51da98dc6e41cb501744689a98e040ea67ea73", - "id": "fls_owp42z12l4lc", - "link": "types-and-traits.html#fls_owp42z12l4lc", - "number": "4.14.2:19" + "checksum": "585dd4699fdeac3e280d38797a9c9f9565c4194543b3c1c048a0ef33913a5e66", + "id": "fls_n6ffcms5pr0r", + "link": "types-and-traits.html#fls_n6ffcms5pr0r", + "number": "4.7.3:8" }, { - "checksum": "a734eb2b309a56c538d9dd9dfb1c4826d69b2288840fcb2d3cd674d2e60ef9c2", - "id": "fls_i1vuix3gj9ej", - "link": "types-and-traits.html#fls_i1vuix3gj9ej", - "number": "4.14.2:20" - }, + "checksum": "fef35c5ee01a70578d0ee80139418ab1d555636b9d7a83d7cf5bcf38dc454153", + "id": "fls_ezh8aq6fmdvz", + "link": "types-and-traits.html#fls_ezh8aq6fmdvz", + "number": "4.7.3:9" + } + ], + "title": "Reference Types" + }, + { + "id": "fls_1ompd93w7c9f", + "informational": false, + "link": "types-and-traits.html#trait-types", + "number": "4.8", + "paragraphs": [], + "title": "Trait Types" + }, + { + "id": "fls_3xqobbu7wfsf", + "informational": false, + "link": "types-and-traits.html#impl-trait-types", + "number": "4.8.1", + "paragraphs": [ { - "checksum": "cad8abc0d81bf2f72b8ee690d6d77d6170b0848acffa5edbfe5137073f871803", - "id": "fls_mlf39pl0b931", - "link": "types-and-traits.html#fls_mlf39pl0b931", - "number": "4.14.2:21" + "checksum": "9013893b3cd5acfa5451f4ad18860a989a9ad70cd891c50e846503ea7f274dcf", + "id": "fls_a6zlvyxpgsew", + "link": "types-and-traits.html#fls_a6zlvyxpgsew", + "number": "4.8.1:1" }, { - "checksum": "2215e0ca6e0b0930e0b97655a732b0131268013502b68c2a789b71977bb44466", - "id": "fls_yknymnlsasyw", - "link": "types-and-traits.html#fls_yknymnlsasyw", - "number": "4.14.2:22" + "checksum": "16cb713fd246c7e18f528a4ffd5338a0fbce00bff8b4aa68d64f137a4d38b4c1", + "id": "fls_ieyqx5vzas2m", + "link": "types-and-traits.html#fls_ieyqx5vzas2m", + "number": "4.8.1:2" }, { - "checksum": "9b7e9c4d5c82583cf8a4c06355a662c0499656e0c927ae7d7b2567c8177d654a", - "id": "fls_xkzo7nj40rbn", - "link": "types-and-traits.html#fls_xkzo7nj40rbn", - "number": "4.14.2:23" + "checksum": "2fd602bae21ca8be8d6cf24e5f3559c0846d9301830440115c150d3a57aac7bf", + "id": "fls_3aKZB0ILIkZw", + "link": "types-and-traits.html#fls_3aKZB0ILIkZw", + "number": "4.8.1:3" }, { - "checksum": "e4cda8eaec5f978c1a3e57c6c8af5970fcd1762a4ff06f9b1a68a8106537f318", - "id": "fls_abn5ycx11zpm", - "link": "types-and-traits.html#fls_abn5ycx11zpm", - "number": "4.14.2:24" + "checksum": "637383dc4d1660eea32118dd12d2223bac4c904a62d1b6362ee20154a1fd8a9c", + "id": "fls_Xo1ODwOyX7Vm", + "link": "types-and-traits.html#fls_Xo1ODwOyX7Vm", + "number": "4.8.1:4" }, { - "checksum": "42ce008b0a6cfe49436f4f3897238024485cda9f72a13fbfab9b3d7814f4111f", - "id": "fls_hvfyog9ygn6q", - "link": "types-and-traits.html#fls_hvfyog9ygn6q", - "number": "4.14.2:25" + "checksum": "05a4175995d776a46a98e4d297c0a67cfb2ed6dcef4796010a1d0cc829263bce", + "id": "fls_kTGFLFymTWch", + "link": "types-and-traits.html#fls_kTGFLFymTWch", + "number": "4.8.1:5" }, { - "checksum": "90d2a2db73cec5e5c1430778f7c4c4e671046095294db8fca4b4f056f598ef18", - "id": "fls_mduolmcawb30", - "link": "types-and-traits.html#fls_mduolmcawb30", - "number": "4.14.2:26" + "checksum": "0dc7132c60e97eae3fe8c4763fb5b0ee4e27b69d33014e5a64c902c25e992bba", + "id": "fls_ECjhEI7eCwAj", + "link": "types-and-traits.html#fls_ECjhEI7eCwAj", + "number": "4.8.1:6" }, { - "checksum": "f5d848b029abc9525e976478047895ce24e4ac85a7af51e1f425f22ab5340b9b", - "id": "fls_y81gmqweqc9w", - "link": "types-and-traits.html#fls_y81gmqweqc9w", - "number": "4.14.2:27" + "checksum": "13a60d90ebb030b15b25dc8cb8e67dacf1551162e23af35c46d25856b08a7364", + "id": "fls_69hqMjvNno9u", + "link": "types-and-traits.html#fls_69hqMjvNno9u", + "number": "4.8.1:7" }, { - "checksum": "a7da2fd26ad1dfa3533285dad37d02fed2b4908be213a6a3a98775b0c2ebdb51", - "id": "fls_etgfvgvymn8o", - "link": "types-and-traits.html#fls_etgfvgvymn8o", - "number": "4.14.2:28" + "checksum": "ddef3b4e840fd2a4e762e483b2babf5dcdc86872d5c7e646d65db47730395ea5", + "id": "fls_KgH6c5cC4S0G", + "link": "types-and-traits.html#fls_KgH6c5cC4S0G", + "number": "4.8.1:8" }, { - "checksum": "92ec09eb8c13eb5501dd2aa83335c6b18b661577435da0da11d655d174667b7d", - "id": "fls_4kjxxrsk1igf", - "link": "types-and-traits.html#fls_4kjxxrsk1igf", - "number": "4.14.2:29" + "checksum": "33a093516df193a7fdc2815dbd081c61f7afc3126d1c8442b5009786e41ad74f", + "id": "fls_iT9WCNfUZQnC", + "link": "types-and-traits.html#fls_iT9WCNfUZQnC", + "number": "4.8.1:9" + } + ], + "title": "Impl Trait Types" + }, + { + "id": "fls_qa98qdi42orq", + "informational": false, + "link": "types-and-traits.html#trait-object-types", + "number": "4.8.2", + "paragraphs": [ + { + "checksum": "7e198679726fb24effe560d32f6ed44994be67d60bad4c175f3da2c84b40b0d9", + "id": "fls_sgrvona1nb6h", + "link": "types-and-traits.html#fls_sgrvona1nb6h", + "number": "4.8.2:1" }, { - "checksum": "d15a6c43afbdab395dde7bf007ffaa85c419254890a3a269617d7567fb692360", - "id": "fls_y4zmb3vrym7p", - "link": "types-and-traits.html#fls_y4zmb3vrym7p", - "number": "4.14.2:30" + "checksum": "35adf29ee8c478f92e6058e972a281cd75546d4f895d5388d058c14a47be175e", + "id": "fls_eWac7zOda3lh", + "link": "types-and-traits.html#fls_eWac7zOda3lh", + "number": "4.8.2:2" }, { - "checksum": "5375ee031b5992cc2d81fb87c92113aa2df9714381a2516c70a907104f22544f", - "id": "fls_9ae3idezsths", - "link": "types-and-traits.html#fls_9ae3idezsths", - "number": "4.14.2:31" + "checksum": "b537bc4965a9b1c056d0dd2da6fec6494eb39fa97f42b8f126dc42e23bad4e4c", + "id": "fls_9z8oleh0wdel", + "link": "types-and-traits.html#fls_9z8oleh0wdel", + "number": "4.8.2:3" }, { - "checksum": "7a621c5f9277c4f46a52133620c001507d1ffde9ce5bc9b977dae5382a587119", - "id": "fls_WD6uyTCziRac", - "link": "types-and-traits.html#fls_WD6uyTCziRac", - "number": "4.14.2:32" + "checksum": "ad208e723038631cfa6c6e948ce39a457dada12fd9e9dee7ca26c9420c6861f8", + "id": "fls_hJII8XYAtZeY", + "link": "types-and-traits.html#fls_hJII8XYAtZeY", + "number": "4.8.2:4" }, { - "checksum": "198b15c57f80ef7b7004d08d14db0126c5a03181c18d4c5c5d9fb54b608cd93a", - "id": "fls_xURR0owesaIE", - "link": "types-and-traits.html#fls_xURR0owesaIE", - "number": "4.14.2:33" + "checksum": "6257637cc04d7a1e7f32788a155b69595ebd5872f7e7366b15a24bc0cb01c3ec", + "id": "fls_s0oy2c8t4yz9", + "link": "types-and-traits.html#fls_s0oy2c8t4yz9", + "number": "4.8.2:5" }, { - "checksum": "e9ab99af1e3626b5bc2ec4a97eb9b693d57cbf3941d58b50da79a3a97cd65b65", - "id": "fls_CvZNYIfnOUcc", - "link": "types-and-traits.html#fls_CvZNYIfnOUcc", - "number": "4.14.2:34" + "checksum": "8afd9b150960eee9cad7ad77a00f4d78e50ae87ac73c9a1198b12f79aa8ff09e", + "id": "fls_CcoUug6b9ohU", + "link": "types-and-traits.html#fls_CcoUug6b9ohU", + "number": "4.8.2:6" }, { - "checksum": "e44c013173ea636304b228c4399fefdf0e90a39d2d469805209757504b18f9d1", - "id": "fls_xrLYQX7W9OnR", - "link": "types-and-traits.html#fls_xrLYQX7W9OnR", - "number": "4.14.2:35" - }, + "checksum": "80503a5bbbf247afc4589e84831bd9388ca01a63f6f99729d27e31db000127ad", + "id": "fls_88b9bmhra55f", + "link": "types-and-traits.html#fls_88b9bmhra55f", + "number": "4.8.2:7" + } + ], + "title": "Trait Object Types" + }, + { + "id": "fls_3pbipk8ki18d", + "informational": false, + "link": "types-and-traits.html#other-types", + "number": "4.9", + "paragraphs": [], + "title": "Other Types" + }, + { + "id": "fls_s45k21yn4qur", + "informational": false, + "link": "types-and-traits.html#inferred-types", + "number": "4.9.1", + "paragraphs": [ { - "checksum": "319beaaf1f1caf0096dba73af40779542bd6c1e51e16900e8189ad49bbf270b7", - "id": "fls_DWPN8DRysgMa", - "link": "types-and-traits.html#fls_DWPN8DRysgMa", - "number": "4.14.2:36" + "checksum": "45bfb5537d2e07e5037870981039b3817a257151920d49f5a8939e10ff7a0815", + "id": "fls_xdtgr5toulpb", + "link": "types-and-traits.html#fls_xdtgr5toulpb", + "number": "4.9.1:1" }, { - "checksum": "0a0c6af41fd048b6c722a4d47422c162b6d1d238eb34928fd9b8d573a4d1e344", - "id": "fls_tOn5oKtp300J", - "link": "types-and-traits.html#fls_tOn5oKtp300J", - "number": "4.14.2:37" + "checksum": "1d559796531e476dc3d85e19f7dc9e8b9e85dc14e75d7e22f7fd0d9cd3e0753c", + "id": "fls_3abhsuaa8nas", + "link": "types-and-traits.html#fls_3abhsuaa8nas", + "number": "4.9.1:2" }, { - "checksum": "b40dd94eaa2e2861c1ac9a47ac10ee9e80b71b3fce42aa4c362c82feaafaf088", - "id": "fls_HqXTVi7gr9wR", - "link": "types-and-traits.html#fls_HqXTVi7gr9wR", - "number": "4.14.2:38" + "checksum": "a61fcb03ca3303b870d5a7bf9412d89fa8c8a999d7275efa89ada3e204974f57", + "id": "fls_hBXlJhbhuoHY", + "link": "types-and-traits.html#fls_hBXlJhbhuoHY", + "number": "4.9.1:3" }, { - "checksum": "4b83f6d8b60d4cc3f44054e47b3add3b07386dbadb45a3e11809ac73890747d5", - "id": "fls_lxfPvK7NDNlj", - "link": "types-and-traits.html#fls_lxfPvK7NDNlj", - "number": "4.14.2:39" + "checksum": "f472521e59830a8a2807dd85f1821de685a14eda76e2e46a580818dcb6e86b8d", + "id": "fls_Vxlr9ZcqiOvY", + "link": "types-and-traits.html#fls_Vxlr9ZcqiOvY", + "number": "4.9.1:4" }, { - "checksum": "a38f8ae59566df6812eb2e4a65493f7869a7ee5d1490a9ea14cca661e002257a", - "id": "fls_awJeqcAHn5O3", - "link": "types-and-traits.html#fls_awJeqcAHn5O3", - "number": "4.14.2:40" + "checksum": "ea3bbe8655934a40bc687f54cf38d663ed640a043e0a93e57b63097ed7b378ec", + "id": "fls_gE9VC8JXrl1N", + "link": "types-and-traits.html#fls_gE9VC8JXrl1N", + "number": "4.9.1:5" }, { - "checksum": "55f986a2532a84d35f03d42bf891d3f758900a1764931afb6e6748f104d30de0", - "id": "fls_AD1IXzsnlwyg", - "link": "types-and-traits.html#fls_AD1IXzsnlwyg", - "number": "4.14.2:41" + "checksum": "1dd464889720d923a6efc632871acb0d7f1fa780fec361617485a85c8eaef334", + "id": "fls_ybyQjFamI1Q5", + "link": "types-and-traits.html#fls_ybyQjFamI1Q5", + "number": "4.9.1:6" }, { - "checksum": "ac51179a778b0f9f9797899d9ef592d947ee84fdc5a06197c3326fcf1bcebda8", - "id": "fls_p2II8E6cc9zt", - "link": "types-and-traits.html#fls_p2II8E6cc9zt", - "number": "4.14.2:42" + "checksum": "8d410f326c8e233e5773842346d9f3d9783f75fad42585cb76ed490065470f5f", + "id": "fls_9d8wbugmar1m", + "link": "types-and-traits.html#fls_9d8wbugmar1m", + "number": "4.9.1:7" + } + ], + "title": "Inferred Types" + }, + { + "id": "fls_xjcxbajhzp3d", + "informational": false, + "link": "types-and-traits.html#type-parameters", + "number": "4.9.2", + "paragraphs": [ + { + "checksum": "e42bea781e368e51e6a841e48a716968a41dbbc44fbbebc2447280a69d1d25fb", + "id": "fls_dCIIVXGhXDlO", + "link": "types-and-traits.html#fls_dCIIVXGhXDlO", + "number": "4.9.2:1" + } + ], + "title": "Type Parameters" + }, + { + "id": "fls_98lnexk53ru4", + "informational": false, + "link": "types-and-traits.html#never-type", + "number": "4.9.3", + "paragraphs": [ + { + "checksum": "c2d4459a9d3124b6eb26bd54c1d0a2741f70e371c43c323dca07a05f424933ba", + "id": "fls_4u0v5uy95pyf", + "link": "types-and-traits.html#fls_4u0v5uy95pyf", + "number": "4.9.3:1" }, { - "checksum": "d108ec536a18cd310c0309f9f0992815ce2c842ccd88899e8bc3b86859afbb92", - "id": "fls_pN5C4FHWEWdK", - "link": "types-and-traits.html#fls_pN5C4FHWEWdK", - "number": "4.14.2:43" + "checksum": "77b18aa146d69730f692da58a1c8e9a4953cad14ad3289ca106e7a071fc98a83", + "id": "fls_xmtc10qzw0ui", + "link": "types-and-traits.html#fls_xmtc10qzw0ui", + "number": "4.9.3:2" }, { - "checksum": "01dcce0fab7d9e766e601132b4336c4324d2cb983b16cbc83cd69c7fa6031d79", - "id": "fls_RdqV0hI8h8BI", - "link": "types-and-traits.html#fls_RdqV0hI8h8BI", - "number": "4.14.2:44" + "checksum": "a31ec075c01c1bf5fe6b674ca2fa9dd54e2f8402cde5540b9faa80e0cb2b6d9a", + "id": "fls_22e8quna7ed5", + "link": "types-and-traits.html#fls_22e8quna7ed5", + "number": "4.9.3:3" + } + ], + "title": "Never Type" + }, + { + "id": "fls_olbj67eyxz2k", + "informational": false, + "link": "types-and-traits.html#parenthesized-types", + "number": "4.9.4", + "paragraphs": [ + { + "checksum": "676be38dcb5032a5b66c95c6e0c85e8d4f049bf01933b118a0967607b5fbbf6c", + "id": "fls_1dvo1epstrdv", + "link": "types-and-traits.html#fls_1dvo1epstrdv", + "number": "4.9.4:1" + } + ], + "title": "Parenthesized Types" + }, + { + "id": "fls_kgvleup5mdhq", + "informational": false, + "link": "types-and-traits.html#type-aliases", + "number": "4.10", + "paragraphs": [ + { + "checksum": "25fd89d90847d3d474a7da20e5aba2d29f31f8f7e17c2aeb98d2a62683b49efc", + "id": "fls_bibigic4jjad", + "link": "types-and-traits.html#fls_bibigic4jjad", + "number": "4.10:1" }, { - "checksum": "46f39fc8f0d7ac26fb707ada2c7d2bb2adf405a080f461715483c86974008ef6", - "id": "fls_Uv5CcMHPX79J", - "link": "types-and-traits.html#fls_Uv5CcMHPX79J", - "number": "4.14.2:45" + "checksum": "8ad56706cf3948a37e7738f416f5065e7f1335a2443524ff1c3ebea736ac75fa", + "id": "fls_rosdkeck5ax2", + "link": "types-and-traits.html#fls_rosdkeck5ax2", + "number": "4.10:2" }, { - "checksum": "8b74cbe1bf4dbd256643c98b2f8a49a0f64cb55a7774638a968c045d642880d1", - "id": "fls_LyvV4pOG7E4l", - "link": "types-and-traits.html#fls_LyvV4pOG7E4l", - "number": "4.14.2:46" + "checksum": "9db2e47ca9bc68f63d03dab529165f2062950ff28398da808d41f83989833ac9", + "id": "fls_drxl7u3etfp9", + "link": "types-and-traits.html#fls_drxl7u3etfp9", + "number": "4.10:3" } ], - "title": "Subtyping and Variance" + "title": "Type Aliases" }, { - "id": "fls_l9ebxrlxyawd", + "id": "fls_7pby13muw48o", "informational": false, - "link": "types-and-traits.html#lifetime-elision", - "number": "4.14.3", + "link": "types-and-traits.html#representation", + "number": "4.11", + "paragraphs": [], + "title": "Representation" + }, + { + "id": "fls_g1z6bpyjqxkz", + "informational": false, + "link": "types-and-traits.html#type-layout", + "number": "4.11.1", "paragraphs": [ { - "checksum": "d79a7a7c2d74efad1b16985dc59f37eab43dacda82826ca7822557c72a0932ec", - "id": "fls_9wtuclhm7yz5", - "link": "types-and-traits.html#fls_9wtuclhm7yz5", - "number": "4.14.3:1" + "checksum": "ec574263a104f5220dbeec30e0118b25ac35e37d6a614ad3bc5cd6e58681532d", + "id": "fls_kdbq02iguzgl", + "link": "types-and-traits.html#fls_kdbq02iguzgl", + "number": "4.11.1:1" }, { - "checksum": "32d1b793cd39bce611678730276e1b4319fbec6614cd48c07f2d26a1e76e5679", - "id": "fls_JmP6O9zj8fkV", - "link": "types-and-traits.html#fls_JmP6O9zj8fkV", - "number": "4.14.3:2" + "checksum": "85b5fbe662d8867421f671eced959a90cc1ae59ca88984af3e8db0fddecd7774", + "id": "fls_26Xgem831Nqg", + "link": "types-and-traits.html#fls_26Xgem831Nqg", + "number": "4.11.1:2" }, { - "checksum": "6bfff075569653189958ff85ac5cf2713c6e2e10a61755e557a5a274114a10f4", - "id": "fls_5ZAQ9p7jQuc2", - "link": "types-and-traits.html#fls_5ZAQ9p7jQuc2", - "number": "4.14.3:3" + "checksum": "4b74a052b7ae0bcd4e6bd2091a170309e1d85fcb0c8e4fd4b397abf85839197a", + "id": "fls_ozYgHEHFTT5c", + "link": "types-and-traits.html#fls_ozYgHEHFTT5c", + "number": "4.11.1:3" }, { - "checksum": "01b2cb5f3106d4540ec427082a3baee106878240cfbf49a243c14625f8e7f690", - "id": "fls_YmUQ8ZiQuycp", - "link": "types-and-traits.html#fls_YmUQ8ZiQuycp", - "number": "4.14.3:4" + "checksum": "9fce4f034a0c87f5dc66db7b3e507d3b8d4ccadac4c182b83669e0c361da3f13", + "id": "fls_muxfn9soi47l", + "link": "types-and-traits.html#fls_muxfn9soi47l", + "number": "4.11.1:4" }, { - "checksum": "ed43d8d7ebdf76d78bd1611b62a5a14e51cc7b74a08399dbbe6a89ca09432165", - "id": "fls_sIMN6Sd8xUZz", - "link": "types-and-traits.html#fls_sIMN6Sd8xUZz", - "number": "4.14.3:5" + "checksum": "07e54228ecadbf143bd9829ea2af5cd942e05ae28f70c0bf04ece785266561b3", + "id": "fls_1pbwigq6f3ha", + "link": "types-and-traits.html#fls_1pbwigq6f3ha", + "number": "4.11.1:5" + }, + { + "checksum": "3d989ed20cee6bc9220874459a4ea548220f9e445447a73f0477590d4aaa0908", + "id": "fls_bk3nm2n47afu", + "link": "types-and-traits.html#fls_bk3nm2n47afu", + "number": "4.11.1:6" + }, + { + "checksum": "509cfea09ca67250d7134d86496da2f7ddccb10a30cc84e8fe0614e40a946388", + "id": "fls_z3i758jshvhx", + "link": "types-and-traits.html#fls_z3i758jshvhx", + "number": "4.11.1:7" + }, + { + "checksum": "955ca7ce99e58bbbb5f65e867911434aaa20ce3d4b5859af83963a91b4528556", + "id": "fls_uixe1ruv52be", + "link": "types-and-traits.html#fls_uixe1ruv52be", + "number": "4.11.1:8" }, { - "checksum": "e2eeb47e603a2757ced1b1b048a74e8c70eff013d072dae88111c56d15f1a50a", - "id": "fls_dIyisjNIx9dC", - "link": "types-and-traits.html#fls_dIyisjNIx9dC", - "number": "4.14.3:6" + "checksum": "596b59e14342649bfc88ebf54917a1503fc3ecb4de2d025b3df3a6048d0516ba", + "id": "fls_7at60xlxm9u4", + "link": "types-and-traits.html#fls_7at60xlxm9u4", + "number": "4.11.1:9" }, { - "checksum": "c4bf3768ceb2465a2a096c8aef590aee129db81593c92b2b8d8529e5a0e7fdfa", - "id": "fls_cD0ZYi23VqWg", - "link": "types-and-traits.html#fls_cD0ZYi23VqWg", - "number": "4.14.3:7" + "checksum": "65566a896e699d4581061cbfa9881b61ebbce21a79ba3ae60cfebc267d506a43", + "id": "fls_395247pkxv48", + "link": "types-and-traits.html#fls_395247pkxv48", + "number": "4.11.1:10" }, { - "checksum": "4eabb3c647de45efeb944bafbeabae6a318a9325155ef389199603ed1e19acc1", - "id": "fls_sA4Lqc5o6cX3", - "link": "types-and-traits.html#fls_sA4Lqc5o6cX3", - "number": "4.14.3:8" - } - ], - "title": "Lifetime Elision" - }, - { - "id": "fls_hethxxbcg7ja", - "informational": false, - "link": "types-and-traits.html#function-lifetime-elision", - "number": "4.14.3.1", - "paragraphs": [ + "checksum": "ea4301cd4ad66803e16f0b4f0d2b5c2a336a7cf59a8a11148379d67b5d3371e0", + "id": "fls_tbe9sc75timc", + "link": "types-and-traits.html#fls_tbe9sc75timc", + "number": "4.11.1:11" + }, { - "checksum": "8fc35e5cf136f57bafd43ae857a0540f3e6377d540a98f10bf9575e151baba5c", - "id": "fls_lAdIRCFFlydD", - "link": "types-and-traits.html#fls_lAdIRCFFlydD", - "number": "4.14.3.1:1" + "checksum": "16ddd66245cad2e311b9193a332f01ac5e3875c354baf1b3bebb93bc4dd430af", + "id": "fls_7jaqx33re3hg", + "link": "types-and-traits.html#fls_7jaqx33re3hg", + "number": "4.11.1:12" }, { - "checksum": "4e446345859b9323669a875dd49ec52cce3e71e7f65700097e1ec21cf6f60506", - "id": "fls_dpudys82dhdc", - "link": "types-and-traits.html#fls_dpudys82dhdc", - "number": "4.14.3.1:2" + "checksum": "56149efe39f3ec896b5d41b41f82964eebe02b0be71a3cda53763e854c3559ed", + "id": "fls_asys0iz6m0md", + "link": "types-and-traits.html#fls_asys0iz6m0md", + "number": "4.11.1:13" }, { - "checksum": "a52ef65ad59c6a03acd104f52921c0bd1ab194c0d933c3d187455e8266facccb", - "id": "fls_pjil71kk0r25", - "link": "types-and-traits.html#fls_pjil71kk0r25", - "number": "4.14.3.1:3" + "checksum": "711825cc745b9af39e7e4048a16c47f77125cf76d5cbe980e5ff38079d76320f", + "id": "fls_wfv5vcxl2lc7", + "link": "types-and-traits.html#fls_wfv5vcxl2lc7", + "number": "4.11.1:14" }, { - "checksum": "560234cb87c2e6ff7cdbb0d27ca2bc465b9ad0c33b073139a622fc945d297d65", - "id": "fls_1jnn9bsb71k7", - "link": "types-and-traits.html#fls_1jnn9bsb71k7", - "number": "4.14.3.1:4" + "checksum": "e999468acaca0c1905fc4050b53eecbc0ba5d19207fe6a97efd97bad0ba0f84e", + "id": "fls_x8dfw50z9c", + "link": "types-and-traits.html#fls_x8dfw50z9c", + "number": "4.11.1:15" }, { - "checksum": "b16b3fc503b0ef9b2bb8a6286c781f2bff6bdc26cebcef598491234908bf0fe3", - "id": "fls_2p29p1fvi182", - "link": "types-and-traits.html#fls_2p29p1fvi182", - "number": "4.14.3.1:5" + "checksum": "4d8795302b12790cc45fa4ebd52923e953f775313a4540fe398a33d90911f943", + "id": "fls_nyxnnlwmt5gu", + "link": "types-and-traits.html#fls_nyxnnlwmt5gu", + "number": "4.11.1:16" }, { - "checksum": "a87e49e984f97b2802b8b6c2cf73a563f66412b9c089d6731015646d67c30786", - "id": "fls_d4u3y82hdadc", - "link": "types-and-traits.html#fls_d4u3y82hdadc", - "number": "4.14.3.1:6" + "checksum": "0824d734bae0a452fabd30f7eebe1ce756436f484b63cb76f82a47710b2b2613", + "id": "fls_lwmrljw9m0pb", + "link": "types-and-traits.html#fls_lwmrljw9m0pb", + "number": "4.11.1:17" }, { - "checksum": "2733e9f6847d7d23dfbaf715feec476d91d9caef20c3b3b354287dd0c3eebfe1", - "id": "fls_ks8wlufmhz6d", - "link": "types-and-traits.html#fls_ks8wlufmhz6d", - "number": "4.14.3.1:7" + "checksum": "f3191b0a3434aec33dea3707cfa15d73aa4eed96e7edc9ef18c8d26970af27f2", + "id": "fls_pzi6izljfv0f", + "link": "types-and-traits.html#fls_pzi6izljfv0f", + "number": "4.11.1:18" }, { - "checksum": "3bcbd78a434bf0ba38571a7dd6ed85585dd4e92b865edc90332ecf4b4daaa3bf", - "id": "fls_hsg9kfyvh35m", - "link": "types-and-traits.html#fls_hsg9kfyvh35m", - "number": "4.14.3.1:8" + "checksum": "b8f0794e107a5077c3c243a841267feca917797abb0af99397715c0652488ec7", + "id": "fls_7cjbxleo998q", + "link": "types-and-traits.html#fls_7cjbxleo998q", + "number": "4.11.1:19" }, { - "checksum": "16c08c00ce2a3df58129efe63027ec80880b95b07f01d9fd2c0c7f505d079150", - "id": "fls_ofqy10q4a9jk", - "link": "types-and-traits.html#fls_ofqy10q4a9jk", - "number": "4.14.3.1:9" + "checksum": "93a93b729d2fc10096d6f3e153b0d420ed95d0a2c40e7b3766a6775b99f2d23b", + "id": "fls_veotnstzigw2", + "link": "types-and-traits.html#fls_veotnstzigw2", + "number": "4.11.1:20" }, { - "checksum": "90c63f8b0b6e4ccfd99d0308b04e2980a4b53f0de2e3a67cbec4ff8019bc7e2d", - "id": "fls_yofbo96tjppf", - "link": "types-and-traits.html#fls_yofbo96tjppf", - "number": "4.14.3.1:10" + "checksum": "b65cf13de25acfcffa5571a788c21ffc81a25f4caf34236efd82d429143e4f38", + "id": "fls_nmoqk7jo1kzf", + "link": "types-and-traits.html#fls_nmoqk7jo1kzf", + "number": "4.11.1:21" }, { - "checksum": "d8fc6c8065816c13c111ca417994ff7168f457d619f2299e9be1d36a9eb4e6dd", - "id": "fls_vf7cxiir91ps", - "link": "types-and-traits.html#fls_vf7cxiir91ps", - "number": "4.14.3.1:11" + "checksum": "5c51e84731e68ffbfb71d7770138e0e073c19250eef8f79cfe653382420ed1eb", + "id": "fls_gd7wozpn2ecp", + "link": "types-and-traits.html#fls_gd7wozpn2ecp", + "number": "4.11.1:22" }, { - "checksum": "012616fbf6f9d9c37bc76e01c82464fb6d65c6787d308e10f7e7f8d4eb477716", - "id": "fls_g56br27hq2zj", - "link": "types-and-traits.html#fls_g56br27hq2zj", - "number": "4.14.3.1:12" + "checksum": "a50cf2c5f56fb453ff79b573e1b12129846b5614280b059f31282bdbad00120a", + "id": "fls_18ke90udyp67", + "link": "types-and-traits.html#fls_18ke90udyp67", + "number": "4.11.1:23" }, { - "checksum": "c20fd04609ae44c467a3be462def3caaead0ee6b0c11a572851ac3bda8e9e7db", - "id": "fls_1j204m1wy333", - "link": "types-and-traits.html#fls_1j204m1wy333", - "number": "4.14.3.1:13" + "checksum": "16f17b053148dcb88c3d4eac54d2fa3ab5c26241239c5f849b5e78a7c1987c27", + "id": "fls_nrqG8i3fmpm4", + "link": "types-and-traits.html#fls_nrqG8i3fmpm4", + "number": "4.11.1:24" }, { - "checksum": "cbcb79548a5d9ac2995323208dcd3a9ccb79c785569231727fae4a32af08e791", - "id": "fls_6km3cbchuxr2", - "link": "types-and-traits.html#fls_6km3cbchuxr2", - "number": "4.14.3.1:14" + "checksum": "61615c593b3ab4fbf2ecc57b3474628e35997aae869a5e77a7db2c1d329c4027", + "id": "fls_e5hivr6m5s3h", + "link": "types-and-traits.html#fls_e5hivr6m5s3h", + "number": "4.11.1:25" }, { - "checksum": "809cb7417b66935ad1e1a2be5657a090e793aeeb0ff3a67c4e7777db5a76dd1a", - "id": "fls_crb6m6b3cdwh", - "link": "types-and-traits.html#fls_crb6m6b3cdwh", - "number": "4.14.3.1:15" + "checksum": "10f4ec2a3b2055e8ac3f6ac122c55a3d72a69b32bc92531cb8cd3174405fc276", + "id": "fls_hlbsjggfxnt2", + "link": "types-and-traits.html#fls_hlbsjggfxnt2", + "number": "4.11.1:26" }, { - "checksum": "0a93ba5afb72ab5a83c7159ad67dc0478704a55a3d3ef9325b44d0aee4a5a11b", - "id": "fls_ac9tdlfwp5et", - "link": "types-and-traits.html#fls_ac9tdlfwp5et", - "number": "4.14.3.1:16" + "checksum": "e182b27b475774c568aecdb1bb2f1070177f14e235e96672e4884e9085b0e263", + "id": "fls_sdrb0k2r18my", + "link": "types-and-traits.html#fls_sdrb0k2r18my", + "number": "4.11.1:27" }, { - "checksum": "b0b24169a682ebd4af464f022caa0e0bc52dc45c2ab5969c01b1b6a2a313b7f4", - "id": "fls_qtjc7334wzhj", - "link": "types-and-traits.html#fls_qtjc7334wzhj", - "number": "4.14.3.1:17" + "checksum": "69bc4664319d5665fede7b3cce71149d020a6355095a53a2e4628588ed83b8a2", + "id": "fls_gt3tkbn4bsa6", + "link": "types-and-traits.html#fls_gt3tkbn4bsa6", + "number": "4.11.1:28" }, { - "checksum": "98fed21cebc3cecb0b210e636cc45a37121a883718d0ba5e64b36abb86d70f58", - "id": "fls_vcmmkp9uruhr", - "link": "types-and-traits.html#fls_vcmmkp9uruhr", - "number": "4.14.3.1:18" + "checksum": "bda0b012a16d3a292ca3bd3cd3bf7397fc037ec2df84e2d98d42f8caad457108", + "id": "fls_njvdevz0xqc0", + "link": "types-and-traits.html#fls_njvdevz0xqc0", + "number": "4.11.1:29" } ], - "title": "Function Lifetime Elision" + "title": "Type Layout" }, { - "id": "fls_u5lqku2rs6uv", + "id": "fls_ohhsmifo0urd", "informational": false, - "link": "types-and-traits.html#static-lifetime-elision", - "number": "4.14.3.2", + "link": "types-and-traits.html#type-representation", + "number": "4.11.2", "paragraphs": [ { - "checksum": "362c1e5e7ef9d1bb007461a6e77c25fd2bdd4146d43523089f709b4eecdb11f2", - "id": "fls_l4RDXaFwnQZ6", - "link": "types-and-traits.html#fls_l4RDXaFwnQZ6", - "number": "4.14.3.2:1" + "checksum": "5f5949a619afc73e47aec91fbc3a9a63a26a1c89ac23fff54dedd659fa5ce152", + "id": "fls_mpqlyi3lgrfv", + "link": "types-and-traits.html#fls_mpqlyi3lgrfv", + "number": "4.11.2:1" }, { - "checksum": "a4e8a63dc12eaffaf473b18138e9e501b7fedc18b8b3cb4105a41724426cd35a", - "id": "fls_8irr97rZWfSC", - "link": "types-and-traits.html#fls_8irr97rZWfSC", - "number": "4.14.3.2:2" + "checksum": "b57e3b6dad3a8e0cb95ec2f29b9c0c03d35a3d2274ce1372f790c18e6d8fe08a", + "id": "fls_9dhnanv21y9z", + "link": "types-and-traits.html#fls_9dhnanv21y9z", + "number": "4.11.2:2" }, { - "checksum": "e2a84e07dc44c60162e59d18213737a3fbdbe13962c4e3f32178970f3d2927ea", - "id": "fls_37udexenqv3p", - "link": "types-and-traits.html#fls_37udexenqv3p", - "number": "4.14.3.2:3" + "checksum": "9af98560b964faa10575d6b41e63eab6392b21e562bd53d52334986e8d562170", + "id": "fls_3dwtkr7vzha0", + "link": "types-and-traits.html#fls_3dwtkr7vzha0", + "number": "4.11.2:3" }, { - "checksum": "1eae2df72c51ffcad42ba90f5618089142bb9116967a5bcd8c0f9f34b11bfcc2", - "id": "fls_xi86he5vvill", - "link": "types-and-traits.html#fls_xi86he5vvill", - "number": "4.14.3.2:4" + "checksum": "4e17b1a0b34e4bf31d8a8074de82b29eab2771c248d198c7505d85d025d02401", + "id": "fls_q465p1xuzxi", + "link": "types-and-traits.html#fls_q465p1xuzxi", + "number": "4.11.2:4" + }, + { + "checksum": "5f34cbfe6622d2c818a60366091bc5da0bc271de40adb90bbb62a83edca10e7e", + "id": "fls_hrsdn21jmgx2", + "link": "types-and-traits.html#fls_hrsdn21jmgx2", + "number": "4.11.2:5" + }, + { + "checksum": "6d966d386110f51effa3166dbdfd2297301ac38191e2cf2c2fb7bd2786dfaec6", + "id": "fls_ergdb18tpx25", + "link": "types-and-traits.html#fls_ergdb18tpx25", + "number": "4.11.2:6" + }, + { + "checksum": "e149df36aa01e86286388dbbe81e921138ff3db34c7e3b70fe56471e82478501", + "id": "fls_8s1vddh8vdhy", + "link": "types-and-traits.html#fls_8s1vddh8vdhy", + "number": "4.11.2:7" + }, + { + "checksum": "eccb8cf4151d93a2f40c470c83debcdb0664c35fb9b1a56d77688b9bbc34fa14", + "id": "fls_b005bktrkrxy", + "link": "types-and-traits.html#fls_b005bktrkrxy", + "number": "4.11.2:8" + }, + { + "checksum": "2df37a1590825b869d3fe88027b64189cf4a81a9bf29a7fbcb2ba1790f600026", + "id": "fls_7plbkqlmed0r", + "link": "types-and-traits.html#fls_7plbkqlmed0r", + "number": "4.11.2:9" + }, + { + "checksum": "ab1548f30cb5de39a5164639b856c80192fe4565c992ebc857d91f330cfd95b1", + "id": "fls_ml4khttq3w5k", + "link": "types-and-traits.html#fls_ml4khttq3w5k", + "number": "4.11.2:10" }, { - "checksum": "686ca62c3062007fe2a2c7112962d70308c2004c4e6eed04508f5a30e22ed81d", - "id": "fls_2GKCEI9MwMn9", - "link": "types-and-traits.html#fls_2GKCEI9MwMn9", - "number": "4.14.3.2:5" + "checksum": "7705e77c0b72bbab2b260c177130bc9c9b2b0cd780adfef1c71bef4a3fa35b1c", + "id": "fls_9q2iqzbup8oy", + "link": "types-and-traits.html#fls_9q2iqzbup8oy", + "number": "4.11.2:11" }, { - "checksum": "87777c1e9a852ae61349dd7554488c91f20c43dc493ec06547aa740dcba8ac22", - "id": "fls_f3yZ31dRuTPG", - "link": "types-and-traits.html#fls_f3yZ31dRuTPG", - "number": "4.14.3.2:6" + "checksum": "bcb966f534ed9405fc751a9c095cd83a0ebf9e78d42f0fa542522fc9ba0ec64f", + "id": "fls_fsbf6ist38ix", + "link": "types-and-traits.html#fls_fsbf6ist38ix", + "number": "4.11.2:12" + }, + { + "checksum": "6ea20905fb18b0e4a39823e4d29c64d3000f65f803a757758399867d375784b8", + "id": "fls_qkkc8x2oghst", + "link": "types-and-traits.html#fls_qkkc8x2oghst", + "number": "4.11.2:13" } ], - "title": "Static Lifetime Elision" + "title": "Type Representation" }, { - "id": "fls_xtbohk2yk4la", + "id": "fls_xc1hof4qbf6p", "informational": false, - "link": "types-and-traits.html#trait-object-lifetime-elision", - "number": "4.14.3.3", + "link": "types-and-traits.html#enum-type-representation", + "number": "4.11.2.1", "paragraphs": [ { - "checksum": "9792cf5e24351dd23a21b5298dab4d7c7f182536a15cd1082d892ea56f6fed7e", - "id": "fls_fuBYWRrgxlbQ", - "link": "types-and-traits.html#fls_fuBYWRrgxlbQ", - "number": "4.14.3.3:1" + "checksum": "3eb2acc0e91b94225e6e44043b9ed3ef348d1fbdfb8db38506d619d240c6f6e5", + "id": "fls_p0c62ejo1u1t", + "link": "types-and-traits.html#fls_p0c62ejo1u1t", + "number": "4.11.2.1:1" }, { - "checksum": "735f380f9e5867b7964548adbbe1ef171e6362a7165d946fa2f88f1c49835e00", - "id": "fls_URl9CeIVsiWs", - "link": "types-and-traits.html#fls_URl9CeIVsiWs", - "number": "4.14.3.3:2" + "checksum": "9223cf2e73ff1760ca4c9edcf7ea6c2b6a71a16c836006c862557a4a50880a0b", + "id": "fls_efp1kfgkpba8", + "link": "types-and-traits.html#fls_efp1kfgkpba8", + "number": "4.11.2.1:2" }, { - "checksum": "b8dc302081286801fab56e83adfb0c6641a4aabc8e37c977089243aa1c6eb354", - "id": "fls_SHhw6lYHeYyQ", - "link": "types-and-traits.html#fls_SHhw6lYHeYyQ", - "number": "4.14.3.3:3" + "checksum": "596c70e3ed03568cc8842491654b253cff2dd3c53ecff886f8790626e79cdf54", + "id": "fls_s9c0a0lg6c0p", + "link": "types-and-traits.html#fls_s9c0a0lg6c0p", + "number": "4.11.2.1:3" }, { - "checksum": "d14fa2465cadaa9f9404de8d4f1dc2c83ea23291fb5a15f281ccbc77646725a3", - "id": "fls_lC2rwdPLRwaf", - "link": "types-and-traits.html#fls_lC2rwdPLRwaf", - "number": "4.14.3.3:4" + "checksum": "a91c1cca73e27a37a40cfe16ebf7b3dd4618e680e133ba6257ba1c08b26b66a6", + "id": "fls_slhvf3gmqz4h", + "link": "types-and-traits.html#fls_slhvf3gmqz4h", + "number": "4.11.2.1:4" }, { - "checksum": "5ce3569a487aec0ffe76efa0ebd2f980162b7540452d2800b92474ac6b7fe730", - "id": "fls_e36Hh4oJvfhv", - "link": "types-and-traits.html#fls_e36Hh4oJvfhv", - "number": "4.14.3.3:5" + "checksum": "fea4e03795d124973a8ffab094f6ee265a8f520b32a2c7d748015882a86761cc", + "id": "fls_u1zy06510m56", + "link": "types-and-traits.html#fls_u1zy06510m56", + "number": "4.11.2.1:5" }, { - "checksum": "416a9c7f994a10348666cc1428fe679ecfbb5cc34c3fd991214116fd5405123c", - "id": "fls_ptejalcnIQtm", - "link": "types-and-traits.html#fls_ptejalcnIQtm", - "number": "4.14.3.3:6" + "checksum": "8a55fc830de2323e466a47edb68b3963f8dbb9972e1194ecb80a9c6b4a9252fd", + "id": "fls_ryvqkcx48u74", + "link": "types-and-traits.html#fls_ryvqkcx48u74", + "number": "4.11.2.1:6" }, { - "checksum": "9a1ce8dbae4264be182622bdedc1cd24fb5bc4c9c3d2a9805d7e83d22bebdac8", - "id": "fls_rGbdKtTijby4", - "link": "types-and-traits.html#fls_rGbdKtTijby4", - "number": "4.14.3.3:7" + "checksum": "9511193dd174fc3bafe80e8350dddde4679a8741fb947fe3f5e1e891a33d51ae", + "id": "fls_zhle0rb0vhpc", + "link": "types-and-traits.html#fls_zhle0rb0vhpc", + "number": "4.11.2.1:7" }, { - "checksum": "3ded6c067304c60cab3a670cb056d96704403c4fd5c6719d0ccfba410623ced1", - "id": "fls_JhmQpUoExiNZ", - "link": "types-and-traits.html#fls_JhmQpUoExiNZ", - "number": "4.14.3.3:8" + "checksum": "f51fdfab79671ea2955e16f13e207e87820a37d5f55b1d4c22064de5c137211a", + "id": "fls_45f57s1gmmh5", + "link": "types-and-traits.html#fls_45f57s1gmmh5", + "number": "4.11.2.1:8" }, { - "checksum": "b9f5585c56ad53d5720ebad44d77024dd47add4769f1886a76551f07ddaeb2ca", - "id": "fls_cglZigwAnASl", - "link": "types-and-traits.html#fls_cglZigwAnASl", - "number": "4.14.3.3:9" + "checksum": "b8004c9524d9dcc6e7c1fad6f95241fbb7388c13ce2a105eea1f0fe9fc97517a", + "id": "fls_hz012yus6b4g", + "link": "types-and-traits.html#fls_hz012yus6b4g", + "number": "4.11.2.1:9" }, { - "checksum": "d6269aed267968a9a469c7003f9ac3e0b12044648f1246e40ef2eea39d5610b4", - "id": "fls_MipY2emZFF6d", - "link": "types-and-traits.html#fls_MipY2emZFF6d", - "number": "4.14.3.3:10" + "checksum": "b6f79b18180060bbc48e3b28c798cfabb1fbe4261d4f29db7e66461a870e1b05", + "id": "fls_q5akku2idrwh", + "link": "types-and-traits.html#fls_q5akku2idrwh", + "number": "4.11.2.1:10" }, { - "checksum": "8160bcf42506146875a69bae6334ab1868c2957f6f1b085629deedafa0ad1914", - "id": "fls_YPesUZqYHVUX", - "link": "types-and-traits.html#fls_YPesUZqYHVUX", - "number": "4.14.3.3:11" + "checksum": "7f5298c7fcf4553ee2096af2930625f9d951363c4f6529b7d5d99c07b1fc520f", + "id": "fls_r6o1wv76yw6m", + "link": "types-and-traits.html#fls_r6o1wv76yw6m", + "number": "4.11.2.1:11" + }, + { + "checksum": "939150187e841861875019dc485b2aa948610bdabcf75995a8afad5299edfc9c", + "id": "fls_3k1tcfxp0g63", + "link": "types-and-traits.html#fls_3k1tcfxp0g63", + "number": "4.11.2.1:12" + }, + { + "checksum": "89652dc80a444cd6f5989de7e2a59fcbee6155ba69450db9b25c7c463673bdf4", + "id": "fls_ebs77rxvk9st", + "link": "types-and-traits.html#fls_ebs77rxvk9st", + "number": "4.11.2.1:13" + }, + { + "checksum": "8bea878c1256e24db0fc455c3c525bca9c6c94300cfac8d7f432673f4a43fafd", + "id": "fls_k907i6w83s2", + "link": "types-and-traits.html#fls_k907i6w83s2", + "number": "4.11.2.1:14" } ], - "title": "Trait Object Lifetime Elision" + "title": "Enum Type Representation" }, { - "id": "fls_zqpv1ybddse1", + "id": "fls_rjxpof29a3nl", "informational": false, - "link": "types-and-traits.html#impl-header-lifetime-elision", - "number": "4.14.3.4", + "link": "types-and-traits.html#struct-type-representation", + "number": "4.11.2.2", "paragraphs": [ { - "checksum": "f6a449af6e0ee8f776a596f24acb1ee832b6830604a4f17b42249cef950b08b8", - "id": "fls_FUdsmzN0T8XP", - "link": "types-and-traits.html#fls_FUdsmzN0T8XP", - "number": "4.14.3.4:1" - }, - { - "checksum": "a0ad068ff94137edb05b75c066208d34c26cad7e567ccbea22214e4480f035f5", - "id": "fls_3p5BdLn3JbKz", - "link": "types-and-traits.html#fls_3p5BdLn3JbKz", - "number": "4.14.3.4:2" - }, - { - "checksum": "ddc3e2f6339da6c1ddd05ef0d487698b078e2c06a7456b12337e4b04720ec88d", - "id": "fls_PfS5AlkN6ANl", - "link": "types-and-traits.html#fls_PfS5AlkN6ANl", - "number": "4.14.3.4:3" - }, - { - "checksum": "23b6ef8e94104fd9bfc08bddfadbc0e6b45e3af1816b852b6542fa4ef26a99b4", - "id": "fls_QV6zE5Mdor5E", - "link": "types-and-traits.html#fls_QV6zE5Mdor5E", - "number": "4.14.3.4:4" + "checksum": "5f76aa9810ba03c28ebf2942db3a8fb181ab22d0b664086601393227aa7bc9eb", + "id": "fls_jr9dykj6rydn", + "link": "types-and-traits.html#fls_jr9dykj6rydn", + "number": "4.11.2.2:1" }, { - "checksum": "7e879940c036b1acde707493ab1511307ccc68d03e5e0c62cfd4d9d230a8bc69", - "id": "fls_P2V1DqrdCjCi", - "link": "types-and-traits.html#fls_P2V1DqrdCjCi", - "number": "4.14.3.4:5" + "checksum": "5fc0a7214a5fa354bcf5cb9aca684565b666321cce8849cf8e89065c73dca2bc", + "id": "fls_6ck71twmnbg5", + "link": "types-and-traits.html#fls_6ck71twmnbg5", + "number": "4.11.2.2:2" }, { - "checksum": "c01105160b098b089c2c01853293cccd8231a07686ae97a7d7aba145d987c330", - "id": "fls_MBa35hfS8J9l", - "link": "types-and-traits.html#fls_MBa35hfS8J9l", - "number": "4.14.3.4:6" + "checksum": "cc7a74a98eb6db165a74e65d78a53928cd6ceb8d9b8f47c5549d190d9c561b5d", + "id": "fls_hydq3pvm00bn", + "link": "types-and-traits.html#fls_hydq3pvm00bn", + "number": "4.11.2.2:3" }, { - "checksum": "3b91fe9b60e23bf9452baf8b3ca1624e42e4109b50e88a340e7c1998b9a6772c", - "id": "fls_w0vwdmO8qV9j", - "link": "types-and-traits.html#fls_w0vwdmO8qV9j", - "number": "4.14.3.4:7" + "checksum": "f0a3247f449084aa9aae9debf4ffa6a6913bb680bf0b458cb343993f72476848", + "id": "fls_yzcdffahxcz", + "link": "types-and-traits.html#fls_yzcdffahxcz", + "number": "4.11.2.2:4" }, { - "checksum": "68c7b26da67383cc4c0a68039f093046d35f47ac193122f01475bcb83de41546", - "id": "fls_vImY3BrpNvNY", - "link": "types-and-traits.html#fls_vImY3BrpNvNY", - "number": "4.14.3.4:8" - } - ], - "title": "Impl Header Lifetime Elision" - } - ], - "title": "Types and Traits" - }, - { - "informational": false, - "link": "values.html", - "sections": [ - { - "id": "fls_94a8v54bufn8", - "informational": false, - "link": "values.html", - "number": "7", - "paragraphs": [ - { - "checksum": "1a30c4fd875bd4b58309644a55091eee8d947eb825edf66ca6ed1b979a89b878", - "id": "fls_buyaqara7am4", - "link": "values.html#fls_buyaqara7am4", - "number": "7:1" + "checksum": "f655a5cbd5d73a7e395bd286c59614f96f179cfba822ade23467a58dd59c9416", + "id": "fls_t2yqmphfd6he", + "link": "types-and-traits.html#fls_t2yqmphfd6he", + "number": "4.11.2.2:5" }, { - "checksum": "0902cf1dae20d01533ef8ea3dade455b4f9f42cb77e7dde502c69b6ac4643be0", - "id": "fls_CUJyMj0Sj8NS", - "link": "values.html#fls_CUJyMj0Sj8NS", - "number": "7:2" + "checksum": "8f137c8f4826a9313a42e8c5bec10771adb0f8414465461fd20d604736c77eea", + "id": "fls_fa5nkvu07jlp", + "link": "types-and-traits.html#fls_fa5nkvu07jlp", + "number": "4.11.2.2:6" }, { - "checksum": "0c88ea1a19e3e040264b950c22acf3f967df13518dee02b93354389d91fc6a8a", - "id": "fls_kaomYy0Ml4Nh", - "link": "values.html#fls_kaomYy0Ml4Nh", - "number": "7:3" + "checksum": "1d2016f00c240d05addd5fab89f965d3641505505bfb01110d95a59a9d0abfda", + "id": "fls_x2pkmgbp63xx", + "link": "types-and-traits.html#fls_x2pkmgbp63xx", + "number": "4.11.2.2:7" }, { - "checksum": "a6432f34a97d6a2e9a35a5c164ee5140344db36cb4b42ca5aef5fbd0a9f8c741", - "id": "fls_B5cmkWfD5GNt", - "link": "values.html#fls_B5cmkWfD5GNt", - "number": "7:4" + "checksum": "a43584acfcb4f9504ce4581592588e3c52f9f07bc3fac6200626820608fe115a", + "id": "fls_y6dwc1ndm395", + "link": "types-and-traits.html#fls_y6dwc1ndm395", + "number": "4.11.2.2:8" }, { - "checksum": "73bb0dbe94c2f521e89fae4c897fc338813c9175ff4600fed13e41c0b9878e30", - "id": "fls_rixdyyc525xp", - "link": "values.html#fls_rixdyyc525xp", - "number": "7:5" + "checksum": "03a369fe556766892948f430b4527499cd07a737e8ebb566d9f4935b1ed0bd81", + "id": "fls_2npku94ookdn", + "link": "types-and-traits.html#fls_2npku94ookdn", + "number": "4.11.2.2:9" }, { - "checksum": "501fd5dc12d09844a612f32f00e4d5a2dedfdf7a1659cf1e4b1e7aa5ab84d7bb", - "id": "fls_m6ctqq70vcxr", - "link": "values.html#fls_m6ctqq70vcxr", - "number": "7:6" + "checksum": "53b32f8e3a420b85e3831feb7488281662c227e49e7585de6de3f2147d00ec6d", + "id": "fls_h7nvs25rsi0y", + "link": "types-and-traits.html#fls_h7nvs25rsi0y", + "number": "4.11.2.2:10" }, { - "checksum": "d4a5c1906b3bbe1c9d809c9d1156e75673a511153eedc4950f47fb135b6a59a8", - "id": "fls_s231d18x5eay", - "link": "values.html#fls_s231d18x5eay", - "number": "7:7" + "checksum": "81829e6f383a4c65151f020b3440763a7261c3898230a6504f0d83bbb62f8f40", + "id": "fls_iu93vpyihrpj", + "link": "types-and-traits.html#fls_iu93vpyihrpj", + "number": "4.11.2.2:11" }, { - "checksum": "01045132eff6e0617d4cca46c1cc3f5d88d56776cf683971fc1530459b7f48fe", - "id": "fls_dfr4yqo93fsn", - "link": "values.html#fls_dfr4yqo93fsn", - "number": "7:8" + "checksum": "779b7205751c55010c330675d65578fe47e6bdaefe5a76eef9dc878a6ad61921", + "id": "fls_7sjkej5otxo", + "link": "types-and-traits.html#fls_7sjkej5otxo", + "number": "4.11.2.2:12" }, { - "checksum": "e4226c8e3ff3c227a9d0de5f8e42ad468c5a93f6fb87c981f7f126d3d5399060", - "id": "fls_eoak5mdl6ma", - "link": "values.html#fls_eoak5mdl6ma", - "number": "7:9" + "checksum": "83a4d8e45980a24abbd901d57911b4c082699c53464ebbe23837f6862ff654c0", + "id": "fls_gwhceoy0m3or", + "link": "types-and-traits.html#fls_gwhceoy0m3or", + "number": "4.11.2.2:13" }, { - "checksum": "2935b280567bdb6d4a9792e339cac22702fe95059923d9a738c60a7a8548dab5", - "id": "fls_6lg0oaaopc26", - "link": "values.html#fls_6lg0oaaopc26", - "number": "7:10" - }, + "checksum": "cc35a963cac35f4ca35b83a2c99092463e9f24c42b164f201487fd81193c4138", + "id": "fls_hvkalvr4e2v0", + "link": "types-and-traits.html#fls_hvkalvr4e2v0", + "number": "4.11.2.2:14" + } + ], + "title": "Struct Type Representation" + }, + { + "id": "fls_cmq8ogs84ivh", + "informational": false, + "link": "types-and-traits.html#union-type-representation", + "number": "4.11.2.3", + "paragraphs": [ { - "checksum": "9fe88066ade0f4cbf2e51e42c37d28efcd55edf9ac6496bf9612d853c6d3c866", - "id": "fls_oqhQ62mDLckN", - "link": "values.html#fls_oqhQ62mDLckN", - "number": "7:11" + "checksum": "7acef59265b9dc0eb9021e3c4144a4243c0f3bcc0119c1cd151745c380b1bd69", + "id": "fls_opz1p1neldsg", + "link": "types-and-traits.html#fls_opz1p1neldsg", + "number": "4.11.2.3:1" }, { - "checksum": "fda28ff83dfbce718ca81156aaddbd799783c6778b78393fd26b1d31b8eff46f", - "id": "fls_uhwpuv6cx4ip", - "link": "values.html#fls_uhwpuv6cx4ip", - "number": "7:12" + "checksum": "151cb09b57de0325104c84d42d1aac2b8865f4dc1fdbf7fc0da0db0967ba3270", + "id": "fls_y5qtvbx5m90g", + "link": "types-and-traits.html#fls_y5qtvbx5m90g", + "number": "4.11.2.3:2" + } + ], + "title": "Union Type Representation" + }, + { + "id": "fls_j02707n615z0", + "informational": false, + "link": "types-and-traits.html#type-model", + "number": "4.12", + "paragraphs": [], + "title": "Type Model" + }, + { + "id": "fls_3gapgqys3ceb", + "informational": false, + "link": "types-and-traits.html#recursive-types", + "number": "4.12.1", + "paragraphs": [ + { + "checksum": "740cfb5bd47f58876ee5586174ee7d3acae1e4bdfac31a402c4461aafee6966f", + "id": "fls_z22std1crl49", + "link": "types-and-traits.html#fls_z22std1crl49", + "number": "4.12.1:1" }, { - "checksum": "470b62e28b525feb1985f8a6eb97b0b0ad3158a5c22541e4e07c02e9b1352f1a", - "id": "fls_xuuFKmm181bs", - "link": "values.html#fls_xuuFKmm181bs", - "number": "7:13" + "checksum": "3f12892eabfac9dd07124c6ca395e4ab531b1edab32e93dbf13a9236a105def9", + "id": "fls_eddnwlr0rz59", + "link": "types-and-traits.html#fls_eddnwlr0rz59", + "number": "4.12.1:2" } ], - "title": "Values" + "title": "Recursive Types" }, { - "id": "fls_ixjc5jaamx84", + "id": "fls_exe4zodlwfez", "informational": false, - "link": "values.html#constants", - "number": "7.1", + "link": "types-and-traits.html#type-unification", + "number": "4.12.2", "paragraphs": [ { - "checksum": "459c0a6560892364338ac1907aa3d368c9f21cf8310c9cc782a84f08d45f1ad4", - "id": "fls_5o5iu4j8in4l", - "link": "values.html#fls_5o5iu4j8in4l", - "number": "7.1:1" + "checksum": "5df68e7a28d0bf034a911d0f1ebabc63d83df1f144975513612741de324e087a", + "id": "fls_ryvdhkgm7vzj", + "link": "types-and-traits.html#fls_ryvdhkgm7vzj", + "number": "4.12.2:1" }, { - "checksum": "7c5a50f4fdf5e813f75ac692c03fc8f4b3ef0e7f8d0ee6e1de095293de8bfffb", - "id": "fls_3mhj0kkupwuz", - "link": "values.html#fls_3mhj0kkupwuz", - "number": "7.1:2" + "checksum": "8efb96431fa13308cf8430377b0cc06be4e7e65b313828a429bfd56b64fbb69b", + "id": "fls_67VZrx6dw68H", + "link": "types-and-traits.html#fls_67VZrx6dw68H", + "number": "4.12.2:2" }, { - "checksum": "4716f007f8ab2d62c80effe03cf31b1970b0da6143b16e60aed00dceda659994", - "id": "fls_ka4y2yd100dx", - "link": "values.html#fls_ka4y2yd100dx", - "number": "7.1:3" + "checksum": "2cefde97b95cdce903b2bb16f1b7f2bb843cd1947a51d99f03b6edc576e8a815", + "id": "fls_aie0tr62vhw5", + "link": "types-and-traits.html#fls_aie0tr62vhw5", + "number": "4.12.2:3" }, { - "checksum": "ff5ba133064323870b5d011bd88ce9eeacc9f115fdaa6244b44b18ee640a1431", - "id": "fls_vt9tlkd676ql", - "link": "values.html#fls_vt9tlkd676ql", - "number": "7.1:4" + "checksum": "a1074c8cfb8f682fa21dc2aa4c3d2b500a67a107b90901fd69a32bd3ea7f487d", + "id": "fls_3U7Ue6Xzuv9M", + "link": "types-and-traits.html#fls_3U7Ue6Xzuv9M", + "number": "4.12.2:4" }, { - "checksum": "ca441fbadf454d310fef0a445ecd27e40458052ffd2566f775fd1f5d49ab6961", - "id": "fls_ndmfqxjpvsqy", - "link": "values.html#fls_ndmfqxjpvsqy", - "number": "7.1:5" + "checksum": "930299ef90063f38b5762819de8b999c9d6d47c338fb35b2fea53ba6308b392c", + "id": "fls_tIiw5WkTRNf7", + "link": "types-and-traits.html#fls_tIiw5WkTRNf7", + "number": "4.12.2:5" }, { - "checksum": "195d2e43a2cb6b0474fb0ebaa8190f17c133efecdb317ae5ad83e396aaf05e0b", - "id": "fls_6rxwbbhf5tc5", - "link": "values.html#fls_6rxwbbhf5tc5", - "number": "7.1:6" + "checksum": "f1514e494d536e424cc53a6d932b2f0efeae9c0c1c9d38cdff61bcb32368c8be", + "id": "fls_EoEbldkKBQW0", + "link": "types-and-traits.html#fls_EoEbldkKBQW0", + "number": "4.12.2:6" }, { - "checksum": "ea8a4c823672702b1fff3e67be0d3834bfde988e46a4ab82202acfcc2d7b9898", - "id": "fls_vnc3ttnid1qr", - "link": "values.html#fls_vnc3ttnid1qr", - "number": "7.1:7" + "checksum": "9ce44d0db04150b232d3c66c01e88cb2a18d590a22685dfff432835fbeb60264", + "id": "fls_16ZDp8PaKi5P", + "link": "types-and-traits.html#fls_16ZDp8PaKi5P", + "number": "4.12.2:7" }, { - "checksum": "9590b37a5fb341bc26220cabf636eb0b9430c183eefc8266362a284afabdcf54", - "id": "fls_deuo1pn8cjd6", - "link": "values.html#fls_deuo1pn8cjd6", - "number": "7.1:8" + "checksum": "ca8ea0205df206f0258c062e6a5214044e0cea21feb89cea071059c7e56920ea", + "id": "fls_pGRLTACDvzv2", + "link": "types-and-traits.html#fls_pGRLTACDvzv2", + "number": "4.12.2:8" }, { - "checksum": "916f9839884f7b71825ecd5be8ae92f6b7ab789a916c47ba1a5f366bd8ac4382", - "id": "fls_xezt9hl069h4", - "link": "values.html#fls_xezt9hl069h4", - "number": "7.1:9" + "checksum": "5856553d7c3dc3037b3f61286c79224e0a36daaa9f1fc4dbb8cfa32293193d99", + "id": "fls_fTy3FVt0fK9g", + "link": "types-and-traits.html#fls_fTy3FVt0fK9g", + "number": "4.12.2:9" }, { - "checksum": "18ea98ed7ee862889d2d483387c3b1e28a42e39e9d60b7bbc3e65d9e4deff0a5", - "id": "fls_ndobth7s92if", - "link": "values.html#fls_ndobth7s92if", - "number": "7.1:10" - } - ], - "title": "Constants" - }, - { - "id": "fls_xdvdl2ssnhlo", - "informational": false, - "link": "values.html#statics", - "number": "7.2", - "paragraphs": [ + "checksum": "35b4e67328db231c512e327ce5efb8ce05016ea1da017f138f262e2e2a227d6d", + "id": "fls_7IsrfUoPXSZU", + "link": "types-and-traits.html#fls_7IsrfUoPXSZU", + "number": "4.12.2:10" + }, { - "checksum": "1fc950d02b5b3c40129159c65daf7d001e7f01521a71c3d8b8a2058dbe50c167", - "id": "fls_ibrmiwfypldh", - "link": "values.html#fls_ibrmiwfypldh", - "number": "7.2:1" + "checksum": "324899ccd2204072a372947b966fee99e3024465753dcf20070c2c483dac6cff", + "id": "fls_Hb95CPyUpCmc", + "link": "types-and-traits.html#fls_Hb95CPyUpCmc", + "number": "4.12.2:11" }, { - "checksum": "8c4334c679018baf9392151eb71d12964b85d09d0477dbea3910ea98adb3612f", - "id": "fls_mt94jvoot9dx", - "link": "values.html#fls_mt94jvoot9dx", - "number": "7.2:2" + "checksum": "b9d38d54656612478233c4d69d338dbc29e2284cc03c851ecf007157faabf4d9", + "id": "fls_jEZVWlfVPevb", + "link": "types-and-traits.html#fls_jEZVWlfVPevb", + "number": "4.12.2:12" }, { - "checksum": "5c1b55de587710b67a5131939b84300663f0c0699041210a7ce0779e6c1a0f65", - "id": "fls_k0r2c6uq29tu", - "link": "values.html#fls_k0r2c6uq29tu", - "number": "7.2:3" + "checksum": "e1774f66d7ec71606db10f7d2ea8bb346f4838230ccab723b87cc20531f9c9e8", + "id": "fls_nKcqFo7yIDBe", + "link": "types-and-traits.html#fls_nKcqFo7yIDBe", + "number": "4.12.2:13" }, { - "checksum": "db2fb6ad99eb58cdd6f4bdb9e2edeff34b4c9b422e25781877ba2d6267194842", - "id": "fls_b6ods85htuyn", - "link": "values.html#fls_b6ods85htuyn", - "number": "7.2:4" + "checksum": "fca446cea9b227ba6edbbd60b3d07b861687b7e6735b5d9430aad156d0c00cac", + "id": "fls_jkaiBnApJAtt", + "link": "types-and-traits.html#fls_jkaiBnApJAtt", + "number": "4.12.2:14" + }, + { + "checksum": "5c234eaaaf625cadcba23e792b9c70b2a5d5a4bf733d3d1b874363f6cb4b4c23", + "id": "fls_dhksyjrvx9a", + "link": "types-and-traits.html#fls_dhksyjrvx9a", + "number": "4.12.2:15" + }, + { + "checksum": "71c2ef4eca01d8d2398c416f696fa413d2aa384ac63d110cf9c02fee44394c17", + "id": "fls_hf0cfkrmt655", + "link": "types-and-traits.html#fls_hf0cfkrmt655", + "number": "4.12.2:16" + }, + { + "checksum": "688edd3c20f0031139a4a191d039bbc1cb4b3cf4e71953f32e59ca93e9e1eb50", + "id": "fls_k9dag68qpe93", + "link": "types-and-traits.html#fls_k9dag68qpe93", + "number": "4.12.2:17" + }, + { + "checksum": "000140bda99fc03cb461ae82fc44f00553e759bdefc4024a743226869bb556d9", + "id": "fls_m6d9qj9q9u1i", + "link": "types-and-traits.html#fls_m6d9qj9q9u1i", + "number": "4.12.2:18" + }, + { + "checksum": "e80dd4b7ce61eb512368a598539097c63700b957a79797c7632b07f1140b827a", + "id": "fls_gg3x25qvymmq", + "link": "types-and-traits.html#fls_gg3x25qvymmq", + "number": "4.12.2:19" }, { - "checksum": "1fc020e39e08ce4f2038eaa3162a6a3594444e03098c3fe4d3adc9059d7ac4e4", - "id": "fls_WRpcVF1fLEpr", - "link": "values.html#fls_WRpcVF1fLEpr", - "number": "7.2:5" + "checksum": "f24aed54e03e3a739813f0003dfe5ba61269ea0b335234f058a75c2258a3a969", + "id": "fls_ni296ev8x9v9", + "link": "types-and-traits.html#fls_ni296ev8x9v9", + "number": "4.12.2:20" }, { - "checksum": "61e4575688b23d6d7db99392e1b544003c08b7a838ac8fc1fdad3b1e44ad8ce6", - "id": "fls_doi4z6u55bi7", - "link": "values.html#fls_doi4z6u55bi7", - "number": "7.2:6" + "checksum": "ae9c6921bc2c5d6daea20516afa9d8caa4b58b39286ba4971eebcfd1ab4c0cd8", + "id": "fls_i1m41c4wkfc0", + "link": "types-and-traits.html#fls_i1m41c4wkfc0", + "number": "4.12.2:21" }, { - "checksum": "436a9f15d2686b36fb1f179b13f38d0cce872399fc6c8d997896182219ca9b12", - "id": "fls_74hp208pto22", - "link": "values.html#fls_74hp208pto22", - "number": "7.2:7" + "checksum": "e6f42ea036f99fbe3b1fb90f2cd2e17a9acf5ca1116043eca60f80c79867be2c", + "id": "fls_mpq64eal9jo3", + "link": "types-and-traits.html#fls_mpq64eal9jo3", + "number": "4.12.2:22" }, { - "checksum": "7594d6fedfa7f00a180909cf5235b4243c8be61026a444da258f79f104bdd93f", - "id": "fls_jfde2vg6mtww", - "link": "values.html#fls_jfde2vg6mtww", - "number": "7.2:8" + "checksum": "00d3c7f7b35c66e24e4a10adce31978ab8ad1bd234a231761dcb86dd442da7b3", + "id": "fls_kcr8npsmy0e5", + "link": "types-and-traits.html#fls_kcr8npsmy0e5", + "number": "4.12.2:23" }, { - "checksum": "ac1f8731f3004fb8442f085915588c449be8d285a8cae244ac37519247a147c9", - "id": "fls_k4tyqb1j6zjo", - "link": "values.html#fls_k4tyqb1j6zjo", - "number": "7.2:9" + "checksum": "195eb65f16abf0575d17f2b1be1087b9e206a49ffe16d3ffdef28cd89e788366", + "id": "fls_kq3lv1zbangz", + "link": "types-and-traits.html#fls_kq3lv1zbangz", + "number": "4.12.2:24" }, { - "checksum": "ae7960becaff9d4e85d3aa35ebbfdf4c482ac55566c5938d255ec72bb2cfe41e", - "id": "fls_t17h5h6a6v4c", - "link": "values.html#fls_t17h5h6a6v4c", - "number": "7.2:10" + "checksum": "1d9c72ecb4ac5ea7fc4fc3d2d75b5180c4f4a47db55dce93873e90df42e46743", + "id": "fls_so2cgqmawlm7", + "link": "types-and-traits.html#fls_so2cgqmawlm7", + "number": "4.12.2:25" }, { - "checksum": "74a8e2029bbf9068f3842d85c2c37aa40cf499e68bbff4aaad87bbcca3cddcff", - "id": "fls_yq0hpy4jx2qb", - "link": "values.html#fls_yq0hpy4jx2qb", - "number": "7.2:11" + "checksum": "408947791758b2ad8dcbc3ecdcf9c5f4c8644d67bdbff00b110458292dce4e83", + "id": "fls_vsax8w6y794m", + "link": "types-and-traits.html#fls_vsax8w6y794m", + "number": "4.12.2:26" }, { - "checksum": "d07bf654cb1e5a9429203f2ebf243ae97b0345768f8f570784f2e53ba60d1a81", - "id": "fls_vgidvfwzm4ks", - "link": "values.html#fls_vgidvfwzm4ks", - "number": "7.2:12" + "checksum": "0c359ddaaaf4bb7ecdeda5b1ed77c19699bd50b53c7c9c180ab91fec01d5755a", + "id": "fls_1j1wc3uxs7h6", + "link": "types-and-traits.html#fls_1j1wc3uxs7h6", + "number": "4.12.2:27" }, { - "checksum": "14ed04b36fe95b827d15d5985c20be8554b5d34cabcbdcff30e24ab1af96c712", - "id": "fls_8dcldbvu7lav", - "link": "values.html#fls_8dcldbvu7lav", - "number": "7.2:13" + "checksum": "6fed9a60e8fbe91b884ebe5950327802bbd098730e114fffad4de5c82403ecfe", + "id": "fls_9dpea9ty0c2l", + "link": "types-and-traits.html#fls_9dpea9ty0c2l", + "number": "4.12.2:28" }, { - "checksum": "0bef3205d769452568c2d9b7420b359b46261b2fd90918adcdf762a9b9d97651", - "id": "fls_w0nb0mphho7b", - "link": "values.html#fls_w0nb0mphho7b", - "number": "7.2:14" + "checksum": "8c89ff60eff543cbe33d8f313577bcc491d151988e3e14ec4a7c5f681d0fa8e2", + "id": "fls_42oj1ekjihq1", + "link": "types-and-traits.html#fls_42oj1ekjihq1", + "number": "4.12.2:29" }, { - "checksum": "c16b08cc3a5ca2e215cad78f4c6f8d9d9c03d11b6737b12ca2cf558d58a77b3e", - "id": "fls_eeocxst9vafn", - "link": "values.html#fls_eeocxst9vafn", - "number": "7.2:15" + "checksum": "c50b057136169daf940732bafe752d5fe2c443eb42159455d87b433a0de3e55a", + "id": "fls_gebpqqqvvklf", + "link": "types-and-traits.html#fls_gebpqqqvvklf", + "number": "4.12.2:30" }, { - "checksum": "8aa9153a03b15bd583210b61b2721907a729d0f7febdc4e7a31ff91f096d0e47", - "id": "fls_47khd5ljsxeq", - "link": "values.html#fls_47khd5ljsxeq", - "number": "7.2:16" + "checksum": "66ba5454612352fff011f2c5f16752b6851b3f672fadb2f8c15fb1c70eddb384", + "id": "fls_i221hm7rssik", + "link": "types-and-traits.html#fls_i221hm7rssik", + "number": "4.12.2:31" }, { - "checksum": "5a0bbb9142d384d7c9551f4e242a13e3d9a43d1496d68ca6b3f409ad6d7e8e7c", - "id": "fls_dowxbphqvk3n", - "link": "values.html#fls_dowxbphqvk3n", - "number": "7.2:17" + "checksum": "d54f897d06ae679945464622ed0a82379a5c0b60452ea600983b97b69902bbe5", + "id": "fls_74cug5zfv2wv", + "link": "types-and-traits.html#fls_74cug5zfv2wv", + "number": "4.12.2:32" }, { - "checksum": "ae3be52255b3602f2d17694a7c90103ed859c959d8b5e8b02d731dd9ee8ac70f", - "id": "fls_b5wsmii7vz3v", - "link": "values.html#fls_b5wsmii7vz3v", - "number": "7.2:18" - } - ], - "title": "Statics" - }, - { - "id": "fls_cleoffpn5ew6", - "informational": false, - "link": "values.html#temporaries", - "number": "7.3", - "paragraphs": [ - { - "checksum": "c643b7ee2f09d4d661349de57526a0d8f6ba3fa1f1d3cf25ce2abb1ca7ad5487", - "id": "fls_awpw61yofckz", - "link": "values.html#fls_awpw61yofckz", - "number": "7.3:1" - } - ], - "title": "Temporaries" - }, - { - "id": "fls_gho955gmob73", - "informational": false, - "link": "values.html#variables", - "number": "7.4", - "paragraphs": [ + "checksum": "c9cabad366daa8e46a924d711447aac73dc93e60621905f3877aa65fc1a0724b", + "id": "fls_keezxl8v4snf", + "link": "types-and-traits.html#fls_keezxl8v4snf", + "number": "4.12.2:33" + }, { - "checksum": "e459e293d2e06ba989b65a863e33bf3d1559d14ba80224385e6471d4187db194", - "id": "fls_hl5tnd9yy252", - "link": "values.html#fls_hl5tnd9yy252", - "number": "7.4:1" + "checksum": "104f0c1ee57173d019b23e0728dc5b6fb9bc04ec70b7666e5348f7dd366b45ed", + "id": "fls_wz2etmkpvxed", + "link": "types-and-traits.html#fls_wz2etmkpvxed", + "number": "4.12.2:34" }, { - "checksum": "a56061a790ee2cc114f7f43c891827153cacfdd9d5bbdee36e4db521b6d5fc2f", - "id": "fls_vgi0gh5zmoiu", - "link": "values.html#fls_vgi0gh5zmoiu", - "number": "7.4:2" + "checksum": "43358ae1fdf6051cf8ba48651a94f4d6b6e62ab05031e0b0e384a83724822952", + "id": "fls_rmqcbb5ja4va", + "link": "types-and-traits.html#fls_rmqcbb5ja4va", + "number": "4.12.2:35" }, { - "checksum": "317f39071a5788d82b4865c063b86490996160f886858c0f19fdba967e4204e8", - "id": "fls_81dlbula47nu", - "link": "values.html#fls_81dlbula47nu", - "number": "7.4:3" + "checksum": "db5b765c1b3a6fcb40b42cccb46ae92a512f298b4eca532573060af03314af81", + "id": "fls_uu8je75y5pss", + "link": "types-and-traits.html#fls_uu8je75y5pss", + "number": "4.12.2:36" }, { - "checksum": "08b9626baa3159ce1dfdeca36fdc64f24594ba26b6b57c9810b67ee7c84c26cb", - "id": "fls_3p0sb9ppmg3w", - "link": "values.html#fls_3p0sb9ppmg3w", - "number": "7.4:4" + "checksum": "f397d8cf8b8c7c3fbcc901785345c36b908996d3a6bd6464e4e20cc108523a56", + "id": "fls_oksjiq3nmq2k", + "link": "types-and-traits.html#fls_oksjiq3nmq2k", + "number": "4.12.2:37" }, { - "checksum": "13806969584a605aded5db6ac700acea4334fa75cce1409689f2cdc10b30c2c8", - "id": "fls_r9km9f969bu8", - "link": "values.html#fls_r9km9f969bu8", - "number": "7.4:5" + "checksum": "81ebbf38df3455de1ee80a6f1a2f412c8878762eb6b05285f16ed47c4a857592", + "id": "fls_52ymp79ert2", + "link": "types-and-traits.html#fls_52ymp79ert2", + "number": "4.12.2:38" }, { - "checksum": "4de69432bc744f7df3566698f62d36feec0eb8432fae9503c73491871c1858d3", - "id": "fls_g8etd5lsgn9j", - "link": "values.html#fls_g8etd5lsgn9j", - "number": "7.4:6" - } - ], - "title": "Variables" - }, - { - "id": "fls_wttihxen35as", - "informational": false, - "link": "values.html#constant-promotion", - "number": "7.4.1", - "paragraphs": [ + "checksum": "3318b943515a14fe6dbf55e7ec47613c778490dee645221932ecbb4716e25074", + "id": "fls_g2u1dfhphrrg", + "link": "types-and-traits.html#fls_g2u1dfhphrrg", + "number": "4.12.2:39" + }, { - "checksum": "2ab496292105588acffc417faa7257c109d7d2599887fea194b6276c78d7d9f9", - "id": "fls_udn9lyf3m0z6", - "link": "values.html#fls_udn9lyf3m0z6", - "number": "7.4.1:1" + "checksum": "7726d6710a5611f30a40c98174045b829d3f80a6b8d962dae4f64b76e83045a4", + "id": "fls_2xgq66qp3h95", + "link": "types-and-traits.html#fls_2xgq66qp3h95", + "number": "4.12.2:40" }, { - "checksum": "0802dcedbdb411093a11433793ec2d81046430a75caa08c88a0652e21e3f2b48", - "id": "fls_yvkdcs4pmxjf", - "link": "values.html#fls_yvkdcs4pmxjf", - "number": "7.4.1:2" + "checksum": "fd91364bed19d0afa9d73fb3c1658c025a696b873269013f813ec189d9214416", + "id": "fls_5dh8c5gg0hmk", + "link": "types-and-traits.html#fls_5dh8c5gg0hmk", + "number": "4.12.2:41" }, { - "checksum": "7495b43c987ba177e81ed96d19821da97b72ed887800433fa2c3b5d3899da923", - "id": "fls_n570za6a9nqd", - "link": "values.html#fls_n570za6a9nqd", - "number": "7.4.1:3" + "checksum": "6a5c53a38ac6502579f67680736ebeff29b1a0a8a697f84d17e10c795d1d4b7d", + "id": "fls_ismr7wwvek4q", + "link": "types-and-traits.html#fls_ismr7wwvek4q", + "number": "4.12.2:42" }, { - "checksum": "c27b3a87d8e551f6925166f12702de6c3b84e10e0560e1124e14b34b75af3571", - "id": "fls_tms5r9f5ogcb", - "link": "values.html#fls_tms5r9f5ogcb", - "number": "7.4.1:4" + "checksum": "b27ba69adda5e65b3928999f9748285a16bee769d0115e2a1aeb237b6e4dbc3a", + "id": "fls_x9933rjecrna", + "link": "types-and-traits.html#fls_x9933rjecrna", + "number": "4.12.2:43" }, { - "checksum": "4b1f204406987d2425c7aa65db5e4321ca60c66f8abe6f59d4b9c954b56369fc", - "id": "fls_bysv5r7iuf5j", - "link": "values.html#fls_bysv5r7iuf5j", - "number": "7.4.1:5" + "checksum": "e08654b77f30bda8b01a6f943f5f80454a4b1b139153afc6307396967f705b66", + "id": "fls_mizmcykgdisb", + "link": "types-and-traits.html#fls_mizmcykgdisb", + "number": "4.12.2:44" }, { - "checksum": "26b8f032dee42ea2be31ad066b7ea126c0a123b1038d4a62f29eb01d10f33637", - "id": "fls_3h5vr7xk2rrt", - "link": "values.html#fls_3h5vr7xk2rrt", - "number": "7.4.1:6" + "checksum": "72049284c54176fc4fb1c1c255561eb7d7942a71235fa68202ca2bb3d8b79613", + "id": "fls_paoh0wttde2z", + "link": "types-and-traits.html#fls_paoh0wttde2z", + "number": "4.12.2:45" }, { - "checksum": "75d27ef13729037fd83b4022475756231b8ae7d7944805b47de72178a03c8b1a", - "id": "fls_3BGncWvMumEt", - "link": "values.html#fls_3BGncWvMumEt", - "number": "7.4.1:7" + "checksum": "5d19ac9384672776d1ee638f44540b560d0f10f40289e94044bc9fa144c252a9", + "id": "fls_akko4dmp4nkw", + "link": "types-and-traits.html#fls_akko4dmp4nkw", + "number": "4.12.2:46" }, { - "checksum": "d4d542c7748a3c35fbf6025eac3cfaadc015af1b45fc042ee9cdfe0b8b09c0db", - "id": "fls_m690b8qg9d9r", - "link": "values.html#fls_m690b8qg9d9r", - "number": "7.4.1:8" + "checksum": "138bb93829e388cb6e4f30afcc6d8172dbc34098c101c23ea3024f033ed853c7", + "id": "fls_8gldjjxbyyb4", + "link": "types-and-traits.html#fls_8gldjjxbyyb4", + "number": "4.12.2:47" }, { - "checksum": "8f845fa5e99e7c007d619bef33689d1b260b69d4631f9648c454ffebb40c4360", - "id": "fls_uf0sg25awre6", - "link": "values.html#fls_uf0sg25awre6", - "number": "7.4.1:9" + "checksum": "35b9f5170166cb82091a22696f9f5551aeeb153b146a6a8cc6fd0ec0f69ba070", + "id": "fls_8jad1ztcuxha", + "link": "types-and-traits.html#fls_8jad1ztcuxha", + "number": "4.12.2:48" }, { - "checksum": "b6c301614db85e9f9a3aa268b8e6ec7397cfd563b3d321bedae74884809dc6ab", - "id": "fls_o7cqfdnr253y", - "link": "values.html#fls_o7cqfdnr253y", - "number": "7.4.1:10" + "checksum": "474c0b6fc42aad94745322806f94d0baac7f6f82466ba9b20bb757aeaf8f0245", + "id": "fls_j3w9ap9zaqud", + "link": "types-and-traits.html#fls_j3w9ap9zaqud", + "number": "4.12.2:49" }, { - "checksum": "3bb8d78cd408d9dd893e63b174a91171c065d65c7708b50a9d029c3bff7c32b1", - "id": "fls_ap85svxyuhvg", - "link": "values.html#fls_ap85svxyuhvg", - "number": "7.4.1:11" - } - ], - "title": "Constant Promotion" - } - ], - "title": "Values" - }, - { - "informational": false, - "link": "statements.html", - "sections": [ - { - "id": "fls_wdicg3sqa98e", - "informational": false, - "link": "statements.html", - "number": "8", - "paragraphs": [ - { - "checksum": "03f55779646bda5f8b813a0bda5a8000aaa7a3f7df2147c58d37a6a19d213825", - "id": "fls_7zh6ziglo5iy", - "link": "statements.html#fls_7zh6ziglo5iy", - "number": "8:1" + "checksum": "f77f51ab20e49154362e7e30356fa9048d737311587b745a4c109b6adfbd0774", + "id": "fls_yvllot5qnc4s", + "link": "types-and-traits.html#fls_yvllot5qnc4s", + "number": "4.12.2:50" }, { - "checksum": "857eb6f6081f30de98150db89a25a25467c0e180b1c3eb19f2786475a5a6e424", - "id": "fls_kdxe1ukmgl1", - "link": "statements.html#fls_kdxe1ukmgl1", - "number": "8:2" + "checksum": "eab384e3f2e76b1ac0fe9ec7ac7cf230e58b0f6069105b253a18e5a57cf0f62c", + "id": "fls_hza5n5eb18ta", + "link": "types-and-traits.html#fls_hza5n5eb18ta", + "number": "4.12.2:51" }, { - "checksum": "1ea74f2f77df136dd43cf45552f12fb2f6c366bcf10505917c84e5c9f9fd5fd8", - "id": "fls_fftdnwe22xrb", - "link": "statements.html#fls_fftdnwe22xrb", - "number": "8:3" + "checksum": "a08b517ccebf64dfb28aba3e871021511369da1be5e56cff2589a3924b3b9d1b", + "id": "fls_ww16urcjrj6i", + "link": "types-and-traits.html#fls_ww16urcjrj6i", + "number": "4.12.2:52" }, { - "checksum": "36cd13cda4653f00db550d04d91838c3dc10bfcbbf15d67f8ba19c0d5ddc297b", - "id": "fls_or125cqtxg9j", - "link": "statements.html#fls_or125cqtxg9j", - "number": "8:4" + "checksum": "89b48d93f4666e6e771a2628649e81f8d1f2de995e07defb06fff0282cffe991", + "id": "fls_bnp6or49voxp", + "link": "types-and-traits.html#fls_bnp6or49voxp", + "number": "4.12.2:53" }, { - "checksum": "82751916e76e8a528dd974fcf8f394553a002d42480c43ef8ca7bd23dec0d51a", - "id": "fls_estqu395zxgk", - "link": "statements.html#fls_estqu395zxgk", - "number": "8:5" + "checksum": "203ad82032af14af279f43593fcd8fccfc929c8078778a6de310ca6d1f78e9f8", + "id": "fls_hdo4c849q3lo", + "link": "types-and-traits.html#fls_hdo4c849q3lo", + "number": "4.12.2:54" }, { - "checksum": "085c0b250339d5488037ef839a9f6dd3672026ab75232f7a43c422bca728726c", - "id": "fls_dl763ssb54q1", - "link": "statements.html#fls_dl763ssb54q1", - "number": "8:6" + "checksum": "94dbd24c5170da391b4fd6bf809cc9f11e71dac5c7534862f9d4dcbae162ccb1", + "id": "fls_w9dx5h7m31sj", + "link": "types-and-traits.html#fls_w9dx5h7m31sj", + "number": "4.12.2:55" } ], - "title": "Statements" + "title": "Type Unification" }, { - "id": "fls_yivm43r5wnp1", + "id": "fls_dw33yt5g6m0k", "informational": false, - "link": "statements.html#let-statements", - "number": "8.1", + "link": "types-and-traits.html#type-coercion", + "number": "4.12.3", "paragraphs": [ { - "checksum": "fa270e107510c6e1cbbd96841a51e304e617eb94f030aaa20c17913ff260d725", - "id": "fls_ct7pp7jnfr86", - "link": "statements.html#fls_ct7pp7jnfr86", - "number": "8.1:1" - }, - { - "checksum": "d10976aa00e76b2bd9ae4e6118e2bb91d70d4093d2416f3c38179f5e2786f112", - "id": "fls_SR3dIgR5K0Kq", - "link": "statements.html#fls_SR3dIgR5K0Kq", - "number": "8.1:2" - }, - { - "checksum": "be1f15f55c52d38164795fc8324e4f7a5b3ca997e5de0de3c0c4ebe283e2d68e", - "id": "fls_iqar7vvtw22c", - "link": "statements.html#fls_iqar7vvtw22c", - "number": "8.1:3" + "checksum": "1c125d2f970819ac01d209699beabb24a85dcf91316e31ca2e951e98dc4a5f2e", + "id": "fls_w5pjcj9qmgbv", + "link": "types-and-traits.html#fls_w5pjcj9qmgbv", + "number": "4.12.3:1" }, { - "checksum": "9e23e5768ee96bddd83192456f161ce9e1ff1c043dfeb8c0a8d59713108c1f50", - "id": "fls_1s1UikGU5YQb", - "link": "statements.html#fls_1s1UikGU5YQb", - "number": "8.1:4" + "checksum": "b3d69f435190dd57fd86cebc434e29483825d6b25b8f60120e3477fef36b44cd", + "id": "fls_5v0n2a32bk95", + "link": "types-and-traits.html#fls_5v0n2a32bk95", + "number": "4.12.3:2" }, { - "checksum": "925e4f38b1fdb1037706134fa36069b0e1bf99946535bcf9436afefb7f125751", - "id": "fls_iB25BeFys0j8", - "link": "statements.html#fls_iB25BeFys0j8", - "number": "8.1:5" + "checksum": "b18d8bddfde5ee8721d48f3d414b5a55dfb48c98bf03d2412a1ab54ae0f93c30", + "id": "fls_j3kbaf43sgpj", + "link": "types-and-traits.html#fls_j3kbaf43sgpj", + "number": "4.12.3:3" }, { - "checksum": "65cba8734e75668ea9ff7e09a1d7c86536c79970652b19d3888805207b40c3b2", - "id": "fls_zObyLdya4DYc", - "link": "statements.html#fls_zObyLdya4DYc", - "number": "8.1:6" + "checksum": "4e13d35d1de469cd6adacba64653a8dc3a8a58eb96060d9dc2fa3c06a4248a7f", + "id": "fls_wxrugvlazy6v", + "link": "types-and-traits.html#fls_wxrugvlazy6v", + "number": "4.12.3:4" }, { - "checksum": "14989244832f8c7b3fd3884340df2f4df49e5f8442fe0aa7848cc011cb94a6f6", - "id": "fls_r38TXWKQPjxv", - "link": "statements.html#fls_r38TXWKQPjxv", - "number": "8.1:7" + "checksum": "dcd00eff58f1f029b5a359bbdfebe7a52a9dda64e77847b71c38aa9ee577082b", + "id": "fls_bhzmble1itog", + "link": "types-and-traits.html#fls_bhzmble1itog", + "number": "4.12.3:5" }, { - "checksum": "1635f6e94205f0755cc45a4da9f7272940034fa7496b94ccdff8a2284b0b8e38", - "id": "fls_6QSdwF4pzjoe", - "link": "statements.html#fls_6QSdwF4pzjoe", - "number": "8.1:8" + "checksum": "2849e3f04859c16c3f699d20cb59e697e2d5ea761ef414a8e9f6a4bfebdad8a2", + "id": "fls_eu4bt3dw1b8c", + "link": "types-and-traits.html#fls_eu4bt3dw1b8c", + "number": "4.12.3:6" }, { - "checksum": "6e23f656fcfe54a647a2278c5484a3fc80ed08112e18b6d4d6a5f11a8c2f6b5f", - "id": "fls_1prqh1trybwz", - "link": "statements.html#fls_1prqh1trybwz", - "number": "8.1:9" + "checksum": "14a2ebfc0dc84e45abcd936047fdf03858f8cfa416f4c711cdb673c3029f2a1d", + "id": "fls_apstt4elv2k7", + "link": "types-and-traits.html#fls_apstt4elv2k7", + "number": "4.12.3:7" }, { - "checksum": "271df8c51d41c031901cb4d22bea984e6f6a8e260e878213b8ed7c580900eabc", - "id": "fls_djkm8r2iuu6u", - "link": "statements.html#fls_djkm8r2iuu6u", - "number": "8.1:10" + "checksum": "853017badc80ef7625757aa8b68a0cbf76baed161093698d90b35416d3ff25a5", + "id": "fls_sp794uzfiofr", + "link": "types-and-traits.html#fls_sp794uzfiofr", + "number": "4.12.3:8" }, { - "checksum": "5c3b2ab8a0b6d48dff5c50e3ca9743ba453c77aadcaa49d6a9485a5fc393c244", - "id": "fls_ppj9gvhp8wcj", - "link": "statements.html#fls_ppj9gvhp8wcj", - "number": "8.1:11" + "checksum": "1a7266235db01fc19f566dc6970836b039c783ca94c394b07d6f46f45a327bae", + "id": "fls_xfqny6bwzsu9", + "link": "types-and-traits.html#fls_xfqny6bwzsu9", + "number": "4.12.3:9" }, { - "checksum": "9fc5558523a5dc96084b645182a9748a4ea46e67b31afc2341107cee0b0121aa", - "id": "fls_1eBQDZdBuDsN", - "link": "statements.html#fls_1eBQDZdBuDsN", - "number": "8.1:12" + "checksum": "b49cdf0f2fb74d20d8bf9bd22739caf559f092b37874cc469a1fc11796b0f3d7", + "id": "fls_u0e42y7nvn7e", + "link": "types-and-traits.html#fls_u0e42y7nvn7e", + "number": "4.12.3:10" }, { - "checksum": "99c4feb3265bf0e6b3a0c606397811171dba2b964d2471c4ccefa87af19ab501", - "id": "fls_m8a7gesa4oim", - "link": "statements.html#fls_m8a7gesa4oim", - "number": "8.1:13" + "checksum": "e94e4fd5d3c8124476595c77485d16348c94894082857326c18e6414d7e2ead0", + "id": "fls_p8hp5y506nam", + "link": "types-and-traits.html#fls_p8hp5y506nam", + "number": "4.12.3:11" }, { - "checksum": "ed71cf991f9a7542c7a7106e4754bb01cdd386dcf21b1c27e7f21519ac4a12cc", - "id": "fls_oaxnre7m9s10", - "link": "statements.html#fls_oaxnre7m9s10", - "number": "8.1:14" + "checksum": "973dbaecfc13fb4ab65e325a94d44a45fc9c953dda41203951b7614d02670864", + "id": "fls_fjc9xev8rcu6", + "link": "types-and-traits.html#fls_fjc9xev8rcu6", + "number": "4.12.3:12" }, { - "checksum": "24c8c95a02e8ee0e3828c7b00c71b666881c4ac437e3c018d06b7d83ad58b70d", - "id": "fls_t5bjwluyv8za", - "link": "statements.html#fls_t5bjwluyv8za", - "number": "8.1:15" + "checksum": "8c78c768eeb8f1a02a7d182fed23128f8bc7bbf0ddc102ba2da4c71a08098ee9", + "id": "fls_n1kh3z8d4q8y", + "link": "types-and-traits.html#fls_n1kh3z8d4q8y", + "number": "4.12.3:13" }, { - "checksum": "017c4fc4f03e296f8d96a0a62fc006c647bf0a9faaa94ad561061c93e9402928", - "id": "fls_4j9riqyf4p9", - "link": "statements.html#fls_4j9riqyf4p9", - "number": "8.1:16" + "checksum": "f631f3eaa473d36dc8b906892fb02d2afdfcc6ac8b36a88720bb9cbe268707cf", + "id": "fls_dgoypa3hcxc0", + "link": "types-and-traits.html#fls_dgoypa3hcxc0", + "number": "4.12.3:14" }, { - "checksum": "ab96a34396479306ef71b507703ffdd2fe9dd68512c87538bc972ad910a8d508", - "id": "fls_t53g5hlabqw1", - "link": "statements.html#fls_t53g5hlabqw1", - "number": "8.1:17" + "checksum": "af9ac95087820df12145d4b1bcdc5c2aefdfe7e0661d4ee8ebce4897d946cc54", + "id": "fls_h8dkehit8rza", + "link": "types-and-traits.html#fls_h8dkehit8rza", + "number": "4.12.3:15" }, { - "checksum": "3c6ccdcf4fec0d1389605503a83c401562b7c707fe92ad33814c8cd10b1d3b1e", - "id": "fls_7j4qlwg72ege", - "link": "statements.html#fls_7j4qlwg72ege", - "number": "8.1:18" + "checksum": "71e522755dbc28be6cd7a324a6a6454114e8e0244de6bbff1a5f0cb0ccb88cd2", + "id": "fls_z00wtlna6grk", + "link": "types-and-traits.html#fls_z00wtlna6grk", + "number": "4.12.3:16" }, { - "checksum": "14d436071a7dda876a858e8877ad564c71299dacb6239d9bff7ea3299d8d6a65", - "id": "fls_ea9bRFZjH8Im", - "link": "statements.html#fls_ea9bRFZjH8Im", - "number": "8.1:19" - } - ], - "title": "Let Statements" - }, - { - "id": "fls_1pg5ig740tg1", - "informational": false, - "link": "statements.html#expression-statements", - "number": "8.2", - "paragraphs": [ + "checksum": "3e2e8a463e825e0de103643132d6ac576478740d745d5bfada48884bf6a73f0f", + "id": "fls_rfjdh79k0wou", + "link": "types-and-traits.html#fls_rfjdh79k0wou", + "number": "4.12.3:17" + }, { - "checksum": "990f51f353986bd884828681d0f82474e27e8ff3042cd1c0b1908f048e7c7315", - "id": "fls_xmdj8uj7ixoe", - "link": "statements.html#fls_xmdj8uj7ixoe", - "number": "8.2:1" + "checksum": "760728def307e32ecd93826afc55eb5b39477adab8e2fdd6b8d2c3445e9c6c6b", + "id": "fls_e3lgrtqb7jwe", + "link": "types-and-traits.html#fls_e3lgrtqb7jwe", + "number": "4.12.3:18" }, { - "checksum": "0c5f0f107d5b1e196ef1a3003b0eb3d235bc6cbebd98a9537e5a9caf9df0c146", - "id": "fls_gzzmudc1hl6s", - "link": "statements.html#fls_gzzmudc1hl6s", - "number": "8.2:2" + "checksum": "b9ab7cd8a6ff174f7370bef719769860ab9bb78d73a887a2d266b10b9f0e356d", + "id": "fls_fwy2z11c1sji", + "link": "types-and-traits.html#fls_fwy2z11c1sji", + "number": "4.12.3:19" }, { - "checksum": "28b62503095184fa5c0dc9898c27c50ecaeebfef606f663370bdcaefa1c68cea", - "id": "fls_kc99n8qrszxh", - "link": "statements.html#fls_kc99n8qrszxh", - "number": "8.2:3" + "checksum": "34586dbc5392132b773e0f3aa90c23b3f4079ca79bedb9d108b354e0c15a9584", + "id": "fls_aujb44849tq1", + "link": "types-and-traits.html#fls_aujb44849tq1", + "number": "4.12.3:20" }, { - "checksum": "f02ee18552936ea98c3764181f5651c46b54e5af37f3149b17bd6906ecb33556", - "id": "fls_r8poocwqaglf", - "link": "statements.html#fls_r8poocwqaglf", - "number": "8.2:4" + "checksum": "b048851b08b58ee5809efccef3bf5a7a5207344b662eba5162de24e194352b83", + "id": "fls_p3ym3ycrnd5m", + "link": "types-and-traits.html#fls_p3ym3ycrnd5m", + "number": "4.12.3:21" }, { - "checksum": "cf7b0c182db5007a86bf5c05bdfb68e63de338e95ef2e377bafa1708a7c00738", - "id": "fls_88e6s3erk8tj", - "link": "statements.html#fls_88e6s3erk8tj", - "number": "8.2:5" + "checksum": "1b30164bd90c0dbbcb47c87803c0841dfe9d3d772e1a34c7a9ccc4a4933b2c10", + "id": "fls_jmo42qgix5uw", + "link": "types-and-traits.html#fls_jmo42qgix5uw", + "number": "4.12.3:22" }, { - "checksum": "fe5fb38b0a811437c9b5e6e9f70af727c9013f79573eee3673146cf030c660c4", - "id": "fls_4q90jb39apwr", - "link": "statements.html#fls_4q90jb39apwr", - "number": "8.2:6" + "checksum": "c1dda665688283c6458e52d3c3a8e7387bab58a818e1c1b871485a8e7d6c068c", + "id": "fls_tbt4236igdzb", + "link": "types-and-traits.html#fls_tbt4236igdzb", + "number": "4.12.3:23" }, { - "checksum": "737bef4656101e6322af305e26b7c23b0c56fe07e9b5404ed0fe6daff16a8d02", - "id": "fls_xqtztcu8ibwq", - "link": "statements.html#fls_xqtztcu8ibwq", - "number": "8.2:7" + "checksum": "e789228b8563894bad59c2a9870a4324c5fde44cc790f00e4a45a0b465c8316a", + "id": "fls_7ri4jk2dydfn", + "link": "types-and-traits.html#fls_7ri4jk2dydfn", + "number": "4.12.3:24" }, { - "checksum": "93c30bb9a1f379d6fce63a4b7aded720c5328d70432fdb6c05a7aa2312426a73", - "id": "fls_2p9xnt519nbw", - "link": "statements.html#fls_2p9xnt519nbw", - "number": "8.2:8" - } - ], - "title": "Expression Statements" - } - ], - "title": "Statements" - }, - { - "informational": false, - "link": "unsafety.html", - "sections": [ - { - "id": "fls_jep7p27kaqlp", - "informational": false, - "link": "unsafety.html", - "number": "19", - "paragraphs": [ + "checksum": "fc7f331b6089c5bd8e347504d88e62078d5c4c846a9ccf65eaeedd8ffc094c48", + "id": "fls_6r3kn0nk5b8o", + "link": "types-and-traits.html#fls_6r3kn0nk5b8o", + "number": "4.12.3:25" + }, { - "checksum": "09ef0477163f852006ab72daea7582f28fd5b47b9ec3a4188da6a52a06c1cbb9", - "id": "fls_8kqo952gjhaf", - "link": "unsafety.html#fls_8kqo952gjhaf", - "number": "19:1" + "checksum": "61b8468e184c14f1902fbd43e6492e9b4640ddad4ae0a33a731901b147214f58", + "id": "fls_ulcdetwp6x96", + "link": "types-and-traits.html#fls_ulcdetwp6x96", + "number": "4.12.3:26" }, { - "checksum": "72911c6efacc1ea81b757fea368ab51b658c5f44690af8bb4bf5076196f02dba", - "id": "fls_ovn9czwnwxue", - "link": "unsafety.html#fls_ovn9czwnwxue", - "number": "19:2" + "checksum": "e233c03b13e1a8e36e57cca5a910344a93904dc508dbe3d5823adb57c58fdaea", + "id": "fls_2uv1r0gni1fk", + "link": "types-and-traits.html#fls_2uv1r0gni1fk", + "number": "4.12.3:27" }, { - "checksum": "ac90013bdfea2c44c2ea16d0d92eb8b7a3b32f33cdb44553018c9de58de2d0ab", - "id": "fls_pfhmcafsjyf7", - "link": "unsafety.html#fls_pfhmcafsjyf7", - "number": "19:3" + "checksum": "fde902a0dcf7245891be85fe02b53a36f549f906b5621552b11c7c0e83e5b3ad", + "id": "fls_sf0c3fbx8z57", + "link": "types-and-traits.html#fls_sf0c3fbx8z57", + "number": "4.12.3:28" }, { - "checksum": "509a721d7a591aac4ddac02ca26ef6fc26e0ae40b040578d4282304dbef76b45", - "id": "fls_jd1inwz7ulyw", - "link": "unsafety.html#fls_jd1inwz7ulyw", - "number": "19:4" + "checksum": "2c0ca29375ecee34ce0749c8579610946e907e1988ceded9635c2d2ab4bc44ec", + "id": "fls_SYnFJBhi0IWj", + "link": "types-and-traits.html#fls_SYnFJBhi0IWj", + "number": "4.12.3:29" }, { - "checksum": "9efb922ed5200526914bd3f95ddb869b8e555bd8367143db358605307ff7ea93", - "id": "fls_3ra8s1v1vbek", - "link": "unsafety.html#fls_3ra8s1v1vbek", - "number": "19:5" + "checksum": "a80ed17718eb533324e24a46fc43672df7763fc96f271b8bccd3fab1d963312b", + "id": "fls_QB4c6FNKxaPl", + "link": "types-and-traits.html#fls_QB4c6FNKxaPl", + "number": "4.12.3:30" }, { - "checksum": "1b9334ca686a2f78808b0a5e1bf0b6793eef547e9367cf94416bb6a4009457bc", - "id": "fls_6ipl0xo5qjyl", - "link": "unsafety.html#fls_6ipl0xo5qjyl", - "number": "19:6" + "checksum": "dd7bc2c44f433ffd630cffd6c4386dd7fbd5fa2ce7379e469299b9dc5c179d09", + "id": "fls_iiiu2q7pym4p", + "link": "types-and-traits.html#fls_iiiu2q7pym4p", + "number": "4.12.3:31" }, { - "checksum": "08016621bfd496b3d4c162d71caf6c3033c213bf9b52662adf1137b696285b4b", - "id": "fls_ucghxcnpaq2t", - "link": "unsafety.html#fls_ucghxcnpaq2t", - "number": "19:7" + "checksum": "8e785eaf45c0dfef77ee6ff7b317a82aaf46b49c84f4c2ecdc296ee62568c727", + "id": "fls_jte6n2js32af", + "link": "types-and-traits.html#fls_jte6n2js32af", + "number": "4.12.3:32" }, { - "checksum": "7740b81deb4cc8d3a5d63073faa941c897de8931c90af82f402d60d8f5cf7ad6", - "id": "fls_ljocmnaz2m49", - "link": "unsafety.html#fls_ljocmnaz2m49", - "number": "19:8" + "checksum": "a9d2363147bd3d1382fb2792473844fa7abd6d6fb6aa67371d82a7ede1452d39", + "id": "fls_20pvqqayzqra", + "link": "types-and-traits.html#fls_20pvqqayzqra", + "number": "4.12.3:33" }, { - "checksum": "bb058285e28804f8335348bcfa3c28e0f032f4346bc2aaf52a67145036233241", - "id": "fls_s5nfhBFOk8Bu", - "link": "unsafety.html#fls_s5nfhBFOk8Bu", - "number": "19:9" + "checksum": "65c7ca810e8b17133d894175777f2b3b7bc290fcd174dc128bc978c1bbf10464", + "id": "fls_j8rcy0xvd155", + "link": "types-and-traits.html#fls_j8rcy0xvd155", + "number": "4.12.3:34" }, { - "checksum": "14f513b2d197bbf061de106591d50be0e1b5efdd28b7b73e3c01a1fd27dc914d", - "id": "fls_jb6krd90tjmc", - "link": "unsafety.html#fls_jb6krd90tjmc", - "number": "19:10" + "checksum": "1af2a2bba8399300cc1e205f0f7fee9618593ec4a2537211d6ba2bac7ca92379", + "id": "fls_wuka4uyo3oj7", + "link": "types-and-traits.html#fls_wuka4uyo3oj7", + "number": "4.12.3:35" }, { - "checksum": "c476883b58a5e4480fa0505d41dc097727812f40edf56fd7a46f4fd3595ee2f3", - "id": "fls_ybnpe7ppq1vh", - "link": "unsafety.html#fls_ybnpe7ppq1vh", - "number": "19:11" - } - ], - "title": "Unsafety" - } - ], - "title": "Unsafety" - }, - { - "informational": false, - "link": "program-structure-and-compilation.html", - "sections": [ - { - "id": "fls_hdwwrsyunir", - "informational": false, - "link": "program-structure-and-compilation.html", - "number": "18", - "paragraphs": [], - "title": "Program Structure and Compilation" - }, - { - "id": "fls_s35hob3i7lr", - "informational": false, - "link": "program-structure-and-compilation.html#source-files", - "number": "18.1", - "paragraphs": [ + "checksum": "8322e0c9269a64e591836ee24ecb8d377ce3093892b78053bacd698ed55a8962", + "id": "fls_w15yo8yvuxq3", + "link": "types-and-traits.html#fls_w15yo8yvuxq3", + "number": "4.12.3:36" + }, { - "checksum": "55f6d3282e08235092b6157374cbc8fad302ec5965229b94dca69fbf5a239934", - "id": "fls_4vicosdeaqmp", - "link": "program-structure-and-compilation.html#fls_4vicosdeaqmp", - "number": "18.1:1" + "checksum": "dcbd46a0126cc58845c6cb40ab6afc0aea44dac093a6f13bfa4fcf14b4b395fe", + "id": "fls_7aw3ifbvfgbd", + "link": "types-and-traits.html#fls_7aw3ifbvfgbd", + "number": "4.12.3:37" }, { - "checksum": "7555cb7065456e8c9abbac7b69fc1d5e7a6e631762739806e629635a718a19ac", - "id": "fls_ann3cha1xpek", - "link": "program-structure-and-compilation.html#fls_ann3cha1xpek", - "number": "18.1:2" - } - ], - "title": "Source Files" - }, - { - "id": "fls_e9hwvqsib5d5", - "informational": false, - "link": "program-structure-and-compilation.html#modules", - "number": "18.2", - "paragraphs": [ + "checksum": "89cc90acc65cd37645feae640026726577f4f4f0349a887ff00a451479f45b0a", + "id": "fls_cnkth59djwgl", + "link": "types-and-traits.html#fls_cnkth59djwgl", + "number": "4.12.3:38" + }, { - "checksum": "ec4163446ba1d6053faea2c9be55ae44b49f9e489b2fb4266106708fe16bdda3", - "id": "fls_odd1hj3y1mgu", - "link": "program-structure-and-compilation.html#fls_odd1hj3y1mgu", - "number": "18.2:1" + "checksum": "afc1ce7f88cf8abf3164653eb9fd348bd326ebc15ebf3037c655e1c0494c89ad", + "id": "fls_4wbk7pqj010i", + "link": "types-and-traits.html#fls_4wbk7pqj010i", + "number": "4.12.3:39" }, { - "checksum": "a552ab77145836cc109d96b376f5e785089d1f7d95d6d51c25ecfddb3708b512", - "id": "fls_whgv72emrm47", - "link": "program-structure-and-compilation.html#fls_whgv72emrm47", - "number": "18.2:2" + "checksum": "cdd6b9592887af36d0772fde53ca3091c4a6bf70805ffbb04a1dbb16ef4d92ff", + "id": "fls_47u0039t0l8f", + "link": "types-and-traits.html#fls_47u0039t0l8f", + "number": "4.12.3:40" }, { - "checksum": "1233b4977ef4e5fd6c5969530904ff70690f361cc5fccddbf8952e1cc7d299f8", - "id": "fls_qypjjpcf8uwq", - "link": "program-structure-and-compilation.html#fls_qypjjpcf8uwq", - "number": "18.2:3" + "checksum": "cefece0d02fa276fe12b8d7fa02401d8fa6a73dd827f6c8960ef8097b6ff375f", + "id": "fls_bmh6g3jju7eq", + "link": "types-and-traits.html#fls_bmh6g3jju7eq", + "number": "4.12.3:41" + }, + { + "checksum": "698fb05a30487c47e16c1814bf5989a6201253e9f222c440fd59e3d8cfabbede", + "id": "fls_da4w32rsrwxc", + "link": "types-and-traits.html#fls_da4w32rsrwxc", + "number": "4.12.3:42" }, { - "checksum": "f54b92d3867600666f831b9a1ca07de6acba61ac72937335bd0451a3de4cbb27", - "id": "fls_cavwpr1ybk37", - "link": "program-structure-and-compilation.html#fls_cavwpr1ybk37", - "number": "18.2:4" + "checksum": "8333e75b01db4b4778f0b23b7e145da87834bc47ac828831f781b14f6b7830c1", + "id": "fls_zi5311z1w7re", + "link": "types-and-traits.html#fls_zi5311z1w7re", + "number": "4.12.3:43" }, { - "checksum": "ddcd4deef3d419dcbf8b2c18aaa5d8bb453229bce34e4d7180d9e8f93d2bf303", - "id": "fls_plepew2319g4", - "link": "program-structure-and-compilation.html#fls_plepew2319g4", - "number": "18.2:5" + "checksum": "3ef2c70df3fbc39e3d8fe5bbe8c0e1adbf015005169243e1455458fed8ded361", + "id": "fls_zst5pa29rpt", + "link": "types-and-traits.html#fls_zst5pa29rpt", + "number": "4.12.3:44" }, { - "checksum": "fa07c91c0b0de63411270c072bdc923db3f1db860082d3eddad31ca6eed189c5", - "id": "fls_1aruwps62c4p", - "link": "program-structure-and-compilation.html#fls_1aruwps62c4p", - "number": "18.2:6" - } - ], - "title": "Modules" - }, - { - "id": "fls_maw4u1o8q37u", - "informational": false, - "link": "program-structure-and-compilation.html#crates", - "number": "18.3", - "paragraphs": [ + "checksum": "f4b1e50283ac71505aad22e917efae5d7d1d0798dbdf1fbe9d80f65bb04514a7", + "id": "fls_agw1aej616vf", + "link": "types-and-traits.html#fls_agw1aej616vf", + "number": "4.12.3:45" + }, { - "checksum": "c64331512493388d716b5e98082811475682dcffee41348b82483eb48cd91fc5", - "id": "fls_qwghk79ok5h0", - "link": "program-structure-and-compilation.html#fls_qwghk79ok5h0", - "number": "18.3:1" + "checksum": "bb81f4f7bb91b154fff3ff42309dea91e61e5a2ced8910f2543d14f89bf79a19", + "id": "fls_tnbga5dl4gz8", + "link": "types-and-traits.html#fls_tnbga5dl4gz8", + "number": "4.12.3:46" }, { - "checksum": "076cdc6ed8a203ec16636f7d7a4f7bfc8fcf4ad833ca2a5eb22898fa6eeb576c", - "id": "fls_unxalgMqIr3v", - "link": "program-structure-and-compilation.html#fls_unxalgMqIr3v", - "number": "18.3:2" + "checksum": "b95cf2362fe2b684e6e861bef2924e960fcd813879dc2455367f8f620a534ff5", + "id": "fls_yoreux8tn65x", + "link": "types-and-traits.html#fls_yoreux8tn65x", + "number": "4.12.3:47" }, { - "checksum": "cc519441f6acd2fc70f4251a27ea4b31fd340a96b04393a9725c4ee2ac8cb0f1", - "id": "fls_e7jGvXvTsFpC", - "link": "program-structure-and-compilation.html#fls_e7jGvXvTsFpC", - "number": "18.3:3" + "checksum": "5fca7b2b2864b7504bace27cb6390c5d3d01591a0d2cffda65c64fab8cd2c085", + "id": "fls_r11shke69uu6", + "link": "types-and-traits.html#fls_r11shke69uu6", + "number": "4.12.3:48" }, { - "checksum": "900926091f2fede279a11afbb6e26774c0925d89e0c2a22e8d66e4454f946b6d", - "id": "fls_kQiJPwb2Hjcc", - "link": "program-structure-and-compilation.html#fls_kQiJPwb2Hjcc", - "number": "18.3:4" + "checksum": "39bf09cd84e3badf0caafcecd1fc51f3d0e38645e4d70876f6313e478f238928", + "id": "fls_ky7ykpufb95t", + "link": "types-and-traits.html#fls_ky7ykpufb95t", + "number": "4.12.3:49" }, { - "checksum": "3ac54406ea82315c87cf8183011163d2c68bb2596b63674f83fda32c046f21c9", - "id": "fls_9ub6ks8qrang", - "link": "program-structure-and-compilation.html#fls_9ub6ks8qrang", - "number": "18.3:5" + "checksum": "215a6f01fd8ef7a0ce0ca1e6aa8dbb82270d88d2f159c6bfe8c8aefe79a45c21", + "id": "fls_8kvme0u4u8r6", + "link": "types-and-traits.html#fls_8kvme0u4u8r6", + "number": "4.12.3:50" }, { - "checksum": "8db6a4b2f241361fa8613dd0e294548a09dde19ef5946330c8eca66ccd46bf09", - "id": "fls_OyFwBtDGVimT", - "link": "program-structure-and-compilation.html#fls_OyFwBtDGVimT", - "number": "18.3:6" + "checksum": "096c4e463b9f21a7f6f450c919b54b1d2c68b8b6e4496fe0dae2feda9e403c36", + "id": "fls_rl9yrdfwnu03", + "link": "types-and-traits.html#fls_rl9yrdfwnu03", + "number": "4.12.3:51" }, { - "checksum": "8b845c17714f7a58a93d5da09f978d3d87cfdbe7d4b7758b0f26740430c368de", - "id": "fls_jQqXxPyND1ds", - "link": "program-structure-and-compilation.html#fls_jQqXxPyND1ds", - "number": "18.3:7" + "checksum": "32a6c25c6e1124e082617948626bcbb07992d4275f54df0c25e6744748e4d53c", + "id": "fls_iqtmhn8flws7", + "link": "types-and-traits.html#fls_iqtmhn8flws7", + "number": "4.12.3:52" }, { - "checksum": "f15de757cf59ea80d471a93296df48599ad3b3402b7f85e22194e0e38f546b11", - "id": "fls_d9nn4yuiw1ja", - "link": "program-structure-and-compilation.html#fls_d9nn4yuiw1ja", - "number": "18.3:8" + "checksum": "275c8b1744879cf91aaf7305f7ba2d9a59d6efce2a635c736da0131f3f9880ed", + "id": "fls_sr8d5har4s03", + "link": "types-and-traits.html#fls_sr8d5har4s03", + "number": "4.12.3:53" }, { - "checksum": "bc735aaea18f5347b09a4b7ac21ae36e48f125b2cfa1ecbdb0566b0070303199", - "id": "fls_Mf62VqAhoZ3c", - "link": "program-structure-and-compilation.html#fls_Mf62VqAhoZ3c", - "number": "18.3:9" + "checksum": "e07e12484817e4c6487e1204a1f9510e836727735508f4ef691226c46e64bf6a", + "id": "fls_92pwnd1xbp5r", + "link": "types-and-traits.html#fls_92pwnd1xbp5r", + "number": "4.12.3:54" }, { - "checksum": "ca2db7453a265830bd34e2a087ce0a6e08f97f20221a3ef1ab151f72892c5e8f", - "id": "fls_RJJmN4tP7j4m", - "link": "program-structure-and-compilation.html#fls_RJJmN4tP7j4m", - "number": "18.3:10" + "checksum": "335cbd4c223a88ecd2be44961d7a6ce553d0352af438c9f034095d6c8978c103", + "id": "fls_bWHQIL4DSN4S", + "link": "types-and-traits.html#fls_bWHQIL4DSN4S", + "number": "4.12.3:55" }, { - "checksum": "7285d85fe56e6377849b918e1d006071c4bb14a18476290ddc8cce3adcc11a30", - "id": "fls_h93C3wfbAoz1", - "link": "program-structure-and-compilation.html#fls_h93C3wfbAoz1", - "number": "18.3:11" + "checksum": "8fed0bd1fa0bfbbe3fd11a53d62ef209fd7e09e5b74a6ad1f1b86277f57d1d86", + "id": "fls_ju4ypa5ysga0", + "link": "types-and-traits.html#fls_ju4ypa5ysga0", + "number": "4.12.3:56" } ], - "title": "Crates" + "title": "Type Coercion" }, { - "id": "fls_gklst7joeo33", + "id": "fls_wsfw8xf3vnil", "informational": false, - "link": "program-structure-and-compilation.html#crate-imports", - "number": "18.4", + "link": "types-and-traits.html#structural-equality", + "number": "4.12.4", "paragraphs": [ { - "checksum": "dbf3d3e47ad3d7220f2ffe8d071ff0607474ea6e8f4c3edd18a553ed36509d07", - "id": "fls_d0pa807s5d5h", - "link": "program-structure-and-compilation.html#fls_d0pa807s5d5h", - "number": "18.4:1" + "checksum": "4f72ed3d4dc04653669e57b2d2f00cb3b728c967aca05c3d7e142f41c86239fa", + "id": "fls_uVTpA7gbLCYX", + "link": "types-and-traits.html#fls_uVTpA7gbLCYX", + "number": "4.12.4:1" }, { - "checksum": "fc37a08d96af01fa9eddc305c921ac70c13dabc6a3a41c58f09f74622cc29075", - "id": "fls_vfam3wzeAiah", - "link": "program-structure-and-compilation.html#fls_vfam3wzeAiah", - "number": "18.4:2" + "checksum": "644c0bc93e9153b1614ccf90b7c10dc639fb525098e57bb2ef0188049521cf07", + "id": "fls_2DZAP6JJjJ9h", + "link": "types-and-traits.html#fls_2DZAP6JJjJ9h", + "number": "4.12.4:2" }, { - "checksum": "c98120af9e1e4f6ac518733e42a91af54ae118579dc4ea7355c255e9b3e077e7", - "id": "fls_ft860vkz0lkc", - "link": "program-structure-and-compilation.html#fls_ft860vkz0lkc", - "number": "18.4:3" + "checksum": "ebe3f4bdb59589737e2a8f2587d31e540b885474c990132b77755e7fe3118e79", + "id": "fls_emcNJzl2tHSA", + "link": "types-and-traits.html#fls_emcNJzl2tHSA", + "number": "4.12.4:3" }, { - "checksum": "662c29b5a73d3fc1aa97c39557c1a8401d2f480bba1ec488dba0c4bf1833fed3", - "id": "fls_k90qtnf8kgu1", - "link": "program-structure-and-compilation.html#fls_k90qtnf8kgu1", - "number": "18.4:4" + "checksum": "9adcd87a7fd208d21fe66dfbd16bfc8617403dbc76b33595031feae169ce3659", + "id": "fls_HpWSAfaTA1Dz", + "link": "types-and-traits.html#fls_HpWSAfaTA1Dz", + "number": "4.12.4:4" }, { - "checksum": "294202b2babc6658a990fded4bd4d3b7339acad054913a48ad0243ba8a907189", - "id": "fls_siv8bl6s2ndu", - "link": "program-structure-and-compilation.html#fls_siv8bl6s2ndu", - "number": "18.4:5" + "checksum": "2996c6bd3a3dcde83041a19b7e0ff0b4645e49e8373faa312d7d231d80add18e", + "id": "fls_5RcnETrW6f9m", + "link": "types-and-traits.html#fls_5RcnETrW6f9m", + "number": "4.12.4:5" }, { - "checksum": "0cecb1f1189c7f1a06d3c8d132b1e51f25548a0bf043f3ed3a93308b36f1506f", - "id": "fls_7vz5n3x6jo1s", - "link": "program-structure-and-compilation.html#fls_7vz5n3x6jo1s", - "number": "18.4:6" + "checksum": "b32c20afa5097b044cad383b2ddafd39d592f035043bc0e89d5c5fc9036d89a5", + "id": "fls_jMeWhn4sNTPF", + "link": "types-and-traits.html#fls_jMeWhn4sNTPF", + "number": "4.12.4:6" }, { - "checksum": "06a0e85326100e5036cbd423c273ca615cb80431541ea5e8806f3714ffb46193", - "id": "fls_3bgpc8m8yk4p", - "link": "program-structure-and-compilation.html#fls_3bgpc8m8yk4p", - "number": "18.4:7" + "checksum": "70b221698272f53f393baae8c1c140264a658dcc9849120449671981eb75f27c", + "id": "fls_hBFlaUrrhqZ6", + "link": "types-and-traits.html#fls_hBFlaUrrhqZ6", + "number": "4.12.4:7" } ], - "title": "Crate Imports" + "title": "Structural Equality" }, { - "id": "fls_5w50kf83oo1u", + "id": "fls_omaq7psg83n3", "informational": false, - "link": "program-structure-and-compilation.html#compilation-roots", - "number": "18.5", + "link": "types-and-traits.html#interior-mutability", + "number": "4.12.5", "paragraphs": [ { - "checksum": "3a9ce035689a8fb35cebf8668b6c24df7ee70eff17799938db3029dea0b1edf4", - "id": "fls_fhiqvgdamq5", - "link": "program-structure-and-compilation.html#fls_fhiqvgdamq5", - "number": "18.5:1" - }, - { - "checksum": "f870a40ce0c25e251e0e8856014efc6ad4611f1a28cdef8c9f460cfde2be372f", - "id": "fls_tk8tl2e0a34", - "link": "program-structure-and-compilation.html#fls_tk8tl2e0a34", - "number": "18.5:2" + "checksum": "9aa56a43b546255945ab60a08d9ae4aa90b591af22e8b4c1b7dc0c4f9af83598", + "id": "fls_khy2e23i9o7z", + "link": "types-and-traits.html#fls_khy2e23i9o7z", + "number": "4.12.5:1" }, { - "checksum": "232503cded53dd7221dcff57d6ac1d42bde0b44a3d83a1af322de4c453e185fa", - "id": "fls_bsyfxdk3ap1t", - "link": "program-structure-and-compilation.html#fls_bsyfxdk3ap1t", - "number": "18.5:3" + "checksum": "d05a898c3b49c36accca96ad2625158f39bce97c30d6fb95823df4e3533194bd", + "id": "fls_sWiU26n2xS3r", + "link": "types-and-traits.html#fls_sWiU26n2xS3r", + "number": "4.12.5:2" } ], - "title": "Compilation Roots" + "title": "Interior Mutability" }, { - "id": "fls_u1afezy1ye99", + "id": "fls_mcxf9y5u66sz", "informational": false, - "link": "program-structure-and-compilation.html#conditional-compilation", - "number": "18.6", + "link": "types-and-traits.html#visible-emptiness", + "number": "4.12.6", "paragraphs": [ { - "checksum": "1a29f8f4ec010a8c63819db415dfd3ccd84eaad03d75e2973e3fe3313e327406", - "id": "fls_9stc6nul6vq9", - "link": "program-structure-and-compilation.html#fls_9stc6nul6vq9", - "number": "18.6:1" + "checksum": "7e42d74e3da8f7f9b6eda796375e6051781a54907535ccc6ff3cc13c1bdb3c5e", + "id": "fls_SD4yUEQ9hHa3", + "link": "types-and-traits.html#fls_SD4yUEQ9hHa3", + "number": "4.12.6:1" + }, + { + "checksum": "c13454d3b8091be8c50bced264293b1579b676e9283537b215d3ab27a25e1f5c", + "id": "fls_GeoneCP5TYwf", + "link": "types-and-traits.html#fls_GeoneCP5TYwf", + "number": "4.12.6:2" + }, + { + "checksum": "79afbe5d444146ae944d95b5b002388b71a46be184cbc0c1ab2a7353ff938587", + "id": "fls_A2W4v53ihTGx", + "link": "types-and-traits.html#fls_A2W4v53ihTGx", + "number": "4.12.6:3" + }, + { + "checksum": "ee8c6b869e019c12fa0288994c7d95c6f96f7a4b0006a608f6df7f213035da77", + "id": "fls_AXOtKdSQR4AF", + "link": "types-and-traits.html#fls_AXOtKdSQR4AF", + "number": "4.12.6:4" + }, + { + "checksum": "dbd52754db7e99e4d5eb10dddfecc9249069d7ed364a9a98bde8982c3263f039", + "id": "fls_ZfnYORORz40y", + "link": "types-and-traits.html#fls_ZfnYORORz40y", + "number": "4.12.6:5" + }, + { + "checksum": "895a35389d5c60d90a8ab6c05a54fb1f409957ecb73275f13a7f0624e190b6f9", + "id": "fls_TE2GBnZX2YFW", + "link": "types-and-traits.html#fls_TE2GBnZX2YFW", + "number": "4.12.6:6" }, { - "checksum": "b3978159779eed65918ef78768828461f8b34e7ba551de7a8c9048cac3b6edcc", - "id": "fls_a0u9nnaf6drz", - "link": "program-structure-and-compilation.html#fls_a0u9nnaf6drz", - "number": "18.6:2" + "checksum": "d3cf9e0f74330b48fcaab97dc1eff8c8615a748c34a894590a2ec023e1a25b71", + "id": "fls_GNFzLMH8RBUC", + "link": "types-and-traits.html#fls_GNFzLMH8RBUC", + "number": "4.12.6:7" }, { - "checksum": "547cdabd4f386341f56db0ee6332a762b67d119af87f9944ebe2697637857ef7", - "id": "fls_pf1v89h7pjhh", - "link": "program-structure-and-compilation.html#fls_pf1v89h7pjhh", - "number": "18.6:3" + "checksum": "946a22061cba769276e82b5bb8702007f5fb27a16e9935f35ead0c741219ca5b", + "id": "fls_fSNrRsgzLd0E", + "link": "types-and-traits.html#fls_fSNrRsgzLd0E", + "number": "4.12.6:8" }, { - "checksum": "82d166eb360bad50b80bad46ce10a4d656966e94303a0fced35f59151561be1e", - "id": "fls_y56RGw3cbFex", - "link": "program-structure-and-compilation.html#fls_y56RGw3cbFex", - "number": "18.6:4" + "checksum": "1f521b54c1dc2d800998bde6191d77887e653d81e4ffb5009f1bb23903423e3d", + "id": "fls_R65LmGTvSlIm", + "link": "types-and-traits.html#fls_R65LmGTvSlIm", + "number": "4.12.6:9" }, { - "checksum": "483be06af44f42bd953cf6480fb4ba9de360289eef475b6671f2fc106a97e109", - "id": "fls_h6b1fuw4nvi1", - "link": "program-structure-and-compilation.html#fls_h6b1fuw4nvi1", - "number": "18.6:5" + "checksum": "07c752400b608840bb4af64f8adad7fafef68d2a16ef58430bd1c677a633204c", + "id": "fls_yiPv3wKYkSRl", + "link": "types-and-traits.html#fls_yiPv3wKYkSRl", + "number": "4.12.6:10" + }, + { + "checksum": "e534f969047e49d4603cc8e39b563a82cafefe659dace5e02c2b5e02e558b550", + "id": "fls_S9QL6yVF5LFI", + "link": "types-and-traits.html#fls_S9QL6yVF5LFI", + "number": "4.12.6:11" } ], - "title": "Conditional Compilation" + "title": "Visible Emptiness" }, { - "id": "fls_8jb3sjqamdpu", + "id": "fls_lv7w7aalpwm5", "informational": false, - "link": "program-structure-and-compilation.html#program-entry-point", - "number": "18.7", + "link": "types-and-traits.html#type-inference", + "number": "4.12.7", "paragraphs": [ { - "checksum": "2e5587a498db2567ffbf412e144bcbb85c9453ae0d1a9b9a2db81e2afa0338a5", - "id": "fls_dp64b08em9BJ", - "link": "program-structure-and-compilation.html#fls_dp64b08em9BJ", - "number": "18.7:1" - }, - { - "checksum": "4211a854bc48f1108224e53ef3f2b18f213150fee9b6e0c6b0a8fdd3cde72ba6", - "id": "fls_sbGnkm8Ephiu", - "link": "program-structure-and-compilation.html#fls_sbGnkm8Ephiu", - "number": "18.7:2" - }, - { - "checksum": "06806b5f2bb00b12d499429e29aa3f6916237f0c54e9136fd16474fae18c14fb", - "id": "fls_o4fxok23134r", - "link": "program-structure-and-compilation.html#fls_o4fxok23134r", - "number": "18.7:3" + "checksum": "0733340fbfcaed4e1531c4bbd915b1e25c8a7f93c5258f26dc010a4fe224801a", + "id": "fls_h8sedxew0d4u", + "link": "types-and-traits.html#fls_h8sedxew0d4u", + "number": "4.12.7:1" }, { - "checksum": "c791afca579a283e2645e4a3bf9473606a55028fcaec0a1fca0c5d87bf7db370", - "id": "fls_bk755pvc1l53", - "link": "program-structure-and-compilation.html#fls_bk755pvc1l53", - "number": "18.7:4" + "checksum": "26ecf6194c3f105ef5dfdc03e0b71e4a1c8160edb540c06c1056e324e136e1e1", + "id": "fls_ybvrhh96fc7y", + "link": "types-and-traits.html#fls_ybvrhh96fc7y", + "number": "4.12.7:2" }, { - "checksum": "4eaf3f71fa346f0de97eaef7ae61b54e234608337d12733cee8930cd3a0a16be", - "id": "fls_a3je4wc53bmo", - "link": "program-structure-and-compilation.html#fls_a3je4wc53bmo", - "number": "18.7:5" + "checksum": "a923067c037b043e99981b0ca0ebdb47199c07b67e084d5a7bc220e594b133e1", + "id": "fls_EWBilpepaDcX", + "link": "types-and-traits.html#fls_EWBilpepaDcX", + "number": "4.12.7:3" }, { - "checksum": "dc35e76e01d0f08b81b61a7c8861a422fa2dc8a5d44ce4557affba712d088d61", - "id": "fls_w8q15zp7kyl0", - "link": "program-structure-and-compilation.html#fls_w8q15zp7kyl0", - "number": "18.7:6" + "checksum": "f0ad52d5ba7639c36c518c3720cb06c3552839e842057b6d3ee4ba143ab4be1d", + "id": "fls_NYSzcvf5nQpi", + "link": "types-and-traits.html#fls_NYSzcvf5nQpi", + "number": "4.12.7:4" }, { - "checksum": "0605829262c1b010a633c97250c2141e26eabf10f42a901e0ffae239bc84909e", - "id": "fls_4psnfphsgdek", - "link": "program-structure-and-compilation.html#fls_4psnfphsgdek", - "number": "18.7:7" + "checksum": "f9c9598c2dd792f8150bbc7db9744da31f5c54d1398f56477bc718402556dab3", + "id": "fls_htLp5J5ObgNh", + "link": "types-and-traits.html#fls_htLp5J5ObgNh", + "number": "4.12.7:5" }, { - "checksum": "406115e1f2b804459a7155b5aa521a67fae4b9a11d3d9ee26b45671e0d705fad", - "id": "fls_m7xfrhqif74", - "link": "program-structure-and-compilation.html#fls_m7xfrhqif74", - "number": "18.7:8" + "checksum": "20eabdd38eacdb13f67b19026f6ad726531238743b530c057d28a339a5e5db77", + "id": "fls_cPlCLGCcl7EK", + "link": "types-and-traits.html#fls_cPlCLGCcl7EK", + "number": "4.12.7:6" }, { - "checksum": "7527f425026d6c1455709270dbc862fa8c81edae9aa97bbf4c63b854ab77ea04", - "id": "fls_qq9fzrw4aykd", - "link": "program-structure-and-compilation.html#fls_qq9fzrw4aykd", - "number": "18.7:9" - } - ], - "title": "Program Entry Point" - } - ], - "title": "Program Structure and Compilation" - }, - { - "informational": false, - "link": "patterns.html", - "sections": [ - { - "id": "fls_xgqh0ju6bmbn", - "informational": false, - "link": "patterns.html", - "number": "5", - "paragraphs": [ - { - "checksum": "fd701b3ef4e17c49416e15bb5e0685b07ec2165ebe338ee5ed86433fe2a1791c", - "id": "fls_imegtsi224ts", - "link": "patterns.html#fls_imegtsi224ts", - "number": "5:1" + "checksum": "ddd1c8f0bf52f83dc4686954218b0f4cd13dfd720d985efc7e5e7cd1fb987375", + "id": "fls_KphY5qHev0Dc", + "link": "types-and-traits.html#fls_KphY5qHev0Dc", + "number": "4.12.7:7" }, { - "checksum": "2e7d16a81699ee46155a23eecb0a8984103307894e9947cc4e2b25beb667d156", - "id": "fls_VQMmveZUfNTn", - "link": "patterns.html#fls_VQMmveZUfNTn", - "number": "5:2" + "checksum": "50c6910a7fc8c1b07b987b8c629d5666817afcdfd7a77af3e544b2df055354eb", + "id": "fls_67Bf5kR5OtYW", + "link": "types-and-traits.html#fls_67Bf5kR5OtYW", + "number": "4.12.7:8" }, { - "checksum": "f11f4d96ce76157d36700b1642b225614023417f88f39e02004f751fa0ceca1c", - "id": "fls_mp6i4blzexnu", - "link": "patterns.html#fls_mp6i4blzexnu", - "number": "5:3" + "checksum": "cd59d75b8786aa2152a00a74d15960f972ee7b9efd9ed6ea6be282ff7bb0393f", + "id": "fls_sAS3vQpkjksr", + "link": "types-and-traits.html#fls_sAS3vQpkjksr", + "number": "4.12.7:9" }, { - "checksum": "640a01339d6302f50e834a92db0b99f585848a4dc1dfb4801941267dfe6c1eff", - "id": "fls_JJ1fJa1SsaWh", - "link": "patterns.html#fls_JJ1fJa1SsaWh", - "number": "5:4" + "checksum": "6f383a50f25c7293a36c28b2eb734e84299ba6ad3b35e8d967d2b84845ccebc4", + "id": "fls_Sowatt1V988J", + "link": "types-and-traits.html#fls_Sowatt1V988J", + "number": "4.12.7:10" }, { - "checksum": "28dec289d15cad76b56462aa121b43b5d1e42ef0f0d0636c38ac0eb7eca4b4f9", - "id": "fls_6xx34zr069bj", - "link": "patterns.html#fls_6xx34zr069bj", - "number": "5:5" + "checksum": "30466e79ab50a4ce686cad40eea4346e303925d4a01f36024720a13e48a89b4f", + "id": "fls_A1NVYkepoaMk", + "link": "types-and-traits.html#fls_A1NVYkepoaMk", + "number": "4.12.7:11" }, { - "checksum": "32457ed53a0bf4373c4cfb97677b5e208930233d39b87a5accc8c508af6f58ce", - "id": "fls_8xzjb0yzftkd", - "link": "patterns.html#fls_8xzjb0yzftkd", - "number": "5:6" + "checksum": "6051ca9ecfe04a062d5a946c136ee02de33353f5ed963a07c287899720961af0", + "id": "fls_J77VeAlD8rsv", + "link": "types-and-traits.html#fls_J77VeAlD8rsv", + "number": "4.12.7:12" }, { - "checksum": "5ac967f09550667532acd0916172717779d286369a004f6e89c12e7852e25bc4", - "id": "fls_cma5t8waon0x", - "link": "patterns.html#fls_cma5t8waon0x", - "number": "5:7" + "checksum": "aa67d478f734851497c9592cfcce1e63452ca21e44b8f258ca57c2fd00174c93", + "id": "fls_P9uiamxA4HE3", + "link": "types-and-traits.html#fls_P9uiamxA4HE3", + "number": "4.12.7:13" }, { - "checksum": "d92c5c133b1308dfee746f9722cc628be4cc1b150f3f387c0d2c08e961e687fb", - "id": "fls_TUanRT7WU14E", - "link": "patterns.html#fls_TUanRT7WU14E", - "number": "5:8" + "checksum": "6dd1a00950a9e651a699734e82b1208e81181f5202936ac1f1028fa651d02a27", + "id": "fls_5d4hw3gj4w4n", + "link": "types-and-traits.html#fls_5d4hw3gj4w4n", + "number": "4.12.7:14" }, { - "checksum": "76ef1771e13cdc1b8dad94d945851e96cf1e0dca5248e57477f71ce6cda80f69", - "id": "fls_72JHo343O7jp", - "link": "patterns.html#fls_72JHo343O7jp", - "number": "5:9" + "checksum": "b6a171c627874b9918c71b9cf66fa1fae6c874bb3359b2d6cba89379886281a9", + "id": "fls_qlovdtcj1v1b", + "link": "types-and-traits.html#fls_qlovdtcj1v1b", + "number": "4.12.7:15" }, { - "checksum": "0bfe474ea8833b7a5007ff4a1e538f5d0205dbcb0de5771c8971175d781819ec", - "id": "fls_6bwTtGKb7ba7", - "link": "patterns.html#fls_6bwTtGKb7ba7", - "number": "5:10" + "checksum": "38de11a4893adbf77df8542f9ecbe28d5ee3b2986a809383ce4e0bf694d0a106", + "id": "fls_Z5gKFjZW5rRA", + "link": "types-and-traits.html#fls_Z5gKFjZW5rRA", + "number": "4.12.7:16" }, { - "checksum": "75924b5c01c922a97bc85d9c2d6f7b9d4da495212045063f33bccd613da4869a", - "id": "fls_8luyomzppck", - "link": "patterns.html#fls_8luyomzppck", - "number": "5:11" + "checksum": "7dd786d9014c1823ba3515b3ea9f68d750d2db96ee522b6f4546aa39d4f678cf", + "id": "fls_vYvumjTQH9Xg", + "link": "types-and-traits.html#fls_vYvumjTQH9Xg", + "number": "4.12.7:17" }, { - "checksum": "b70079f33aaa9cb4c2507706bdb13c9e8beed748288a7751770b7aa4309c17bf", - "id": "fls_rpvdfmy3n05a", - "link": "patterns.html#fls_rpvdfmy3n05a", - "number": "5:12" + "checksum": "d8578c25c7dd2694e72bf1900ec0de0b6ea38280d2f75db22a3f5249f98fb764", + "id": "fls_QaGKt99CmvF6", + "link": "types-and-traits.html#fls_QaGKt99CmvF6", + "number": "4.12.7:18" }, { - "checksum": "9931e26d30bcf23edc7433916de4a6c22fec5fc0614afe05991620ec08509266", - "id": "fls_YDVgFaTQwcL8", - "link": "patterns.html#fls_YDVgFaTQwcL8", - "number": "5:13" + "checksum": "53bb50d4c95b2e725abb10288ad64df42968237c86530bd3bee7f867075fab87", + "id": "fls_RJIeW597XRvS", + "link": "types-and-traits.html#fls_RJIeW597XRvS", + "number": "4.12.7:19" }, { - "checksum": "52545837b9862e70dc97203f0a7e1d899fb3e37432c52e62a76dbf52c298f7e1", - "id": "fls_kv533rntni1x", - "link": "patterns.html#fls_kv533rntni1x", - "number": "5:14" - } - ], - "title": "Patterns" - }, - { - "id": "fls_uh76pw6ykd57", - "informational": false, - "link": "patterns.html#refutability", - "number": "5.1", - "paragraphs": [ + "checksum": "229d0d5b05eb5ff42588ebd5ac4c95a095a29d82ff3f26152bebfe73e02bbc3e", + "id": "fls_veG2D64fIXvo", + "link": "types-and-traits.html#fls_veG2D64fIXvo", + "number": "4.12.7:20" + }, { - "checksum": "74e294f01efeecaf5f7ea7508839e45750aee3872825525951cd7e1a5a369127", - "id": "fls_9ntc4qmjmo90", - "link": "patterns.html#fls_9ntc4qmjmo90", - "number": "5.1:1" + "checksum": "f5d2a97e84c341b7cc97283f2c6fff644af2eaa3f18136ae71b93c5ee9b56241", + "id": "fls_uvvn4usfsbhr", + "link": "types-and-traits.html#fls_uvvn4usfsbhr", + "number": "4.12.7:21" }, { - "checksum": "bedd24701ba15fd54193582bf452c8d028b7e7d24e62f7cf549bccc5f8d4e4b8", - "id": "fls_9fjspnefoyvz", - "link": "patterns.html#fls_9fjspnefoyvz", - "number": "5.1:2" + "checksum": "dec24d875b5094ddba99326af168b3189c2c89ffe2f65919c91d6df57ae7013a", + "id": "fls_gDalJm1XS0mi", + "link": "types-and-traits.html#fls_gDalJm1XS0mi", + "number": "4.12.7:22" }, { - "checksum": "30c83040e0dcb6e5472460546b82a2f1223224d1b6b315de2204a12c412741b0", - "id": "fls_uq7ftuuq1sig", - "link": "patterns.html#fls_uq7ftuuq1sig", - "number": "5.1:3" + "checksum": "2fba60344712b36c7e3d85a11370baa6cf316b8b2a345d1b4b3f0dd61383b631", + "id": "fls_7ov36fpd9mwe", + "link": "types-and-traits.html#fls_7ov36fpd9mwe", + "number": "4.12.7:23" }, { - "checksum": "788cc1b098646556626fa9669f0f2c05d8907e582b4bc899ecac453fcfc61dde", - "id": "fls_mnbyt7jfYAZ9", - "link": "patterns.html#fls_mnbyt7jfYAZ9", - "number": "5.1:4" + "checksum": "6f3816132ffe28efaf427fe33b1433ad594067052bda3f57277c1451893dbbc3", + "id": "fls_3hv3wxkhjjp1", + "link": "types-and-traits.html#fls_3hv3wxkhjjp1", + "number": "4.12.7:24" }, { - "checksum": "fb93c07735fd3880cae598758a01af1eb04fd98aa6fbdca79a2fd2e8a525c26d", - "id": "fls_l76ycteulo8e", - "link": "patterns.html#fls_l76ycteulo8e", - "number": "5.1:5" + "checksum": "605267afb8b9036c0eee2345a972043cff7c21380f985f12caaba2f7d30aee0a", + "id": "fls_bXQ63GYYDuMp", + "link": "types-and-traits.html#fls_bXQ63GYYDuMp", + "number": "4.12.7:25" }, { - "checksum": "8cbff3175ce2f1524fa0ece5a443ba2edd4a78433ff410a98a31ed00f38a2bde", - "id": "fls_lh0d85tl4qvy", - "link": "patterns.html#fls_lh0d85tl4qvy", - "number": "5.1:6" + "checksum": "fbdb2b0ff1c5aa038413a78be7e9a6ff41c0612db0648417cc31a20a682f98a0", + "id": "fls_JryXiKBIFvF3", + "link": "types-and-traits.html#fls_JryXiKBIFvF3", + "number": "4.12.7:26" }, { - "checksum": "abe5de2e97771298a372e5a404f34278ed54f0e8ada2a337d6f5d85fc5a7e7c0", - "id": "fls_sgu9bnp7xajv", - "link": "patterns.html#fls_sgu9bnp7xajv", - "number": "5.1:7" + "checksum": "59854f2b212e191c9c0c639c1bb9caad6bdf38f48c95024fb3a49ae93818cd30", + "id": "fls_rvj3XspFZ1u3", + "link": "types-and-traits.html#fls_rvj3XspFZ1u3", + "number": "4.12.7:27" }, { - "checksum": "d347b514f8d0662ae967270ae73f6f7be119b4ab60fdf44106ebe781e251e33d", - "id": "fls_cl1g4fxfa020", - "link": "patterns.html#fls_cl1g4fxfa020", - "number": "5.1:8" - } - ], - "title": "Refutability" - }, - { - "id": "fls_7bxv8lybxm18", - "informational": false, - "link": "patterns.html#identifier-patterns", - "number": "5.2", - "paragraphs": [ + "checksum": "8ccd07581891d7328efac206e6e8a5952ef3b60a40fbf3b2b43182fc636b4fb8", + "id": "fls_6GrNr2izovRN", + "link": "types-and-traits.html#fls_6GrNr2izovRN", + "number": "4.12.7:28" + }, { - "checksum": "12df3c9420d70b5492aee216c48d595bec10c1bfc7c95589c1aa150787788c68", - "id": "fls_uljdw9rf7ies", - "link": "patterns.html#fls_uljdw9rf7ies", - "number": "5.2:1" + "checksum": "0557c8604fc016c9a1c036a9f02b61c95d2b7d1d6a0043aea89868044534e8f4", + "id": "fls_9dSltJ6U98Fo", + "link": "types-and-traits.html#fls_9dSltJ6U98Fo", + "number": "4.12.7:29" }, { - "checksum": "241eeb37b7dae1635d202ba61372dfbf4a693e183ca1ece17e3818904f6c809d", - "id": "fls_vy9uw586wy0d", - "link": "patterns.html#fls_vy9uw586wy0d", - "number": "5.2:2" + "checksum": "1f73592f04b242864c2f82f5c27c59de5ca04bb1db0fb55e1634cf3d074eff81", + "id": "fls_v5dWGuBKvQSJ", + "link": "types-and-traits.html#fls_v5dWGuBKvQSJ", + "number": "4.12.7:30" }, { - "checksum": "d9b79bd4e33b5e5d59d8f9ed2df014f66c7fa0c487fc3b0b70e3700fdc7dc420", - "id": "fls_hqwt3fvr063y", - "link": "patterns.html#fls_hqwt3fvr063y", - "number": "5.2:3" + "checksum": "849706ce4bb64a8cb1a45280565a1bd62f3e35ac9862270ec8aabd354c93aa06", + "id": "fls_SZgixDCAx6PQ", + "link": "types-and-traits.html#fls_SZgixDCAx6PQ", + "number": "4.12.7:31" }, { - "checksum": "4f2142d8ccdb2e64efc1eaedfef6bdc5b02f5ab32bf7ed59698ff61681fd7384", - "id": "fls_joIQdDn44oIT", - "link": "patterns.html#fls_joIQdDn44oIT", - "number": "5.2:4" + "checksum": "dbbbf92cb2589a9aaaf11ef14b6aeb380d37d5f6b741d4cb2c5b1fa2e5a1aecb", + "id": "fls_XYY1U9h9HlAa", + "link": "types-and-traits.html#fls_XYY1U9h9HlAa", + "number": "4.12.7:32" }, { - "checksum": "e7b226edcf6dc4bc129b1212d923d2b0052764e5f3087f635a8f5c48af37d436", - "id": "fls_24c95c56tugl", - "link": "patterns.html#fls_24c95c56tugl", - "number": "5.2:5" + "checksum": "4b63ff86116c425fe2cddd007442d0c03073678cf0cdc42b62c7224845aaa0e3", + "id": "fls_1rnssw39aRWn", + "link": "types-and-traits.html#fls_1rnssw39aRWn", + "number": "4.12.7:33" }, { - "checksum": "3ee68e9c11cc6de58983cada3e69d6e25c9096bea32debb22614bb2a25d155f0", - "id": "fls_twcavjk7iquy", - "link": "patterns.html#fls_twcavjk7iquy", - "number": "5.2:6" + "checksum": "8e96bbc341a6b1de1d507f569d8d34e94e53b067964a454afed96b6b4bdb7d05", + "id": "fls_aYJaZXcOVVyk", + "link": "types-and-traits.html#fls_aYJaZXcOVVyk", + "number": "4.12.7:34" }, { - "checksum": "e762d3ac05f362349d87a2a26cc37f37eaa2148eadb4ab7a97745abed9a62df2", - "id": "fls_k1yBTstX7jEE", - "link": "patterns.html#fls_k1yBTstX7jEE", - "number": "5.2:7" + "checksum": "24c8209ffc45df2596e294640bba2aa7b42f5686d8a0e98b7f5df152a82815d5", + "id": "fls_X8kLC7JwiF0A", + "link": "types-and-traits.html#fls_X8kLC7JwiF0A", + "number": "4.12.7:35" }, { - "checksum": "a52f9ed5eaa2b34c018a779a37184c71defa111f92bc5a73d7642178df8e755f", - "id": "fls_hw26hy33guk5", - "link": "patterns.html#fls_hw26hy33guk5", - "number": "5.2:8" + "checksum": "e434416bfa8e8dffc8e6c1164cd594db7a1eb893254a271421442b4a889a0d70", + "id": "fls_ZFQhOxO3jpby", + "link": "types-and-traits.html#fls_ZFQhOxO3jpby", + "number": "4.12.7:36" }, { - "checksum": "b439ed8b20762b4889891571152f54745dc81b2f6b4abc45db0448e2cac4888d", - "id": "fls_svfxwz4yy5i", - "link": "patterns.html#fls_svfxwz4yy5i", - "number": "5.2:9" + "checksum": "49ca000ce5a71f5ae340a653f432992078bb8394c14aaba4e946158942b0ea9e", + "id": "fls_l1G52a0qqEes", + "link": "types-and-traits.html#fls_l1G52a0qqEes", + "number": "4.12.7:37" }, { - "checksum": "db308f657bf02a10ddea3390b29681c6eccf4bb81b2069d5f1b2ecfbc7853dc1", - "id": "fls_x6f6q22b5jpc", - "link": "patterns.html#fls_x6f6q22b5jpc", - "number": "5.2:10" + "checksum": "6c39d9fa3aff34c355cc05dcaefa243378bdafc12a5968b7239c801d4ad37d50", + "id": "fls_Tx4Sx4Qy8y2d", + "link": "types-and-traits.html#fls_Tx4Sx4Qy8y2d", + "number": "4.12.7:38" }, { - "checksum": "dd8b86458df1b70addc97086f4ada7c7692ff5103622010059fbf0283f8ffbb3", - "id": "fls_r2mb8v2lh3x0", - "link": "patterns.html#fls_r2mb8v2lh3x0", - "number": "5.2:11" + "checksum": "1819eb47223394c7033f3abd693ab921b11aff33067751eb8cd6863b755d3f61", + "id": "fls_Nbdth8N0PSRq", + "link": "types-and-traits.html#fls_Nbdth8N0PSRq", + "number": "4.12.7:39" }, { - "checksum": "0905da49ff8fceb1c24a37a019ba80b02f466cbede9db934f3506067251411de", - "id": "fls_7oioaitb075g", - "link": "patterns.html#fls_7oioaitb075g", - "number": "5.2:12" + "checksum": "74689e53776dd9f93adcf731cfd28c8cf305612f2ba7174d640713d10f582686", + "id": "fls_hISRWZUuqE4Q", + "link": "types-and-traits.html#fls_hISRWZUuqE4Q", + "number": "4.12.7:40" }, { - "checksum": "11934cfbec627d51e8441eeef28fa1983d4ed59d73d9dcbff26e5d4cf4007fed", - "id": "fls_40qin0ss5sqd", - "link": "patterns.html#fls_40qin0ss5sqd", - "number": "5.2:13" + "checksum": "27a406335ebbb1fd6b5a75a0757865b493c7c597c042bfc555c7188be7f10991", + "id": "fls_ygi1ACJ0RkfS", + "link": "types-and-traits.html#fls_ygi1ACJ0RkfS", + "number": "4.12.7:41" }, { - "checksum": "05ce4678c850feb8fbdc7b0bf9a65ccdd82414dec3163903b5ff487cf3cc1b8c", - "id": "fls_pivz0v7ey6sw", - "link": "patterns.html#fls_pivz0v7ey6sw", - "number": "5.2:14" + "checksum": "588b531763a50551cc01572cee3f92331ffeca722aee95bdcef6579dc6a1df49", + "id": "fls_97Fxlv2KN6QF", + "link": "types-and-traits.html#fls_97Fxlv2KN6QF", + "number": "4.12.7:42" }, { - "checksum": "6827708c08fa8d7048aaf70d448f1ed456a018a5621ccbad9e38ba38eb4f001f", - "id": "fls_2ahkrddxwj1n", - "link": "patterns.html#fls_2ahkrddxwj1n", - "number": "5.2:15" + "checksum": "13a1275180d2c104a792fff30db7a7ca08c5753e22d660c88f6f8fd5714a2ce6", + "id": "fls_hzXqj6YT1mFr", + "link": "types-and-traits.html#fls_hzXqj6YT1mFr", + "number": "4.12.7:43" }, { - "checksum": "5938b3e5c53f5eb860f3c7aae1864e4e5b74c0a93b9460455798ee597639f3bd", - "id": "fls_eucnafj3uedy", - "link": "patterns.html#fls_eucnafj3uedy", - "number": "5.2:16" + "checksum": "f1e4f473dd23e3b9cf44076d9391ecb3e1b4acd8b5e43e81c022030fc8ee42e9", + "id": "fls_Kv0dzoMODtdy", + "link": "types-and-traits.html#fls_Kv0dzoMODtdy", + "number": "4.12.7:44" }, { - "checksum": "25e0df417675d625d1a57e95afba6af3de12fa61abe805e4d2d81e90613250d0", - "id": "fls_f8zo4scodhcr", - "link": "patterns.html#fls_f8zo4scodhcr", - "number": "5.2:17" + "checksum": "5d1d8604e42079634aa8e88ee40d69c6321bb9e01bccd24ed821e193b76cb937", + "id": "fls_5v6TR7oqOwFM", + "link": "types-and-traits.html#fls_5v6TR7oqOwFM", + "number": "4.12.7:45" }, { - "checksum": "7d886e7fba9c09e14a0789ddb951c710bb88a50cf8e06a6bcdd6629fb55562f6", - "id": "fls_d3fs2h7oqjl0", - "link": "patterns.html#fls_d3fs2h7oqjl0", - "number": "5.2:18" + "checksum": "676707c30188643f9aee026a919dfa809da219d59e53f2bdc47e3e89d5e5c973", + "id": "fls_Gwx0Kfx68DXL", + "link": "types-and-traits.html#fls_Gwx0Kfx68DXL", + "number": "4.12.7:46" }, { - "checksum": "655158cf590f06600319e8e2695db1b56219fadc4cc034888e17c53b07a84274", - "id": "fls_exo8asevh5x1", - "link": "patterns.html#fls_exo8asevh5x1", - "number": "5.2:19" + "checksum": "f8723a3646aa5ab3733bf3c732510697a8de3a235b3982cf3dd70d352f129ede", + "id": "fls_J6ydUCCJp1Sn", + "link": "types-and-traits.html#fls_J6ydUCCJp1Sn", + "number": "4.12.7:47" }, { - "checksum": "d3b46f205bcb595fe85d75191fcd80dd7933343080fd05b57200c0118ad6b609", - "id": "fls_sfyfdxhvhk44", - "link": "patterns.html#fls_sfyfdxhvhk44", - "number": "5.2:20" + "checksum": "6488fc5972643c8005b9ff665490a63292c4f336c2a0a5236c0539a65816b2d6", + "id": "fls_FSQqHs8T4bUx", + "link": "types-and-traits.html#fls_FSQqHs8T4bUx", + "number": "4.12.7:48" }, { - "checksum": "b32516046ade3590c7036c2a5ecbf17d55a9f6f00da394c3d5281096f8cf26dc", - "id": "fls_as0pqqmo1des", - "link": "patterns.html#fls_as0pqqmo1des", - "number": "5.2:21" - } - ], - "title": "Identifier Patterns" - }, - { - "id": "fls_2krxnq8q9ef1", - "informational": false, - "link": "patterns.html#literal-patterns", - "number": "5.3", - "paragraphs": [ + "checksum": "fc18f70656fef1ef9b09d7ca8f249620b596fb1b8c70efba58e3a5a678b21213", + "id": "fls_0HHC1iOk5dwz", + "link": "types-and-traits.html#fls_0HHC1iOk5dwz", + "number": "4.12.7:49" + }, { - "checksum": "7afee4377bf01eee53e398835c943ac86067eba24c1cf64551d7f801915e5ece", - "id": "fls_pah15qa54irs", - "link": "patterns.html#fls_pah15qa54irs", - "number": "5.3:1" + "checksum": "27509db5c82720088c62ec3e141b026c1e322a7186cb994b8a0d5e924203e03e", + "id": "fls_QZWTS0Giy3I3", + "link": "types-and-traits.html#fls_QZWTS0Giy3I3", + "number": "4.12.7:50" }, { - "checksum": "c8e3035b1a25edf2fccd9214fd3039e797d799bd22e801fdeda70976a39c68a2", - "id": "fls_COQKJC0dvtNO", - "link": "patterns.html#fls_COQKJC0dvtNO", - "number": "5.3:2" + "checksum": "b5d0949443935576123b18cca27070bb53af6c5002605c2aec5715f19a860365", + "id": "fls_KJsIu1lgVZxP", + "link": "types-and-traits.html#fls_KJsIu1lgVZxP", + "number": "4.12.7:51" }, { - "checksum": "279b07c6a35f3bcfed9d5bcccc78afdc28e8982a5be9e9b768dd95c2a3b0ae9c", - "id": "fls_JP8YSbxSN0Ym", - "link": "patterns.html#fls_JP8YSbxSN0Ym", - "number": "5.3:3" + "checksum": "e676b83659cdf96c2848aaad17275d4f1455977a2a1ec7bd5732238283eb9382", + "id": "fls_KRQxdSav1KBA", + "link": "types-and-traits.html#fls_KRQxdSav1KBA", + "number": "4.12.7:52" }, { - "checksum": "b5e4dea23898449138503e31ca3616865806df2f2af08e0460bf75c15cfda0d0", - "id": "fls_co60bzvwashg", - "link": "patterns.html#fls_co60bzvwashg", - "number": "5.3:4" + "checksum": "0bf1dd0f944f4720a843ee0ff984ac3d257f69cc6ba1b5214da858e40de1d6c3", + "id": "fls_Mcpwyvz47SoG", + "link": "types-and-traits.html#fls_Mcpwyvz47SoG", + "number": "4.12.7:53" }, { - "checksum": "617fb1beb5774f8f6685a4671e5bf2d72fd056bbe348e8236f22254e8462a51a", - "id": "fls_fqclaznjgtb1", - "link": "patterns.html#fls_fqclaznjgtb1", - "number": "5.3:5" - } - ], - "title": "Literal Patterns" - }, - { - "id": "fls_1xit18et4ohh", - "informational": false, - "link": "patterns.html#parenthesized-patterns", - "number": "5.4", - "paragraphs": [ + "checksum": "18ee894162cc21546a88f2a251a9f6bfe341f49b0bdb784abc72aba91b0d6ae6", + "id": "fls_34AQ9g7xhdUj", + "link": "types-and-traits.html#fls_34AQ9g7xhdUj", + "number": "4.12.7:54" + }, { - "checksum": "24403c83d4acc034aaa73d968a14afbfe7d9f1bfdbee90b17ef960075453aaac", - "id": "fls_kvqzmt7my5dh", - "link": "patterns.html#fls_kvqzmt7my5dh", - "number": "5.4:1" + "checksum": "8842ea78c118edaa940f0f0073d6e7c3290bc1f164964dd50de8eb727650dc48", + "id": "fls_4ZT35povCL04", + "link": "types-and-traits.html#fls_4ZT35povCL04", + "number": "4.12.7:55" }, { - "checksum": "5f42a52d41b664edb742dfd664f01990bf8432d041e601e283d750cb83a85f90", - "id": "fls_mrjhpiq5refe", - "link": "patterns.html#fls_mrjhpiq5refe", - "number": "5.4:2" + "checksum": "8b10910fa086c26e1e94705d9398a51c68e7a3c6a9a4578d4fc204abc3be6d40", + "id": "fls_62OcWZaVN9hh", + "link": "types-and-traits.html#fls_62OcWZaVN9hh", + "number": "4.12.7:56" }, { - "checksum": "41b7c39a45d4144509bc833e84ea455b7d86c0a67c5d58960e14e6337b2c44d4", - "id": "fls_pe5kh8y8u664", - "link": "patterns.html#fls_pe5kh8y8u664", - "number": "5.4:3" + "checksum": "7412650a46a74061d81ffa512d2179f23b1edf74b3872464cc7f6b596caaaadd", + "id": "fls_st9onPgDrc8y", + "link": "types-and-traits.html#fls_st9onPgDrc8y", + "number": "4.12.7:57" }, { - "checksum": "edcbe640bd79fa7513605658a4cc16f8d648069f3cca0dd200ee8a2224d1612e", - "id": "fls_2xq8852gihn9", - "link": "patterns.html#fls_2xq8852gihn9", - "number": "5.4:4" + "checksum": "82e588ccaea063d84f7b82b1242d7802cd1df6d675a27ce6e2bd69459a9c21ab", + "id": "fls_F999gqcBfff9", + "link": "types-and-traits.html#fls_F999gqcBfff9", + "number": "4.12.7:58" }, { - "checksum": "6b2bf289b930cfce356ecbd497063fe3f045ee08dc3919d303a7a90c52eae0fb", - "id": "fls_2dmeukyjqz9y", - "link": "patterns.html#fls_2dmeukyjqz9y", - "number": "5.4:5" - } - ], - "title": "Parenthesized Patterns" - }, - { - "id": "fls_uloyjbaso8pz", - "informational": false, - "link": "patterns.html#path-patterns", - "number": "5.5", - "paragraphs": [ + "checksum": "ebc67189c8cb991506019fe72864d2436c9245d633826ceebaf4b8277518858e", + "id": "fls_Esa4ST7lLp8T", + "link": "types-and-traits.html#fls_Esa4ST7lLp8T", + "number": "4.12.7:59" + }, { - "checksum": "6eab6a03669622290fe084028190344d03230efeca455f6bd9e2891ac8781c05", - "id": "fls_1crq0mexo5r1", - "link": "patterns.html#fls_1crq0mexo5r1", - "number": "5.5:1" + "checksum": "5052100ae827ec33641989b63bb47e384a21c161e2fb0240e543ae9120df54ab", + "id": "fls_9GDElCkL1UbH", + "link": "types-and-traits.html#fls_9GDElCkL1UbH", + "number": "4.12.7:60" }, { - "checksum": "6b5457b32a2fa8723c542bd4b63d6de477dfc4e8406457a6b0ec05a421fbb295", - "id": "fls_xz5otkhogn31", - "link": "patterns.html#fls_xz5otkhogn31", - "number": "5.5:2" + "checksum": "806a8a9c4c2a0fa926f161ee08b989bacce0c50dc99d9f154417a5c2d8731adb", + "id": "fls_0eATa6RtDNtA", + "link": "types-and-traits.html#fls_0eATa6RtDNtA", + "number": "4.12.7:61" }, { - "checksum": "853158ce8e75116be9bf2d63bc0d49626010b9d0fa0c5ebe80a595ef4da37aa3", - "id": "fls_t8sjzsif2ilf", - "link": "patterns.html#fls_t8sjzsif2ilf", - "number": "5.5:3" + "checksum": "bb3507010c18cd505e9adb4b8c8a562d3ee002bee23c005dfbd3e702c9f232f8", + "id": "fls_q1JZZMxqWXCk", + "link": "types-and-traits.html#fls_q1JZZMxqWXCk", + "number": "4.12.7:62" }, { - "checksum": "32107632e6d0b56902397df91b587c925c1cf6ce0a1f364dd9a3708ce1e015f6", - "id": "fls_zCswsyuitexI", - "link": "patterns.html#fls_zCswsyuitexI", - "number": "5.5:4" + "checksum": "526646cd5391985fdbc176a363fbee22aff06d3523b542342418b6e8503f0e22", + "id": "fls_hH58ftCxBYzm", + "link": "types-and-traits.html#fls_hH58ftCxBYzm", + "number": "4.12.7:63" }, { - "checksum": "a7a4f59c59d6e146496c10f6e1dd0db5adf148faf1242b54a905805871e1fefb", - "id": "fls_wJ9f906BlBvg", - "link": "patterns.html#fls_wJ9f906BlBvg", - "number": "5.5:5" + "checksum": "c22d990803ea4ee571303d09a964df44019da966c6d9a8cba436ae48cc9437c9", + "id": "fls_aiSI99pbAYqT", + "link": "types-and-traits.html#fls_aiSI99pbAYqT", + "number": "4.12.7:64" }, { - "checksum": "c1c27a19deb008cded1ee11e11c906c699c4a1a086d847190fd58dcc97f4785c", - "id": "fls_hF19K8sWU8FF", - "link": "patterns.html#fls_hF19K8sWU8FF", - "number": "5.5:6" + "checksum": "57584da05d3dc1e8f0c830d364af56f2bb21b7f6a8a932fd90cc629d6f3de8c6", + "id": "fls_zRdxowO4eDMN", + "link": "types-and-traits.html#fls_zRdxowO4eDMN", + "number": "4.12.7:65" }, { - "checksum": "e3791cb88451803cd0eccbbf5f370fc3c972abe87f321825dc692aacb617742b", - "id": "fls_bv9psmitxfuw", - "link": "patterns.html#fls_bv9psmitxfuw", - "number": "5.5:7" + "checksum": "67e11b06e517777ca3b49bb1108497bbf9f41dd3f7a6c03b9d584e8cd270105d", + "id": "fls_IoceMi7HfqsK", + "link": "types-and-traits.html#fls_IoceMi7HfqsK", + "number": "4.12.7:66" }, { - "checksum": "7f7d5841b9bc8e1f3eca2f3f52fb1c78bb016e93d8212e3225a673fd4bc4b053", - "id": "fls_sl47k9oj5p7t", - "link": "patterns.html#fls_sl47k9oj5p7t", - "number": "5.5:8" + "checksum": "6a0bf506b4c1329bf0fd129c93e06a2ee1d0d06fcc05379daa9446f90b84030e", + "id": "fls_lDkPMB5UI58B", + "link": "types-and-traits.html#fls_lDkPMB5UI58B", + "number": "4.12.7:67" }, { - "checksum": "e7dd58da27f566ab9464de185e98749b36b5c3340a8c22fed6be8e199d11dd91", - "id": "fls_cfoy86mkmqa4", - "link": "patterns.html#fls_cfoy86mkmqa4", - "number": "5.5:9" + "checksum": "37e26fff3428ba52d2fe60fe551dfdbf498213a5d36c669cda5b1014a30cad85", + "id": "fls_JKZHF3ZDHshw", + "link": "types-and-traits.html#fls_JKZHF3ZDHshw", + "number": "4.12.7:68" }, { - "checksum": "2a1df52ca100bd83979fa4b150b96d836a748c8114e32462390076966acbb005", - "id": "fls_rnppz6y5z8pi", - "link": "patterns.html#fls_rnppz6y5z8pi", - "number": "5.5:10" + "checksum": "f672813590c7e0fa2c72a85763bf792b275fbea9cd74a4607794cb8f9ac171f2", + "id": "fls_rT6zpG3cYhaF", + "link": "types-and-traits.html#fls_rT6zpG3cYhaF", + "number": "4.12.7:69" }, { - "checksum": "686e4dfe42f5f913ee2465c883ade027d7e59e9e065e490993681639adbc6114", - "id": "fls_ag6m4mvpturw", - "link": "patterns.html#fls_ag6m4mvpturw", - "number": "5.5:11" + "checksum": "8fece354838a30ef4ea331708e12dc54c63518c2e2b4a4e072f377569a53b15f", + "id": "fls_UFMyHzk6ucsT", + "link": "types-and-traits.html#fls_UFMyHzk6ucsT", + "number": "4.12.7:70" }, { - "checksum": "0156b2335414d9b4db2b75d75ed55fbca446bd1b5b81604e0171ea7872fd44a2", - "id": "fls_pedy2pqrvnx7", - "link": "patterns.html#fls_pedy2pqrvnx7", - "number": "5.5:12" + "checksum": "bde009024b27bfe5ac2165324303983849b296af03f1d09a999b6ea05b2e33f1", + "id": "fls_hPZmcfQiNasT", + "link": "types-and-traits.html#fls_hPZmcfQiNasT", + "number": "4.12.7:71" }, { - "checksum": "546530c46d84a01d6017493ea2f9e61645c557cd4ca99d9d518a6145f9ec0367", - "id": "fls_u59rilepu8z9", - "link": "patterns.html#fls_u59rilepu8z9", - "number": "5.5:13" - } - ], - "title": "Path Patterns" - }, - { - "id": "fls_6tl1fx99yn6c", - "informational": false, - "link": "patterns.html#range-patterns", - "number": "5.6", - "paragraphs": [ + "checksum": "18a9afceee9c153ebb83180b204571112295c6c883a85c1be27d367890c63aa6", + "id": "fls_rgC3Iea5p9Kr", + "link": "types-and-traits.html#fls_rgC3Iea5p9Kr", + "number": "4.12.7:72" + }, { - "checksum": "959fe2ad28f3934a1ce306646ec29b82f95de36f84a5c69840957aeac1b88eb9", - "id": "fls_okupyoav13rm", - "link": "patterns.html#fls_okupyoav13rm", - "number": "5.6:1" + "checksum": "81163aef39eafdb0708b75f24ba80cb5182c0ef48bed2d3cf8feba5b9bef6f95", + "id": "fls_5jDBPymVKzDv", + "link": "types-and-traits.html#fls_5jDBPymVKzDv", + "number": "4.12.7:73" }, { - "checksum": "0b382770b9780b9239f762eb87f95835b03588805b1a4b3def28336d666ff7a1", - "id": "fls_jhchm7dy927k", - "link": "patterns.html#fls_jhchm7dy927k", - "number": "5.6:2" + "checksum": "546efd775e9d74760187551b78d221ef19b4646f33a34b4867f7a7f270798d70", + "id": "fls_f21GNntBOxaz", + "link": "types-and-traits.html#fls_f21GNntBOxaz", + "number": "4.12.7:74" }, { - "checksum": "1cfc9d659ff30640a5179ad40746d5c08c3c187b5271f28b88305b4de6ba138a", - "id": "fls_q86j23iiqv8w", - "link": "patterns.html#fls_q86j23iiqv8w", - "number": "5.6:3" + "checksum": "a5590b6dee25c97033f7305a0e6f936e9da7265aea3829f4c2e7c58e4d7884a5", + "id": "fls_NpwLzJJH9cGw", + "link": "types-and-traits.html#fls_NpwLzJJH9cGw", + "number": "4.12.7:75" }, { - "checksum": "b49f2f20e78bc39e912613682452652bf00d8a9b7ad85fcbbeef325cc638e298", - "id": "fls_3PyquOKjA7SI", - "link": "patterns.html#fls_3PyquOKjA7SI", - "number": "5.6:4" + "checksum": "a041f706bb15d0161c35bfcae72a7de2e0b4e11a3e262fb649508ed8ada51bd7", + "id": "fls_56J8BlLOuvr4", + "link": "types-and-traits.html#fls_56J8BlLOuvr4", + "number": "4.12.7:76" + }, + { + "checksum": "69715763744f3c7fed67ba5ddc98229c812a0800cf36b28ee12f43fb82bf9a42", + "id": "fls_jK2pIVxOmtJ8", + "link": "types-and-traits.html#fls_jK2pIVxOmtJ8", + "number": "4.12.7:77" }, { - "checksum": "2730f4be7e58310acdcc2e8df0d5792b76b2437850f3bea63f0e94ad25e46eee", - "id": "fls_akf9x5r6e0ta", - "link": "patterns.html#fls_akf9x5r6e0ta", - "number": "5.6:5" + "checksum": "aac5b083447e8a2e273adb8cb2fcc744493827cedc47c65b0b3baedbd7b9743b", + "id": "fls_fjV22WcosNnt", + "link": "types-and-traits.html#fls_fjV22WcosNnt", + "number": "4.12.7:78" }, { - "checksum": "0ba8c1ec907ade4c15c689e735f0a5a8b187b5496f656c893968eca7e5468b54", - "id": "fls_vrpr6ttpfpal", - "link": "patterns.html#fls_vrpr6ttpfpal", - "number": "5.6:6" + "checksum": "330e26cc6685cb1dfa82a84d2178bf60165a81473d6eed85a81862c931f9a731", + "id": "fls_h3OVuCdsKPhV", + "link": "types-and-traits.html#fls_h3OVuCdsKPhV", + "number": "4.12.7:79" }, { - "checksum": "656232ce77042076d43593f3f21939aac2c7066d0c750836345ff9ec0a42124b", - "id": "fls_nk48gregn3me", - "link": "patterns.html#fls_nk48gregn3me", - "number": "5.6:7" + "checksum": "1a0181d42cf641e33221d4f38b0852492555c4ac8e15be51f143586d6b985c1b", + "id": "fls_be2djziKJw3I", + "link": "types-and-traits.html#fls_be2djziKJw3I", + "number": "4.12.7:80" }, { - "checksum": "6316acea87cf2eeeb7b2e0acb3d99f9f56716dc64ba70e167716659033b9bde4", - "id": "fls_83v1xqbebs58", - "link": "patterns.html#fls_83v1xqbebs58", - "number": "5.6:8" + "checksum": "ec677f1076b9878644693b68b12fa4f88ab646f06cf6b311b260e4da1befb4b5", + "id": "fls_nHt0LVSiwTB3", + "link": "types-and-traits.html#fls_nHt0LVSiwTB3", + "number": "4.12.7:81" }, { - "checksum": "785db35c5291babacd0f8648588325297f8f45c2eebe7fb665e52b8ac76eca68", - "id": "fls_2hpuccwh2xml", - "link": "patterns.html#fls_2hpuccwh2xml", - "number": "5.6:9" + "checksum": "2a6207f713e77bf4b36f34f67ee44602ec627d4216450a3fd15002b80806184f", + "id": "fls_sLCBZ3vG1AWs", + "link": "types-and-traits.html#fls_sLCBZ3vG1AWs", + "number": "4.12.7:82" }, { - "checksum": "0c9d1f2587c2e5d8ed9ee166a68762909b9b7936245dc9d2341c1f7df6670e6e", - "id": "fls_9kk81isk0mlp", - "link": "patterns.html#fls_9kk81isk0mlp", - "number": "5.6:10" + "checksum": "7bfe1b4191cc568f4965ea2ae5ca690ed51993b7fc4ec6bee03da8ea64653fb7", + "id": "fls_mCISAdm7sjRs", + "link": "types-and-traits.html#fls_mCISAdm7sjRs", + "number": "4.12.7:83" }, { - "checksum": "7254a7b52add01e9d7d977900c9322544a9970def2f7c73fdb9a63b417574f5c", - "id": "fls_8bdOqkO1NuJW", - "link": "patterns.html#fls_8bdOqkO1NuJW", - "number": "5.6:11" + "checksum": "6b0b0bc2f95b49ae5e152da970bce05f896dbf296e9728753d1d673ae72cb4c7", + "id": "fls_Fv8fj9R8prUV", + "link": "types-and-traits.html#fls_Fv8fj9R8prUV", + "number": "4.12.7:84" }, { - "checksum": "88a5cfae2ddc1c38d1df4a2dc1b86e99bcced79bc5b40afef543633e7c48bce9", - "id": "fls_s2b5n4snc4d7", - "link": "patterns.html#fls_s2b5n4snc4d7", - "number": "5.6:12" + "checksum": "84a7593ed63e9466545fae000a156e8e448806170dea5f772cb868f68f526def", + "id": "fls_QDWVv2nTufX7", + "link": "types-and-traits.html#fls_QDWVv2nTufX7", + "number": "4.12.7:85" }, { - "checksum": "b55a395def7ce875a26f0fe8a3f186342864481fa72edba9c13c5d5f3f93c9e6", - "id": "fls_4o4ge6x9a8rs", - "link": "patterns.html#fls_4o4ge6x9a8rs", - "number": "5.6:13" + "checksum": "4e795302ddd133846579329661e9e97b490f86e981444546b66b4dd9022abb89", + "id": "fls_O2r51Xrmmj38", + "link": "types-and-traits.html#fls_O2r51Xrmmj38", + "number": "4.12.7:86" }, { - "checksum": "fe437f18136d4053707f9c2e3b8a79e527d78c77863abc6bfd04f4a73204d3b5", - "id": "fls_6o995ak4hywq", - "link": "patterns.html#fls_6o995ak4hywq", - "number": "5.6:14" + "checksum": "4d4e567b4ab8ca5f4a1fb1c8c3d8df91c67fb770bd4a4cace6b5edb67fac789c", + "id": "fls_b96Zca6oFn82", + "link": "types-and-traits.html#fls_b96Zca6oFn82", + "number": "4.12.7:87" }, { - "checksum": "5d22fc340f1c7e324df48a970cf5ae8a4811f8a5ad3880704eda257dd3d2add8", - "id": "fls_3js1645tgh31", - "link": "patterns.html#fls_3js1645tgh31", - "number": "5.6:15" + "checksum": "c4b02ef7e1b298b5a276082e81cd8daa172fdc32e30770b173dd4a6e774155ba", + "id": "fls_07AIc06bGnZt", + "link": "types-and-traits.html#fls_07AIc06bGnZt", + "number": "4.12.7:88" }, { - "checksum": "d004b00ad56daa028bd112c7ad04042c90cfb4ab5ad4df095a5a9d01f32c4bba", - "id": "fls_8Q6NfRx4j5V7", - "link": "patterns.html#fls_8Q6NfRx4j5V7", - "number": "5.6:16" + "checksum": "402fa34cae898d54c45a23952960408a04d5d8d41b0f81ab618f87b26930cb88", + "id": "fls_A36NBOl1FTCb", + "link": "types-and-traits.html#fls_A36NBOl1FTCb", + "number": "4.12.7:89" }, { - "checksum": "4993d699bb304cc5b32245e03cfd7425d322876b5dc54fef12048c5874c667c0", - "id": "fls_rgr7t33s0m7m", - "link": "patterns.html#fls_rgr7t33s0m7m", - "number": "5.6:17" + "checksum": "a7a2ae8190de32bf5aee8dd116623acd4efd8254bbc6c440a4722d6acb532232", + "id": "fls_h3mmmIBR72kV", + "link": "types-and-traits.html#fls_h3mmmIBR72kV", + "number": "4.12.7:90" }, { - "checksum": "0315d5b53fda43c91943de2a08eda96b79f469004237b8d0f02f52f4c1f35567", - "id": "fls_5ey5mj8t8knd", - "link": "patterns.html#fls_5ey5mj8t8knd", - "number": "5.6:18" + "checksum": "2937ec9b056891de7aab2b57810eba0528ecc27fc3e8075a1f852903bb9b994a", + "id": "fls_8edzBBIo7jF7", + "link": "types-and-traits.html#fls_8edzBBIo7jF7", + "number": "4.12.7:91" }, { - "checksum": "9f6d9020a6778f7a34629150159b8a67764f0aea6638d4f0d47a20fc22fbe70e", - "id": "fls_z4js96mchcsv", - "link": "patterns.html#fls_z4js96mchcsv", - "number": "5.6:19" + "checksum": "4db1070a4a0b985d421c7c4686bb275d07c92be932852e255c6f80818a9a9a35", + "id": "fls_lUg26vFuSePP", + "link": "types-and-traits.html#fls_lUg26vFuSePP", + "number": "4.12.7:92" }, { - "checksum": "5dc529cd76b4f3bba978dfd0fcaf3a618fcaf8f381f22c9f5d63d39585ed34fa", - "id": "fls_3wwpq8i6mo2a", - "link": "patterns.html#fls_3wwpq8i6mo2a", - "number": "5.6:20" - } - ], - "title": "Range Patterns" - }, - { - "id": "fls_d2sc9hl3v0mk", - "informational": false, - "link": "patterns.html#reference-patterns", - "number": "5.7", - "paragraphs": [ - { - "checksum": "0ac86e5a12c490f5eb80008b0e18cc9da39af2074dfbdc99545777192814afa3", - "id": "fls_fhahcc1mz2qh", - "link": "patterns.html#fls_fhahcc1mz2qh", - "number": "5.7:1" + "checksum": "397cf84d1d20fefbdb6416f126bc1fedb49f880fa10fa4a885aad6fa92e26167", + "id": "fls_21ay7EUUUmhx", + "link": "types-and-traits.html#fls_21ay7EUUUmhx", + "number": "4.12.7:93" }, { - "checksum": "028c5b3284521a34ff97d008866bec2c4654a464089a7da864aad3e4a4f04686", - "id": "fls_x0bmzl1315gq", - "link": "patterns.html#fls_x0bmzl1315gq", - "number": "5.7:2" + "checksum": "93b5392211c91ee8d504d9868a7d68ac136546dcdbbad5d24d3f577f2c88e489", + "id": "fls_8VgAhOgDOk0y", + "link": "types-and-traits.html#fls_8VgAhOgDOk0y", + "number": "4.12.7:94" }, { - "checksum": "cdf387e98a664c3724ff90541e30f67670be603da49923fcb4e3a4ee54ba432e", - "id": "fls_fedo8zhgpla5", - "link": "patterns.html#fls_fedo8zhgpla5", - "number": "5.7:3" + "checksum": "7f686eb11bcf39ea892c2d0e96ad372bdc601edb69025cd3e0629e638394539d", + "id": "fls_OVVY9CE0pGtJ", + "link": "types-and-traits.html#fls_OVVY9CE0pGtJ", + "number": "4.12.7:95" }, { - "checksum": "b78639be70162116c61abcb66a81cfa0e2adb505b2a71ffb60f9639cf7a038d5", - "id": "fls_30u9ij164ww3", - "link": "patterns.html#fls_30u9ij164ww3", - "number": "5.7:4" + "checksum": "569bfa594da4cd446bcaad60663a04566c36afc4f5ad3d236f098f50b66fb41d", + "id": "fls_FojOvB6l3lAh", + "link": "types-and-traits.html#fls_FojOvB6l3lAh", + "number": "4.12.7:96" }, { - "checksum": "cc0ee91216508f36e23696e7ee09cd4fa77b065e4c323a2cf3610c891336fe3f", - "id": "fls_d1kc73hpncpo", - "link": "patterns.html#fls_d1kc73hpncpo", - "number": "5.7:5" + "checksum": "f42fbda3021b2099c9a16bf8df1231e066450bcc017fefa22f943451c146644b", + "id": "fls_CVfHkJq1PixR", + "link": "types-and-traits.html#fls_CVfHkJq1PixR", + "number": "4.12.7:97" }, { - "checksum": "f829652e4b19ceb3e612e92c40aa060fe77b46424b5fc3858eccbcfcea45016c", - "id": "fls_mpeuhov0umfa", - "link": "patterns.html#fls_mpeuhov0umfa", - "number": "5.7:6" - } - ], - "title": "Reference Patterns" - }, - { - "id": "fls_7wpgnp4kjq82", - "informational": false, - "link": "patterns.html#rest-patterns", - "number": "5.8", - "paragraphs": [ + "checksum": "f38861e12ba15b26d22b39bced0e85caf806900ac14d4b691b9bfdc04c56006c", + "id": "fls_0RZ7w0YqmzE3", + "link": "types-and-traits.html#fls_0RZ7w0YqmzE3", + "number": "4.12.7:98" + }, { - "checksum": "071b1b905e799a04118d9b0fe22e82b9944ef00a527e80a327c0c4f6511fd52b", - "id": "fls_eso51epfofxb", - "link": "patterns.html#fls_eso51epfofxb", - "number": "5.8:1" + "checksum": "030b457cddbacd3c66ddde1079b1b179afde8cd0c3147430bfce82fddff4d95e", + "id": "fls_YppNCEPMYqWJ", + "link": "types-and-traits.html#fls_YppNCEPMYqWJ", + "number": "4.12.7:99" }, { - "checksum": "d787720d08f2fd60c09276b2b5be62b0bd3a0ad5aef799a71bacba9691897fda", - "id": "fls_5a75a2y43uev", - "link": "patterns.html#fls_5a75a2y43uev", - "number": "5.8:2" + "checksum": "4eb0f7cb1d0475f4762ea3ac14eea3f50eec51fce9d2cd082fa76a05b372905d", + "id": "fls_SZmiJjI43fQL", + "link": "types-and-traits.html#fls_SZmiJjI43fQL", + "number": "4.12.7:100" }, { - "checksum": "3bf650bd8078af8405ff969b3616899e1bc80ef2ef591d2b291cf99f03008f32", - "id": "fls_rsqyza99vl3x", - "link": "patterns.html#fls_rsqyza99vl3x", - "number": "5.8:3" + "checksum": "7b85215a3effc40177a6c31907cde3fa3a7d61b57ff6c0c02e88d5e9ba01bd8e", + "id": "fls_ipWTrhF4xakC", + "link": "types-and-traits.html#fls_ipWTrhF4xakC", + "number": "4.12.7:101" }, { - "checksum": "11486a40e40c1eaa6529d06f4f52771cd804d9d0cea29d215e5e9e6748877b01", - "id": "fls_w1pw40phsv2o", - "link": "patterns.html#fls_w1pw40phsv2o", - "number": "5.8:4" + "checksum": "ba56f2bf9c4b4756691a32ecda47282715bc88ff46cf80ae2f7484635b80aa2c", + "id": "fls_zgRFKbfdI6ro", + "link": "types-and-traits.html#fls_zgRFKbfdI6ro", + "number": "4.12.7:102" }, { - "checksum": "318a513cb1fdf4e37047eccc4975636818c7cd86126e00b5b3e132411c629003", - "id": "fls_x8ylgxrf9ca", - "link": "patterns.html#fls_x8ylgxrf9ca", - "number": "5.8:5" + "checksum": "0cb1d7de2f135ef45509098cda1f169336b740fa28b6108348f05ec0f32936ce", + "id": "fls_z2UkZX5Qdmws", + "link": "types-and-traits.html#fls_z2UkZX5Qdmws", + "number": "4.12.7:103" }, { - "checksum": "de50b523931b5b23b262977da389190179884668be06d4b514d2908f9ad2aeab", - "id": "fls_zgoke73xrhk3", - "link": "patterns.html#fls_zgoke73xrhk3", - "number": "5.8:6" + "checksum": "35723ad98d3805267108e83cb916ccc229ff4ccba59f3dc700de834c2734089b", + "id": "fls_U46IXItEKMCB", + "link": "types-and-traits.html#fls_U46IXItEKMCB", + "number": "4.12.7:104" }, { - "checksum": "3e6c88357b38cf981ef309c03b2df1cb25d9f796365ca21fbbd67f2d4571cd42", - "id": "fls_bdcv6rwx0fsv", - "link": "patterns.html#fls_bdcv6rwx0fsv", - "number": "5.8:7" + "checksum": "2b0d9332d54179fcf153daa53495e5dcc69f8f1fc6183007301ea8d263db458f", + "id": "fls_TAJ3JJwIeDbQ", + "link": "types-and-traits.html#fls_TAJ3JJwIeDbQ", + "number": "4.12.7:105" }, { - "checksum": "c6203fbb28f63565945031f16bc7f7096f16bee4c02f2c16161cb0c3cf80c7a5", - "id": "fls_qz9guhlg19j3", - "link": "patterns.html#fls_qz9guhlg19j3", - "number": "5.8:8" + "checksum": "233f2aa19661d54999f5b48a7b7ceb27a0590d0f09de50a48967716f0de48064", + "id": "fls_VrpaTruoBwtF", + "link": "types-and-traits.html#fls_VrpaTruoBwtF", + "number": "4.12.7:106" } ], - "title": "Rest Patterns" + "title": "Type Inference" }, { - "id": "fls_qte70mgzpras", + "id": "fls_85vx1qfa061i", "informational": false, - "link": "patterns.html#slice-patterns", - "number": "5.9", + "link": "types-and-traits.html#traits", + "number": "4.13", "paragraphs": [ { - "checksum": "f9dbdad2eec41e1e1c60cd8c0cacf14973b904c94631089f41ca0e1bbda98a17", - "id": "fls_qqiu594hki8g", - "link": "patterns.html#fls_qqiu594hki8g", - "number": "5.9:1" + "checksum": "d7212257397d1d14621b886a1829ea8f8c8386cdaccfbfbe7f70c9321f85dff2", + "id": "fls_tani6lesan9u", + "link": "types-and-traits.html#fls_tani6lesan9u", + "number": "4.13:1" }, { - "checksum": "948b35cfd7619032dc69a5d84fa17b28a615e792a6f0e0d49e140816b136e7e9", - "id": "fls_h6x9xlxi7y5n", - "link": "patterns.html#fls_h6x9xlxi7y5n", - "number": "5.9:2" + "checksum": "4dc696ed6dbb85f0cd265718a2213f335ef1caf861c1e2858c1e895b91bd05d8", + "id": "fls_PiAR1B26SoZV", + "link": "types-and-traits.html#fls_PiAR1B26SoZV", + "number": "4.13:2" }, { - "checksum": "b21f2e9bea39902d97b0a75bd48f584b5bcb685635cecfeab096b27a096de184", - "id": "fls_jbmxu7y5fnm6", - "link": "patterns.html#fls_jbmxu7y5fnm6", - "number": "5.9:3" + "checksum": "853f1c4b6645e1f5b164185c45a96a2de4e6ddc84851ab72105e660ab292f120", + "id": "fls_Y28596CVBzDG", + "link": "types-and-traits.html#fls_Y28596CVBzDG", + "number": "4.13:3" }, { - "checksum": "9ea93d82c4cebaa6550a0787f2925118cf5370ed8a0c02104b3a623c197ae9c1", - "id": "fls_r78zzw7yyg34", - "link": "patterns.html#fls_r78zzw7yyg34", - "number": "5.9:4" + "checksum": "9e95c1f9c8e8b1d57a5759c5453c5cb606c711e191df6d096a42e1f8e45b154c", + "id": "fls_AdbbUZZgMEsQ", + "link": "types-and-traits.html#fls_AdbbUZZgMEsQ", + "number": "4.13:4" }, { - "checksum": "93891609dbf9042a20ed470adda1893453d146707a716c6a05793ff9c64e10f0", - "id": "fls_ndor56nou676", - "link": "patterns.html#fls_ndor56nou676", - "number": "5.9:5" + "checksum": "c6afddf3086e634c55738c917ada6174289ada7dde046b2ad1f620e70d67f5e9", + "id": "fls_I9JaKZelMiby", + "link": "types-and-traits.html#fls_I9JaKZelMiby", + "number": "4.13:5" }, { - "checksum": "b7fb9cf107baa52a1c3140dbb24ee079813601a2c74f83321a61e14127737833", - "id": "fls_9yuobz1jsehf", - "link": "patterns.html#fls_9yuobz1jsehf", - "number": "5.9:6" - } - ], - "title": "Slice Patterns" - }, - { - "id": "fls_7dbd5t2750ce", - "informational": false, - "link": "patterns.html#struct-patterns", - "number": "5.10", - "paragraphs": [ - { - "checksum": "d667b4f41510c041296f0f8c9950b33a0e697269e268024bd87aac1d04c4b450", - "id": "fls_vjdkpr3zml51", - "link": "patterns.html#fls_vjdkpr3zml51", - "number": "5.10:1" + "checksum": "b94347bbd83933a75718cc03b215a9d769aa3d6f969c54d90c57fb5f932c1fc9", + "id": "fls_CYtxPjK3zq2T", + "link": "types-and-traits.html#fls_CYtxPjK3zq2T", + "number": "4.13:6" }, { - "checksum": "1226159f94faa8a70839d078914f48a7ec073bb47550a83a77554fab5106550c", - "id": "fls_6o3x101wo478", - "link": "patterns.html#fls_6o3x101wo478", - "number": "5.10:2" + "checksum": "3f9a02739cb12bc30e822ddfcc0c32cfa2a7ba100fca18cf4393b8a82f73ba75", + "id": "fls_ytn5cdonytyn", + "link": "types-and-traits.html#fls_ytn5cdonytyn", + "number": "4.13:7" }, { - "checksum": "4951a056adbfabc5da1900d164468c7f4be7716b2094657ab9e28e53f3b985a9", - "id": "fls_k9zih9s0oe5h", - "link": "patterns.html#fls_k9zih9s0oe5h", - "number": "5.10:3" + "checksum": "850630ec9e0b341a370fae1c5d0089a6199d8a2d37d472dcd8ff712543d65358", + "id": "fls_vucd1u38sq7i", + "link": "types-and-traits.html#fls_vucd1u38sq7i", + "number": "4.13:8" }, { - "checksum": "b68597321297c9f745d6b9be53beda335702f6e28a1bb97a08c277dac931130d", - "id": "fls_r8rat3qmc4hy", - "link": "patterns.html#fls_r8rat3qmc4hy", - "number": "5.10:4" + "checksum": "61a7ac2c1a5f76feec6326da351d7821d5d885c3ddf66d460a3cf531b46961da", + "id": "fls_kyr81mi01me2", + "link": "types-and-traits.html#fls_kyr81mi01me2", + "number": "4.13:9" }, { - "checksum": "c8b65f1ad8bbb7a147b1cfa9636cd2ebe6b61c1727898f08637df63d004a8391", - "id": "fls_hUX723DmLg2a", - "link": "patterns.html#fls_hUX723DmLg2a", - "number": "5.10:5" + "checksum": "e23a38fedc7034747afe6208416e064bfa435ac281aad0629ac7f4ea812f2435", + "id": "fls_YynbrIceKmsJ", + "link": "types-and-traits.html#fls_YynbrIceKmsJ", + "number": "4.13:10" }, { - "checksum": "83183cd23b19214bc0cd0c9da72221ac67d6ec71f26a49c5445307f86a2d7d8c", - "id": "fls_p4OplpUvS04l", - "link": "patterns.html#fls_p4OplpUvS04l", - "number": "5.10:6" + "checksum": "a35062dc24915937215d9047a1f414ade029557a2c46169a0c9a79cc96db0d02", + "id": "fls_Bd4HwdrRuXMm", + "link": "types-and-traits.html#fls_Bd4HwdrRuXMm", + "number": "4.13:11" }, { - "checksum": "0bdc00a13a57ef1c0b6611c62de6e170f3d281e81b68d9cf86a7b8f227180905", - "id": "fls_pre3YwAv01FE", - "link": "patterns.html#fls_pre3YwAv01FE", - "number": "5.10:7" + "checksum": "df1a3afdefc49c32fcd12cc2c3c586b0e8bf3ba1b02491b649c096e25e4a3c1d", + "id": "fls_UzfG5ic8PUIH", + "link": "types-and-traits.html#fls_UzfG5ic8PUIH", + "number": "4.13:12" }, { - "checksum": "81f1732cfd416b1abb73fa22440e8d2cd8f4406e031d6f14d72610567aac5bca", - "id": "fls_MK83WE0iDqNf", - "link": "patterns.html#fls_MK83WE0iDqNf", - "number": "5.10:8" - } - ], - "title": "Struct Patterns" - }, - { - "id": "fls_nruvg0es3kx7", - "informational": false, - "link": "patterns.html#record-struct-patterns", - "number": "5.10.1", - "paragraphs": [ - { - "checksum": "939f66a2cc6a712f7ec20d7536b02ec2600f18a1799b2144aea19eec95022846", - "id": "fls_g6dytd6aq62d", - "link": "patterns.html#fls_g6dytd6aq62d", - "number": "5.10.1:1" + "checksum": "a585409a841ee7361e6fb14891ca557214c6bde16ccd359302549b407e7a771c", + "id": "fls_02D6ku4Sd6yL", + "link": "types-and-traits.html#fls_02D6ku4Sd6yL", + "number": "4.13:13" }, { - "checksum": "62c9ac85366cbfb730c4705821715b95ce875f485a87b252fa3dd810128a62b6", - "id": "fls_3px4oiweg9dm", - "link": "patterns.html#fls_3px4oiweg9dm", - "number": "5.10.1:2" + "checksum": "b612eb84031e77cb9d34f88c555e0396ba5722ba4729a356e68d8b3fb33b3058", + "id": "fls_RLFIzQeAPhG6", + "link": "types-and-traits.html#fls_RLFIzQeAPhG6", + "number": "4.13:14" }, { - "checksum": "e1814d800e0feb6269bf895ed95022e6deb5cc33cc635c2bb63b14baec538159", - "id": "fls_mnh35ehva8tx", - "link": "patterns.html#fls_mnh35ehva8tx", - "number": "5.10.1:3" + "checksum": "8b15c2b4a5b8923f6ec42c9dab38eed453f1bbfdbea8094465a5124ff0ec2e5f", + "id": "fls_avSxO0LEka2x", + "link": "types-and-traits.html#fls_avSxO0LEka2x", + "number": "4.13:15" }, { - "checksum": "3e48a4f72d15cce2c39a5d3dc6ee479532f77da0b1bf93691b99fc840384f6fe", - "id": "fls_p2rjnlbvifaa", - "link": "patterns.html#fls_p2rjnlbvifaa", - "number": "5.10.1:4" + "checksum": "b23001be74c2e310999ac798d2f614d308ee8e1e9b8b7c463397a615dcb3e025", + "id": "fls_ft8axGGOe3aL", + "link": "types-and-traits.html#fls_ft8axGGOe3aL", + "number": "4.13:16" }, { - "checksum": "6645895b56834ed4cc394b448f835a215f06b82857dcc694fa41f1413c2e8780", - "id": "fls_23be2x50at14", - "link": "patterns.html#fls_23be2x50at14", - "number": "5.10.1:5" + "checksum": "2d9fed8708df09f431fc7d1136ad692f13e72f458245e3a972715016cf03cfb6", + "id": "fls_M4EoT1NMyxJS", + "link": "types-and-traits.html#fls_M4EoT1NMyxJS", + "number": "4.13:17" }, { - "checksum": "a4aa45e4d07c9304ffcc9d862e913507d5000a1730b669278ac285dc603a771c", - "id": "fls_46u4ddj0yf93", - "link": "patterns.html#fls_46u4ddj0yf93", - "number": "5.10.1:6" + "checksum": "ee3443dc44a15bfc89d333261e8beae2340b01881208cafec32e48b372342900", + "id": "fls_Tir2kpKNP1KC", + "link": "types-and-traits.html#fls_Tir2kpKNP1KC", + "number": "4.13:18" }, { - "checksum": "7df489a39e2a1942c161d32a9ce2ef5ce0e4848eab53b9ff7321c1d546572c74", - "id": "fls_qu3dvfdq6oy7", - "link": "patterns.html#fls_qu3dvfdq6oy7", - "number": "5.10.1:7" + "checksum": "9c94dc64607d1d1d3d47085d136d875d1b22bd42a1da63f644567c9afb1941da", + "id": "fls_WxHiKr8BGGvz", + "link": "types-and-traits.html#fls_WxHiKr8BGGvz", + "number": "4.13:19" }, { - "checksum": "b07b1a9330e57dc967b57e196fbc8f1a408952860995a438d3d757900c0a9bb5", - "id": "fls_4b2hchdzv30u", - "link": "patterns.html#fls_4b2hchdzv30u", - "number": "5.10.1:8" + "checksum": "cf5ca60e94e50f04ef2036f964845c07eebcc1218b99dc55e644c63fc7e21699", + "id": "fls_mjg7yrq66hh0", + "link": "types-and-traits.html#fls_mjg7yrq66hh0", + "number": "4.13:20" }, { - "checksum": "a8132d829bf34f884995af4cb9ae77e246d14ffc39f464dfd0579841244ed1a7", - "id": "fls_9wfizujx0szd", - "link": "patterns.html#fls_9wfizujx0szd", - "number": "5.10.1:9" + "checksum": "5791023029771fef296097ed718e040eeb95b76d6aee5549bf5621b22838e5e0", + "id": "fls_ydowwijzirmm", + "link": "types-and-traits.html#fls_ydowwijzirmm", + "number": "4.13:21" + } + ], + "title": "Traits" + }, + { + "id": "fls_4ikc07mfrez5", + "informational": false, + "link": "types-and-traits.html#object-safety", + "number": "4.13.1", + "paragraphs": [ + { + "checksum": "a7dbfe3171723b462343aa8888eb93779e47e2cb6564c4d0b5409518c3d3d3f9", + "id": "fls_lrdki56hpc3k", + "link": "types-and-traits.html#fls_lrdki56hpc3k", + "number": "4.13.1:1" }, { - "checksum": "6e903cc0782eee63efea8b0c7d9bd83aaeca3c3659c72b4f1b64b24dbae63273", - "id": "fls_jTh9Hur0qsIb", - "link": "patterns.html#fls_jTh9Hur0qsIb", - "number": "5.10.1:10" + "checksum": "663e533be7fe2c58643b5cb29f1fafabecaebe9a67814b0c45b7339f9d6bce98", + "id": "fls_5wlltclogfkw", + "link": "types-and-traits.html#fls_5wlltclogfkw", + "number": "4.13.1:2" }, { - "checksum": "c8519c37bf9a85c68075db3cb98c5ff8ab8db77174740ac05497368044b20534", - "id": "fls_as54u97xis8z", - "link": "patterns.html#fls_as54u97xis8z", - "number": "5.10.1:11" + "checksum": "4f932d4a55167c3225e7142ff450c023aa8ab9c2ffd3b2a47def283f52f5f94e", + "id": "fls_droy0w5gtqaw", + "link": "types-and-traits.html#fls_droy0w5gtqaw", + "number": "4.13.1:3" }, { - "checksum": "3515e0658c65c83d72e0dd7ad0e82d86216d338c31040d3e27832951a2aa83b5", - "id": "fls_8364ueejn5y3", - "link": "patterns.html#fls_8364ueejn5y3", - "number": "5.10.1:12" + "checksum": "7f14d5ddfe3e2040c798b3f2d8f25e11b0f81f91269a2671aba43d70a8af5063", + "id": "fls_46gd1q80c6bn", + "link": "types-and-traits.html#fls_46gd1q80c6bn", + "number": "4.13.1:4" }, { - "checksum": "f951f3c203a03b558cf318fb35a3313fa155405830ed6d804fcdea0b828eb716", - "id": "fls_7t0be1w2hq3c", - "link": "patterns.html#fls_7t0be1w2hq3c", - "number": "5.10.1:13" + "checksum": "9d15230a9c2fcfdf8a4f47962a5d4e7dcb5bb898443fee71fc2974ce3fa2df1b", + "id": "fls_kwo4cknx0yat", + "link": "types-and-traits.html#fls_kwo4cknx0yat", + "number": "4.13.1:5" }, { - "checksum": "6103ce2aaa63552db1ddd1712d330355e209efd8b3f05e2ee118128d79c0f57a", - "id": "fls_3vgmkm2mzwwy", - "link": "patterns.html#fls_3vgmkm2mzwwy", - "number": "5.10.1:14" + "checksum": "8650f7fa66fca7c39de1d48ec9b57fd7ee9a3c6fbfc7e0a085c6195fe404817e", + "id": "fls_vmLLL82EQasI", + "link": "types-and-traits.html#fls_vmLLL82EQasI", + "number": "4.13.1:6" }, { - "checksum": "b94ca5c69e41ecdd7da89cf82764d0e4561528548bd0a3491afd0dcc1dc27452", - "id": "fls_m91ith3rjy79", - "link": "patterns.html#fls_m91ith3rjy79", - "number": "5.10.1:15" + "checksum": "5711109d6669d3b8bf4917e6183c03969ae5401cf0c18e9b48a3ed6232f13fd1", + "id": "fls_uixekv82g2e5", + "link": "types-and-traits.html#fls_uixekv82g2e5", + "number": "4.13.1:7" }, { - "checksum": "b71b81a6c560dbce28bfa29908e6476ea8fd75a9b27df354ac6d310149124194", - "id": "fls_c09jf2vpcr58", - "link": "patterns.html#fls_c09jf2vpcr58", - "number": "5.10.1:16" + "checksum": "e083235f159e108c388da479f4936b36dd2295363c8b91b7e278346774955c83", + "id": "fls_72tvfoemwpyy", + "link": "types-and-traits.html#fls_72tvfoemwpyy", + "number": "4.13.1:8" }, { - "checksum": "cb358844aa1dd3a9c5395b092c62c4e62c28a58e0b909d2c9d99344e409fafac", - "id": "fls_4h00oqypa8qg", - "link": "patterns.html#fls_4h00oqypa8qg", - "number": "5.10.1:17" + "checksum": "2617716de591dde4480a295b243e0b0c274432a579cc5786fce8def72047c033", + "id": "fls_j7nb34o87l1z", + "link": "types-and-traits.html#fls_j7nb34o87l1z", + "number": "4.13.1:9" }, { - "checksum": "2406df736b1c1b7e052beb8d6a704041b3804de21cf83ac7e64bb84ea5c0180f", - "id": "fls_195mqijyrnam", - "link": "patterns.html#fls_195mqijyrnam", - "number": "5.10.1:18" + "checksum": "45afe066d5ef3c3b31decbde94b80e2c45c5c03be74022b5c37133f162b2544b", + "id": "fls_k1vc9vd8at92", + "link": "types-and-traits.html#fls_k1vc9vd8at92", + "number": "4.13.1:10" }, { - "checksum": "685de37c2ee81a66dccabb7f81942c82479d896f00e1e1334d4a60a9ea3b6bdc", - "id": "fls_ta0vdoqmt2k1", - "link": "patterns.html#fls_ta0vdoqmt2k1", - "number": "5.10.1:19" + "checksum": "d1ce3e872cb3cdd9970e74966c197ed43066d9c21e6b962361276044a54ee9fa", + "id": "fls_kqylg31sm5wv", + "link": "types-and-traits.html#fls_kqylg31sm5wv", + "number": "4.13.1:11" }, { - "checksum": "c02d71a02dfad46132450d2be620a7cabbd2eabf47c6bd9b01945121cd5d3aa0", - "id": "fls_f0u0j4q90lpl", - "link": "patterns.html#fls_f0u0j4q90lpl", - "number": "5.10.1:20" - }, + "checksum": "b43aecbed94e34ebbf059fd7dd52fc451df0deec59c26167f70142669b8ca293", + "id": "fls_aer3gaur7avp", + "link": "types-and-traits.html#fls_aer3gaur7avp", + "number": "4.13.1:12" + } + ], + "title": "Object Safety" + }, + { + "id": "fls_jeoas4n6su4", + "informational": false, + "link": "types-and-traits.html#trait-and-lifetime-bounds", + "number": "4.14", + "paragraphs": [ { - "checksum": "b0f4dad87afccd06af733d465607f36dcde450f57438e710856d594b3dfb6e8d", - "id": "fls_8bi8q3usubby", - "link": "patterns.html#fls_8bi8q3usubby", - "number": "5.10.1:21" + "checksum": "5d702214fefa6f0a53043997a796eb9c5880872b5e01081e4b326f34b335437b", + "id": "fls_5g508z6c7q5f", + "link": "types-and-traits.html#fls_5g508z6c7q5f", + "number": "4.14:1" }, { - "checksum": "da482f0aac1389101e1acc7d29df971ea52cbb5a914a549589717f367179ec59", - "id": "fls_1x0o71kxj3yq", - "link": "patterns.html#fls_1x0o71kxj3yq", - "number": "5.10.1:22" + "checksum": "9d3437b59c9ab4d5061e5c413b3f9748921ef9527b71e24efda1f586edca9e1d", + "id": "fls_BqLPVaSyyXRG", + "link": "types-and-traits.html#fls_BqLPVaSyyXRG", + "number": "4.14:2" }, { - "checksum": "d75c1d570151a0c0add5bd9dbd8879ab364860d21ddd5a1981f20b5a358d8ac6", - "id": "fls_1thgpx95lfg5", - "link": "patterns.html#fls_1thgpx95lfg5", - "number": "5.10.1:23" + "checksum": "0f8fed9095b2593b2ce110d5083b1d4174edfd4d58da66a283c60dabbfa46666", + "id": "fls_grby8tmmd8sb", + "link": "types-and-traits.html#fls_grby8tmmd8sb", + "number": "4.14:3" }, { - "checksum": "014416678ebb189d6688fb5242e5205e11770c286ca59a48f79dda7f6ae4a8b1", - "id": "fls_rpo1wimbmzhc", - "link": "patterns.html#fls_rpo1wimbmzhc", - "number": "5.10.1:24" + "checksum": "f4c5aa350fe7996e2be77424186b6514c7bdc0bed572827e1783a11b5551e07a", + "id": "fls_knut10hoz6wc", + "link": "types-and-traits.html#fls_knut10hoz6wc", + "number": "4.14:4" }, { - "checksum": "5dfa02c07c1e043d1fbc3b8eab7d4555147d7e61752f59b311c2486f4a53c2ef", - "id": "fls_brhtaaxt1s3s", - "link": "patterns.html#fls_brhtaaxt1s3s", - "number": "5.10.1:25" + "checksum": "4faa1836d2f6f8eecc49eaa8a8b5a46d717231f2dd398d4590d48ee4e14c5986", + "id": "fls_sf6zg0ez9hbb", + "link": "types-and-traits.html#fls_sf6zg0ez9hbb", + "number": "4.14:5" }, { - "checksum": "bc8d158c05838e267c985dd76e4808b795b9c9b82d65690a8d2744bf8753e87e", - "id": "fls_jwz3arnfkxwn", - "link": "patterns.html#fls_jwz3arnfkxwn", - "number": "5.10.1:26" + "checksum": "838585a97e19cda475b2d3a30b265a5e45814e4e6fed4a7f4273c944223c6e97", + "id": "fls_vujl3fblz6x2", + "link": "types-and-traits.html#fls_vujl3fblz6x2", + "number": "4.14:6" }, { - "checksum": "cd32c48d96ca8821d8b097e61f7031858e9955ae4cb198bb1001a71624f77a7f", - "id": "fls_pfz8xlwezbw1", - "link": "patterns.html#fls_pfz8xlwezbw1", - "number": "5.10.1:27" + "checksum": "c2729c9c6fa8817169c4c6b3ee4585e3518a521decde9f3a96bfa7d4313c831a", + "id": "fls_AzuZmR9DXSQh", + "link": "types-and-traits.html#fls_AzuZmR9DXSQh", + "number": "4.14:7" }, { - "checksum": "892be146ad066dad2bd25128cc3091054cbd49abdf7268f54e6f21339362e750", - "id": "fls_XFKBJZe6k1o2", - "link": "patterns.html#fls_XFKBJZe6k1o2", - "number": "5.10.1:28" + "checksum": "05bc50eddffd3a18ed8f3377b123bb37c1b5e94af198ccfd7de28fe31d741de9", + "id": "fls_1Sm2Yq1Ow76f", + "link": "types-and-traits.html#fls_1Sm2Yq1Ow76f", + "number": "4.14:8" }, { - "checksum": "3dda2685fefad7736c09572d77850f39aea37aff6a75b4775df949965c478004", - "id": "fls_mu166csowj71", - "link": "patterns.html#fls_mu166csowj71", - "number": "5.10.1:29" + "checksum": "2329dd8b3848ee98473bcfcb998f59926c182d917d53d0b5c562e486ce7905d9", + "id": "fls_tx4uspewnk7w", + "link": "types-and-traits.html#fls_tx4uspewnk7w", + "number": "4.14:9" }, { - "checksum": "ae7dbf4db5d0804bb1b4f47f1a3d6ac4f752d00aacb5fd25e7d8f748f4760adc", - "id": "fls_y09fygnglu3n", - "link": "patterns.html#fls_y09fygnglu3n", - "number": "5.10.1:30" + "checksum": "88ab9938b63b54586e5a194cda9b4fd9238fdc4204039efa73640037b90e8336", + "id": "fls_5kj8bmvb8xfc", + "link": "types-and-traits.html#fls_5kj8bmvb8xfc", + "number": "4.14:10" }, { - "checksum": "b61a46d5ed3d3618df19128c327bc64de735101d00f7fc3b645d60b73ded72dd", - "id": "fls_2tadaatmauzk", - "link": "patterns.html#fls_2tadaatmauzk", - "number": "5.10.1:31" + "checksum": "ea8cafd5ce15c0c1f15675f652353abf992d1d606ef8a4a1543796515a9f5575", + "id": "fls_J9DEsd06Ttu9", + "link": "types-and-traits.html#fls_J9DEsd06Ttu9", + "number": "4.14:11" }, { - "checksum": "d75819f44a8d7f85c50196f3a44487fcbfdaa07c19627675b6bb09b4f2fc97c4", - "id": "fls_oq30xkmvyz72", - "link": "patterns.html#fls_oq30xkmvyz72", - "number": "5.10.1:32" + "checksum": "f2c5ee9dc74cb81bb9b1010d4971b395c10d7ae0b69878d350cb1456826c8e37", + "id": "fls_IfHRxSasGAih", + "link": "types-and-traits.html#fls_IfHRxSasGAih", + "number": "4.14:12" }, { - "checksum": "5f55bfd0214be05b8229314e347c9671c485811ab7606a2c8c5a06c39aa89aae", - "id": "fls_9y1gbv47z23o", - "link": "patterns.html#fls_9y1gbv47z23o", - "number": "5.10.1:33" + "checksum": "4b84d24f6f4b7c87cfc09aa1f59294dadaa53c24876e0fd68f67fc28b88830ad", + "id": "fls_K8nPGP5xbLb7", + "link": "types-and-traits.html#fls_K8nPGP5xbLb7", + "number": "4.14:13" }, { - "checksum": "013fca3143fcb9507c780ebe41cc1f74a40222e55f6c4b695758e3b78c605d12", - "id": "fls_dHtV2BPRFVBB", - "link": "patterns.html#fls_dHtV2BPRFVBB", - "number": "5.10.1:34" + "checksum": "504b10589747b39626c8009a403ca0deba66fe396ad2de9be37b123b48dc0c99", + "id": "fls_Uw6y5ZlaK6RY", + "link": "types-and-traits.html#fls_Uw6y5ZlaK6RY", + "number": "4.14:14" }, { - "checksum": "af8c99976d128dfd24b2788359de45493a9f94be7c7f89b9a4f4f5864bdbf1b0", - "id": "fls_zRCiKnhQebyp", - "link": "patterns.html#fls_zRCiKnhQebyp", - "number": "5.10.1:35" + "checksum": "b42f5355d699fc50f184c1c645f88e77a6a11c2137fc64204d96f8c69dbcf6d1", + "id": "fls_OnyR0Wsfk7KI", + "link": "types-and-traits.html#fls_OnyR0Wsfk7KI", + "number": "4.14:15" + } + ], + "title": "Trait and Lifetime Bounds" + }, + { + "id": "fls_yqcygq3y6m5j", + "informational": false, + "link": "types-and-traits.html#lifetimes", + "number": "4.14.1", + "paragraphs": [ + { + "checksum": "89e96b4228d15b001e337309f76928a584c677698e9d8f56c3fd0bf4ef702172", + "id": "fls_nne91at3143t", + "link": "types-and-traits.html#fls_nne91at3143t", + "number": "4.14.1:1" }, { - "checksum": "c268e10c795ada4873656e9f0fab449fea0c4a36994b5b024693b3b6bb4ee3a7", - "id": "fls_D5tAGzrjXFTu", - "link": "patterns.html#fls_D5tAGzrjXFTu", - "number": "5.10.1:36" + "checksum": "b2051972bf09f0bc6966717b054dfab460bc29d92a31477488c0f18205bac5d0", + "id": "fls_vbclxg9dq4yo", + "link": "types-and-traits.html#fls_vbclxg9dq4yo", + "number": "4.14.1:2" }, { - "checksum": "16873908fc7b0e081b3a93d248479b79ce08a2c8d800fe5b80a55f81b3507300", - "id": "fls_FhvMzLPRlY7p", - "link": "patterns.html#fls_FhvMzLPRlY7p", - "number": "5.10.1:37" + "checksum": "cb7a8e0c24c9740e97f2f5acc1326c7e1f22108d641e9cc3a0ce60f0f8be917c", + "id": "fls_gcszhqg6hnva", + "link": "types-and-traits.html#fls_gcszhqg6hnva", + "number": "4.14.1:3" } ], - "title": "Record Struct Patterns" + "title": "Lifetimes" }, { - "id": "fls_vlrto778v49m", + "id": "fls_ikfvbeewame7", "informational": false, - "link": "patterns.html#tuple-struct-patterns", - "number": "5.10.2", + "link": "types-and-traits.html#subtyping-and-variance", + "number": "4.14.2", "paragraphs": [ { - "checksum": "398db4748805660067dff91c2d8c07f0ce9a02f750eedab4d786b7097ea8a772", - "id": "fls_ks6y1syab2bp", - "link": "patterns.html#fls_ks6y1syab2bp", - "number": "5.10.2:1" + "checksum": "71b5298b738eb859f8cc6f0ef4b99931284871ae39f1bdc321d865a23684d1cd", + "id": "fls_atq2cltx487m", + "link": "types-and-traits.html#fls_atq2cltx487m", + "number": "4.14.2:1" + }, + { + "checksum": "737d3ffccd43df4591537722f4458fda52a59fc66125c7bfefb7f5640a36a8bc", + "id": "fls_df87d44kgwcv", + "link": "types-and-traits.html#fls_df87d44kgwcv", + "number": "4.14.2:2" + }, + { + "checksum": "9ad41a73cca1f1351639e50cba9344d86cdc286da8a2b7f88955ec691b73c9b7", + "id": "fls_7ex941yysuhq", + "link": "types-and-traits.html#fls_7ex941yysuhq", + "number": "4.14.2:3" + }, + { + "checksum": "ddfa75fd9e0142c3c367eb0ba02a65e7221d9e86a4ddef947ebbcfa2bf8a4f60", + "id": "fls_7qud6i05ze2", + "link": "types-and-traits.html#fls_7qud6i05ze2", + "number": "4.14.2:4" + }, + { + "checksum": "23cf56704ab7dff0f1809d6e8435b4c569536305b94f1a7f05e5b58529ee4a7d", + "id": "fls_wpm0p0gtctvi", + "link": "types-and-traits.html#fls_wpm0p0gtctvi", + "number": "4.14.2:5" }, { - "checksum": "c49ac22e3f94f28c61a3b041503608d3e3f6a337ad9fdd9162106b3e8a729ddd", - "id": "fls_t1mrijw16k9a", - "link": "patterns.html#fls_t1mrijw16k9a", - "number": "5.10.2:2" + "checksum": "74f5a55b32bebc82b21a7e2a9db5f21b1c14e6be7e935138309af650bb1b5ee1", + "id": "fls_3rfs58i2kg6l", + "link": "types-and-traits.html#fls_3rfs58i2kg6l", + "number": "4.14.2:6" }, { - "checksum": "caed82759506953820cc05207e2ff166b8c73479f82882f1d52cec340b5ea5f2", - "id": "fls_ryfcrqrkp28y", - "link": "patterns.html#fls_ryfcrqrkp28y", - "number": "5.10.2:3" + "checksum": "1f5b98767e075851c7d070ea2843150c47227558e39872de71e84085c4a044b6", + "id": "fls_kbo3e3bosr0m", + "link": "types-and-traits.html#fls_kbo3e3bosr0m", + "number": "4.14.2:7" }, { - "checksum": "52efa16e2d2072309e596e1e678e1ed4c435a2a10a044d200823d457ae75c86b", - "id": "fls_ehf9r6halgh1", - "link": "patterns.html#fls_ehf9r6halgh1", - "number": "5.10.2:4" + "checksum": "920a8bda29bfda122db8cdb94e70a6c5a4fd76dc0dcc4e6a47f71c256eed960a", + "id": "fls_n36p6w2a75sm", + "link": "types-and-traits.html#fls_n36p6w2a75sm", + "number": "4.14.2:8" }, { - "checksum": "88a50cbe98942f52e03b0825ff7e25f2b0c89e3c3a66979381da5376a170cee7", - "id": "fls_5lo1hs8wzz0t", - "link": "patterns.html#fls_5lo1hs8wzz0t", - "number": "5.10.2:5" + "checksum": "6b1036112f36ab96e0fba76056076e5a4b5cebf3a68ee27c25399aec62dc6107", + "id": "fls_xw7eo3us0ow4", + "link": "types-and-traits.html#fls_xw7eo3us0ow4", + "number": "4.14.2:9" }, { - "checksum": "3dc2817ca063542426c88f985729203dbe054c09d6efe49d7eb52097ec52bca6", - "id": "fls_gwuc2xffosu", - "link": "patterns.html#fls_gwuc2xffosu", - "number": "5.10.2:6" + "checksum": "cf9cf2053b0da4ee4b6530a47cadc73a560b803d10402cde9767cf463797e626", + "id": "fls_qc6jma5g9vpn", + "link": "types-and-traits.html#fls_qc6jma5g9vpn", + "number": "4.14.2:10" }, { - "checksum": "541bd9b4e34b9b13bac861950cbc1ff7a06f887ca4d3a08d65ca538b08a21639", - "id": "fls_w369n8lmwr7g", - "link": "patterns.html#fls_w369n8lmwr7g", - "number": "5.10.2:7" + "checksum": "c31d4d2d2e461e81ba0e0d047ad24e21fe792abb8e791776c1087a6004460b67", + "id": "fls_hpiiwxzg16rj", + "link": "types-and-traits.html#fls_hpiiwxzg16rj", + "number": "4.14.2:11" }, { - "checksum": "ca44a0d983f6a2c58de0d0da866f70042fb1589545be9d179d6b19542bf94f4f", - "id": "fls_4is6h95jj3gd", - "link": "patterns.html#fls_4is6h95jj3gd", - "number": "5.10.2:8" + "checksum": "8b26cdf579eba2baed79aeec149c7a4d4277036b4004f66485b0ecade29cd5ad", + "id": "fls_aspdlqluwh9w", + "link": "types-and-traits.html#fls_aspdlqluwh9w", + "number": "4.14.2:12" }, { - "checksum": "d8b0cbea39dac23dc53d3b24c98a4832ca33e84bd17e7a35598838c1ec69d58b", - "id": "fls_budf0rpsa4lx", - "link": "patterns.html#fls_budf0rpsa4lx", - "number": "5.10.2:9" + "checksum": "cfe83442e8b9e2c97e23204f4e49cab1e49d103de5746c223fe4e5a2f4d47494", + "id": "fls_8ohuze7hqtc1", + "link": "types-and-traits.html#fls_8ohuze7hqtc1", + "number": "4.14.2:13" }, { - "checksum": "f47bc7199534d69e8491c54e3507a6dab7fa624a91bc061b87c451e8ed8af807", - "id": "fls_vo6mtauh4qhb", - "link": "patterns.html#fls_vo6mtauh4qhb", - "number": "5.10.2:10" + "checksum": "5ef125fa78302d1bd0ab1c9c6ce402e84343ce65094c6dbcaa49f0bd9aad81f8", + "id": "fls_7pkqgxabojkn", + "link": "types-and-traits.html#fls_7pkqgxabojkn", + "number": "4.14.2:14" }, { - "checksum": "9e91b8f7ea80554c6c9d801b284fd737f8072273ca2869a7a1a7c23f6cba1e10", - "id": "fls_rco3fwlx2a76", - "link": "patterns.html#fls_rco3fwlx2a76", - "number": "5.10.2:11" + "checksum": "d8c93934a67920a9c041b06a6f58c10bca90f62627301c61c512c531cf54b8cb", + "id": "fls_ln9pqd4xu5e", + "link": "types-and-traits.html#fls_ln9pqd4xu5e", + "number": "4.14.2:15" }, { - "checksum": "435aadfae6890acd5b2f7c089d2bf6c6bde46b865278bc446f816f2900ad444d", - "id": "fls_4vrnxslad09e", - "link": "patterns.html#fls_4vrnxslad09e", - "number": "5.10.2:12" + "checksum": "9cb44289a5c32876d21e093980654ac08ef05f7b857ee5acd5f952af4fe5882d", + "id": "fls_z4jo3ojkcu9v", + "link": "types-and-traits.html#fls_z4jo3ojkcu9v", + "number": "4.14.2:16" }, { - "checksum": "c18b1eaabbf21d6d906c4d4255fe8e1efcf58af3fbebbfffa7348ac6b6edf3c5", - "id": "fls_qgilaqy5zx7q", - "link": "patterns.html#fls_qgilaqy5zx7q", - "number": "5.10.2:13" + "checksum": "cf5c8e900ca37acad0bc171bfa0c6b714f1d23d6b8cb493c3038690fdb8ae839", + "id": "fls_67w6yslr3e25", + "link": "types-and-traits.html#fls_67w6yslr3e25", + "number": "4.14.2:17" }, { - "checksum": "2d585002f71b603779602116d0639fd90c72f6c4832288b134b5a2c57dc36717", - "id": "fls_2u99arsbnlnk", - "link": "patterns.html#fls_2u99arsbnlnk", - "number": "5.10.2:14" - } - ], - "title": "Tuple Struct Patterns" - }, - { - "id": "fls_urbr5rg9206v", - "informational": false, - "link": "patterns.html#tuple-patterns", - "number": "5.10.3", - "paragraphs": [ + "checksum": "288d8383e4a918a8df537e25a4154df2c81df5a683c07e73b4544c524b308ed0", + "id": "fls_ojal3qytkqql", + "link": "types-and-traits.html#fls_ojal3qytkqql", + "number": "4.14.2:18" + }, { - "checksum": "86e5cf86cc1727a1ec3d2c9b3c7770ba2d5a0a0be11e05f181cf89208cef7e8f", - "id": "fls_e2manugp4e0b", - "link": "patterns.html#fls_e2manugp4e0b", - "number": "5.10.3:1" + "checksum": "cfc97b21efeb749f2447127d8a51da98dc6e41cb501744689a98e040ea67ea73", + "id": "fls_owp42z12l4lc", + "link": "types-and-traits.html#fls_owp42z12l4lc", + "number": "4.14.2:19" }, { - "checksum": "968065a0acde4dc1fc6772c9a80d889371d997d4d812bece3bb143b855b791f3", - "id": "fls_xk8udu4k61kj", - "link": "patterns.html#fls_xk8udu4k61kj", - "number": "5.10.3:2" + "checksum": "a734eb2b309a56c538d9dd9dfb1c4826d69b2288840fcb2d3cd674d2e60ef9c2", + "id": "fls_i1vuix3gj9ej", + "link": "types-and-traits.html#fls_i1vuix3gj9ej", + "number": "4.14.2:20" }, { - "checksum": "c046f561eb355602680f839ad87e202a633dd889397bbc12f7027ffc7b5a284a", - "id": "fls_yhcaz6v49ub2", - "link": "patterns.html#fls_yhcaz6v49ub2", - "number": "5.10.3:3" + "checksum": "cad8abc0d81bf2f72b8ee690d6d77d6170b0848acffa5edbfe5137073f871803", + "id": "fls_mlf39pl0b931", + "link": "types-and-traits.html#fls_mlf39pl0b931", + "number": "4.14.2:21" }, { - "checksum": "208f90ce72da5d32c30755d34df67702194f5fe887e9c7a1bd0554c4718eeede", - "id": "fls_6WCm0Ra8NQl4", - "link": "patterns.html#fls_6WCm0Ra8NQl4", - "number": "5.10.3:4" + "checksum": "2215e0ca6e0b0930e0b97655a732b0131268013502b68c2a789b71977bb44466", + "id": "fls_yknymnlsasyw", + "link": "types-and-traits.html#fls_yknymnlsasyw", + "number": "4.14.2:22" }, { - "checksum": "d4a0b43d9d6a5e0b17d67fcdf081a940f340d1b85d971f31c684778d9986fb2b", - "id": "fls_a3qvQjyilORx", - "link": "patterns.html#fls_a3qvQjyilORx", - "number": "5.10.3:5" + "checksum": "9b7e9c4d5c82583cf8a4c06355a662c0499656e0c927ae7d7b2567c8177d654a", + "id": "fls_xkzo7nj40rbn", + "link": "types-and-traits.html#fls_xkzo7nj40rbn", + "number": "4.14.2:23" }, { - "checksum": "289e79602b945e44a6b259d8d0c1e225e914afa943874c7698ef97f89f8f3def", - "id": "fls_KmIHFxlBYelZ", - "link": "patterns.html#fls_KmIHFxlBYelZ", - "number": "5.10.3:6" + "checksum": "e4cda8eaec5f978c1a3e57c6c8af5970fcd1762a4ff06f9b1a68a8106537f318", + "id": "fls_abn5ycx11zpm", + "link": "types-and-traits.html#fls_abn5ycx11zpm", + "number": "4.14.2:24" }, { - "checksum": "bd944455b4e5b2992d60a0276dd284685483fbdb2ce0a6e8b861676e3d2069b7", - "id": "fls_5bXqIaKiFcLg", - "link": "patterns.html#fls_5bXqIaKiFcLg", - "number": "5.10.3:7" + "checksum": "42ce008b0a6cfe49436f4f3897238024485cda9f72a13fbfab9b3d7814f4111f", + "id": "fls_hvfyog9ygn6q", + "link": "types-and-traits.html#fls_hvfyog9ygn6q", + "number": "4.14.2:25" }, { - "checksum": "47877ba9b3984b4661b6db0b34526c8edb770ea1f800c33d64c5523a6d44b8f2", - "id": "fls_soHCAVfGlv5f", - "link": "patterns.html#fls_soHCAVfGlv5f", - "number": "5.10.3:8" + "checksum": "90d2a2db73cec5e5c1430778f7c4c4e671046095294db8fca4b4f056f598ef18", + "id": "fls_mduolmcawb30", + "link": "types-and-traits.html#fls_mduolmcawb30", + "number": "4.14.2:26" }, { - "checksum": "325628fddc5edcfee5dde126d580262949d54d516c4f6d9243baff7c4f607da9", - "id": "fls_iiKvYs61959S", - "link": "patterns.html#fls_iiKvYs61959S", - "number": "5.10.3:9" + "checksum": "f5d848b029abc9525e976478047895ce24e4ac85a7af51e1f425f22ab5340b9b", + "id": "fls_y81gmqweqc9w", + "link": "types-and-traits.html#fls_y81gmqweqc9w", + "number": "4.14.2:27" }, { - "checksum": "80b589c7aaacf22228397ab8de7f859870551fa23bbc58316341c4fe297455b0", - "id": "fls_F4k6ljuP8Amf", - "link": "patterns.html#fls_F4k6ljuP8Amf", - "number": "5.10.3:10" + "checksum": "a7da2fd26ad1dfa3533285dad37d02fed2b4908be213a6a3a98775b0c2ebdb51", + "id": "fls_etgfvgvymn8o", + "link": "types-and-traits.html#fls_etgfvgvymn8o", + "number": "4.14.2:28" }, { - "checksum": "d0a0133555000675455b2bf5ab37681e6e7bb93783c7e8ebd5178f80704e6ce1", - "id": "fls_GjjCDkVJPQS8", - "link": "patterns.html#fls_GjjCDkVJPQS8", - "number": "5.10.3:11" + "checksum": "92ec09eb8c13eb5501dd2aa83335c6b18b661577435da0da11d655d174667b7d", + "id": "fls_4kjxxrsk1igf", + "link": "types-and-traits.html#fls_4kjxxrsk1igf", + "number": "4.14.2:29" }, { - "checksum": "46bd974ba3f88ec8c528de9c9907e74139719b19f51d32a06f37cede033afd44", - "id": "fls_9Qw9N87swwNe", - "link": "patterns.html#fls_9Qw9N87swwNe", - "number": "5.10.3:12" + "checksum": "d15a6c43afbdab395dde7bf007ffaa85c419254890a3a269617d7567fb692360", + "id": "fls_y4zmb3vrym7p", + "link": "types-and-traits.html#fls_y4zmb3vrym7p", + "number": "4.14.2:30" }, { - "checksum": "f1887b73be484d54fe02b6a04ea8aee012209872ae40fb9179a705c3f4856242", - "id": "fls_CQ84wkLyrAJv", - "link": "patterns.html#fls_CQ84wkLyrAJv", - "number": "5.10.3:13" + "checksum": "5375ee031b5992cc2d81fb87c92113aa2df9714381a2516c70a907104f22544f", + "id": "fls_9ae3idezsths", + "link": "types-and-traits.html#fls_9ae3idezsths", + "number": "4.14.2:31" }, { - "checksum": "176644151cc57b936a43b35578e7182a3c89ac881b62ba4e3aa5bfede1008ecf", - "id": "fls_cC6ohNuiltfL", - "link": "patterns.html#fls_cC6ohNuiltfL", - "number": "5.10.3:14" + "checksum": "7a621c5f9277c4f46a52133620c001507d1ffde9ce5bc9b977dae5382a587119", + "id": "fls_WD6uyTCziRac", + "link": "types-and-traits.html#fls_WD6uyTCziRac", + "number": "4.14.2:32" }, { - "checksum": "c479906bca39314417e1caa942e7ef8efbb0987857a2c5ea2e3dc30d69cc4a20", - "id": "fls_8r81vtv5hnrd", - "link": "patterns.html#fls_8r81vtv5hnrd", - "number": "5.10.3:15" - } - ], - "title": "Tuple Patterns" - }, - { - "id": "fls_qfsfnql1t7m", - "informational": false, - "link": "patterns.html#underscore-patterns", - "number": "5.11", - "paragraphs": [ + "checksum": "198b15c57f80ef7b7004d08d14db0126c5a03181c18d4c5c5d9fb54b608cd93a", + "id": "fls_xURR0owesaIE", + "link": "types-and-traits.html#fls_xURR0owesaIE", + "number": "4.14.2:33" + }, { - "checksum": "ffe05e2b6f44aab3204db8f037bcbabda722b0b2a4c36233bd344165b0915664", - "id": "fls_dreny9e0ei6r", - "link": "patterns.html#fls_dreny9e0ei6r", - "number": "5.11:1" + "checksum": "e9ab99af1e3626b5bc2ec4a97eb9b693d57cbf3941d58b50da79a3a97cd65b65", + "id": "fls_CvZNYIfnOUcc", + "link": "types-and-traits.html#fls_CvZNYIfnOUcc", + "number": "4.14.2:34" }, { - "checksum": "512af904f1afbc0ae83e21197bac2017c65eb4d2820eb33ef0e964251753ac99", - "id": "fls_42fye1v0th8l", - "link": "patterns.html#fls_42fye1v0th8l", - "number": "5.11:2" + "checksum": "e44c013173ea636304b228c4399fefdf0e90a39d2d469805209757504b18f9d1", + "id": "fls_xrLYQX7W9OnR", + "link": "types-and-traits.html#fls_xrLYQX7W9OnR", + "number": "4.14.2:35" }, { - "checksum": "4df12d4b4abe0cd1029651c34033e70582bd69ad83cfc6a3a733e4bf20d8da0a", - "id": "fls_b87mvrcc13f2", - "link": "patterns.html#fls_b87mvrcc13f2", - "number": "5.11:3" + "checksum": "319beaaf1f1caf0096dba73af40779542bd6c1e51e16900e8189ad49bbf270b7", + "id": "fls_DWPN8DRysgMa", + "link": "types-and-traits.html#fls_DWPN8DRysgMa", + "number": "4.14.2:36" }, { - "checksum": "7dd5b2d964186c34d5eb0d46902b5bcc8cef29ddc8f75425c634837f9354dbcd", - "id": "fls_j3u6x1ensrbe", - "link": "patterns.html#fls_j3u6x1ensrbe", - "number": "5.11:4" - } - ], - "title": "Underscore Patterns" - }, - { - "id": "fls_qssijtofa9i8", - "informational": false, - "link": "patterns.html#binding-modes", - "number": "5.12", - "paragraphs": [ + "checksum": "0a0c6af41fd048b6c722a4d47422c162b6d1d238eb34928fd9b8d573a4d1e344", + "id": "fls_tOn5oKtp300J", + "link": "types-and-traits.html#fls_tOn5oKtp300J", + "number": "4.14.2:37" + }, { - "checksum": "ce737fd6a8e0011aa5361836e179e7da00b62d001685d8a3099eda2e5f94ff52", - "id": "fls_7xby6d1903kw", - "link": "patterns.html#fls_7xby6d1903kw", - "number": "5.12:1" + "checksum": "b40dd94eaa2e2861c1ac9a47ac10ee9e80b71b3fce42aa4c362c82feaafaf088", + "id": "fls_HqXTVi7gr9wR", + "link": "types-and-traits.html#fls_HqXTVi7gr9wR", + "number": "4.14.2:38" }, { - "checksum": "ee24110781d1935c43cf0567d764e1aadf2cc3db7f17740efe4e767ac0ee9632", - "id": "fls_vnh9wfrvumdz", - "link": "patterns.html#fls_vnh9wfrvumdz", - "number": "5.12:2" + "checksum": "4b83f6d8b60d4cc3f44054e47b3add3b07386dbadb45a3e11809ac73890747d5", + "id": "fls_lxfPvK7NDNlj", + "link": "types-and-traits.html#fls_lxfPvK7NDNlj", + "number": "4.14.2:39" }, { - "checksum": "3689001f3e5a3fadb5bce3f16c76e8babf3aa8a1b12b24dc1d08cc2709b6336c", - "id": "fls_RViC5UEZPQUV", - "link": "patterns.html#fls_RViC5UEZPQUV", - "number": "5.12:3" + "checksum": "a38f8ae59566df6812eb2e4a65493f7869a7ee5d1490a9ea14cca661e002257a", + "id": "fls_awJeqcAHn5O3", + "link": "types-and-traits.html#fls_awJeqcAHn5O3", + "number": "4.14.2:40" }, { - "checksum": "1a8541da9715b497aff0fb8fd6116cd249a0350898d3dd89d00b2c30c7e82671", - "id": "fls_6lXtoxebD5It", - "link": "patterns.html#fls_6lXtoxebD5It", - "number": "5.12:4" + "checksum": "55f986a2532a84d35f03d42bf891d3f758900a1764931afb6e6748f104d30de0", + "id": "fls_AD1IXzsnlwyg", + "link": "types-and-traits.html#fls_AD1IXzsnlwyg", + "number": "4.14.2:41" }, { - "checksum": "0b306a42847f796ecf6339eefc0af21e5781a2cde421ae3141018b146e11ffb0", - "id": "fls_xNxQN8sgpZ3O", - "link": "patterns.html#fls_xNxQN8sgpZ3O", - "number": "5.12:5" + "checksum": "ac51179a778b0f9f9797899d9ef592d947ee84fdc5a06197c3326fcf1bcebda8", + "id": "fls_p2II8E6cc9zt", + "link": "types-and-traits.html#fls_p2II8E6cc9zt", + "number": "4.14.2:42" }, { - "checksum": "0871ae0bb0826a8d223e8c2cee52f184b7c0e39f86c5063fcd676f7fbc614365", - "id": "fls_dqe75i8h2fie", - "link": "patterns.html#fls_dqe75i8h2fie", - "number": "5.12:6" + "checksum": "d108ec536a18cd310c0309f9f0992815ce2c842ccd88899e8bc3b86859afbb92", + "id": "fls_pN5C4FHWEWdK", + "link": "types-and-traits.html#fls_pN5C4FHWEWdK", + "number": "4.14.2:43" }, { - "checksum": "959846317dd64ecd23fd6c93f96608048d818682c7cd48599dfffcd231a8557a", - "id": "fls_y3wuvj1y5j20", - "link": "patterns.html#fls_y3wuvj1y5j20", - "number": "5.12:7" + "checksum": "01dcce0fab7d9e766e601132b4336c4324d2cb983b16cbc83cd69c7fa6031d79", + "id": "fls_RdqV0hI8h8BI", + "link": "types-and-traits.html#fls_RdqV0hI8h8BI", + "number": "4.14.2:44" }, { - "checksum": "edc7cf534c55c29625cd191432a05763797465d54cbd387fee8127a17ad0f1ed", - "id": "fls_55jtzh6a292x", - "link": "patterns.html#fls_55jtzh6a292x", - "number": "5.12:8" + "checksum": "46f39fc8f0d7ac26fb707ada2c7d2bb2adf405a080f461715483c86974008ef6", + "id": "fls_Uv5CcMHPX79J", + "link": "types-and-traits.html#fls_Uv5CcMHPX79J", + "number": "4.14.2:45" }, { - "checksum": "174692122d916648a54e197fff15a1e7bf17984b9523fd06224c71a7484e0445", - "id": "fls_qcaf2kup7zn0", - "link": "patterns.html#fls_qcaf2kup7zn0", - "number": "5.12:9" + "checksum": "8b74cbe1bf4dbd256643c98b2f8a49a0f64cb55a7774638a968c045d642880d1", + "id": "fls_LyvV4pOG7E4l", + "link": "types-and-traits.html#fls_LyvV4pOG7E4l", + "number": "4.14.2:46" + } + ], + "title": "Subtyping and Variance" + }, + { + "id": "fls_l9ebxrlxyawd", + "informational": false, + "link": "types-and-traits.html#lifetime-elision", + "number": "4.14.3", + "paragraphs": [ + { + "checksum": "d79a7a7c2d74efad1b16985dc59f37eab43dacda82826ca7822557c72a0932ec", + "id": "fls_9wtuclhm7yz5", + "link": "types-and-traits.html#fls_9wtuclhm7yz5", + "number": "4.14.3:1" }, { - "checksum": "c9dfbc0519fed7bb3f112777102c928f15a4d5c3cfc13d4d3017e9c15259ce08", - "id": "fls_6acdqz8rwnn", - "link": "patterns.html#fls_6acdqz8rwnn", - "number": "5.12:10" + "checksum": "32d1b793cd39bce611678730276e1b4319fbec6614cd48c07f2d26a1e76e5679", + "id": "fls_JmP6O9zj8fkV", + "link": "types-and-traits.html#fls_JmP6O9zj8fkV", + "number": "4.14.3:2" }, { - "checksum": "61b076c4aa153ed7b1ed996845d08b881a2327bfe131501f20b3ae9b3a2c042d", - "id": "fls_tv0avib387bv", - "link": "patterns.html#fls_tv0avib387bv", - "number": "5.12:11" + "checksum": "6bfff075569653189958ff85ac5cf2713c6e2e10a61755e557a5a274114a10f4", + "id": "fls_5ZAQ9p7jQuc2", + "link": "types-and-traits.html#fls_5ZAQ9p7jQuc2", + "number": "4.14.3:3" }, { - "checksum": "a30925ea032d35a24204cb5c81fc5ad636e77d19ce00fb38615985b1bec396d6", - "id": "fls_dbgmwldye42e", - "link": "patterns.html#fls_dbgmwldye42e", - "number": "5.12:12" + "checksum": "01b2cb5f3106d4540ec427082a3baee106878240cfbf49a243c14625f8e7f690", + "id": "fls_YmUQ8ZiQuycp", + "link": "types-and-traits.html#fls_YmUQ8ZiQuycp", + "number": "4.14.3:4" }, { - "checksum": "e9940ece8b67430f6507527efb4e0837eea8ecd615884b2d4a3773018ca5df88", - "id": "fls_t34oqarwcusu", - "link": "patterns.html#fls_t34oqarwcusu", - "number": "5.12:13" + "checksum": "ed43d8d7ebdf76d78bd1611b62a5a14e51cc7b74a08399dbbe6a89ca09432165", + "id": "fls_sIMN6Sd8xUZz", + "link": "types-and-traits.html#fls_sIMN6Sd8xUZz", + "number": "4.14.3:5" }, { - "checksum": "94de4c12fad70f9cac54c2a50a2c65b9a5d0f42b1847bddbbb2e5c23d1e9917c", - "id": "fls_7gxb74u1np36", - "link": "patterns.html#fls_7gxb74u1np36", - "number": "5.12:14" + "checksum": "e2eeb47e603a2757ced1b1b048a74e8c70eff013d072dae88111c56d15f1a50a", + "id": "fls_dIyisjNIx9dC", + "link": "types-and-traits.html#fls_dIyisjNIx9dC", + "number": "4.14.3:6" }, { - "checksum": "7b6cefba0ed64069e11e48d9eddf7bdab4f5ec7b32d73b3a9c0c92d7fd078d3a", - "id": "fls_7y56d0ulxomf", - "link": "patterns.html#fls_7y56d0ulxomf", - "number": "5.12:15" + "checksum": "c4bf3768ceb2465a2a096c8aef590aee129db81593c92b2b8d8529e5a0e7fdfa", + "id": "fls_cD0ZYi23VqWg", + "link": "types-and-traits.html#fls_cD0ZYi23VqWg", + "number": "4.14.3:7" }, { - "checksum": "7a06ffc02f7927cd15e2513d53f8a618b2100301b066a92e111ba6ce488b6941", - "id": "fls_pxvtqxke1enp", - "link": "patterns.html#fls_pxvtqxke1enp", - "number": "5.12:16" + "checksum": "4eabb3c647de45efeb944bafbeabae6a318a9325155ef389199603ed1e19acc1", + "id": "fls_sA4Lqc5o6cX3", + "link": "types-and-traits.html#fls_sA4Lqc5o6cX3", + "number": "4.14.3:8" } ], - "title": "Binding Modes" + "title": "Lifetime Elision" }, { - "id": "fls_jm6l7b90h6wa", + "id": "fls_hethxxbcg7ja", "informational": false, - "link": "patterns.html#pattern-matching", - "number": "5.13", + "link": "types-and-traits.html#function-lifetime-elision", + "number": "4.14.3.1", "paragraphs": [ { - "checksum": "fed5fae34172da372479806ae83b3c3ee3fddd0115580bd763e26a0af5da0cee", - "id": "fls_zv73CR8rplIa", - "link": "patterns.html#fls_zv73CR8rplIa", - "number": "5.13:1" + "checksum": "8fc35e5cf136f57bafd43ae857a0540f3e6377d540a98f10bf9575e151baba5c", + "id": "fls_lAdIRCFFlydD", + "link": "types-and-traits.html#fls_lAdIRCFFlydD", + "number": "4.14.3.1:1" }, { - "checksum": "6b0c9b448ffa1317860d5ce72327a183542c035ca71f57b637fe069226784ff8", - "id": "fls_tlwr4u7bjhh5", - "link": "patterns.html#fls_tlwr4u7bjhh5", - "number": "5.13:2" + "checksum": "4e446345859b9323669a875dd49ec52cce3e71e7f65700097e1ec21cf6f60506", + "id": "fls_dpudys82dhdc", + "link": "types-and-traits.html#fls_dpudys82dhdc", + "number": "4.14.3.1:2" }, { - "checksum": "4ea2d8ef09b45f966ea91c945da620f5ef84eadc6c24c492c2a79b4140fab595", - "id": "fls_tZJgZDWVChJV", - "link": "patterns.html#fls_tZJgZDWVChJV", - "number": "5.13:3" + "checksum": "a52ef65ad59c6a03acd104f52921c0bd1ab194c0d933c3d187455e8266facccb", + "id": "fls_pjil71kk0r25", + "link": "types-and-traits.html#fls_pjil71kk0r25", + "number": "4.14.3.1:3" }, { - "checksum": "2efb0c843c3347a6968949abf2fca84daa99dd61608b4660accbec71d32cde04", - "id": "fls_62626ws222op", - "link": "patterns.html#fls_62626ws222op", - "number": "5.13:4" + "checksum": "560234cb87c2e6ff7cdbb0d27ca2bc465b9ad0c33b073139a622fc945d297d65", + "id": "fls_1jnn9bsb71k7", + "link": "types-and-traits.html#fls_1jnn9bsb71k7", + "number": "4.14.3.1:4" }, { - "checksum": "c6772d317d3324a285727d769064ce8e2af5e86ef7c2e382aa8518b4bf698356", - "id": "fls_q0z46h1gnzez", - "link": "patterns.html#fls_q0z46h1gnzez", - "number": "5.13:5" + "checksum": "b16b3fc503b0ef9b2bb8a6286c781f2bff6bdc26cebcef598491234908bf0fe3", + "id": "fls_2p29p1fvi182", + "link": "types-and-traits.html#fls_2p29p1fvi182", + "number": "4.14.3.1:5" }, { - "checksum": "57dbb3cb675ba83f531a2f5110697c39a191fb7fa60f0f3c6c7848d2770af6ca", - "id": "fls_1r0vm6rg13o9", - "link": "patterns.html#fls_1r0vm6rg13o9", - "number": "5.13:6" + "checksum": "a87e49e984f97b2802b8b6c2cf73a563f66412b9c089d6731015646d67c30786", + "id": "fls_d4u3y82hdadc", + "link": "types-and-traits.html#fls_d4u3y82hdadc", + "number": "4.14.3.1:6" }, { - "checksum": "6b05b0f396c6a2f7c1c6ed27a9344405d935e0e8c6ea2d8c832886a2ac168357", - "id": "fls_am5h8r887bz5", - "link": "patterns.html#fls_am5h8r887bz5", - "number": "5.13:7" + "checksum": "2733e9f6847d7d23dfbaf715feec476d91d9caef20c3b3b354287dd0c3eebfe1", + "id": "fls_ks8wlufmhz6d", + "link": "types-and-traits.html#fls_ks8wlufmhz6d", + "number": "4.14.3.1:7" }, { - "checksum": "061060573b3a2fa03fecf4917de59898492a684a1b822036866ea0bba7b10b1f", - "id": "fls_eppmiloh7bgg", - "link": "patterns.html#fls_eppmiloh7bgg", - "number": "5.13:8" + "checksum": "3bcbd78a434bf0ba38571a7dd6ed85585dd4e92b865edc90332ecf4b4daaa3bf", + "id": "fls_hsg9kfyvh35m", + "link": "types-and-traits.html#fls_hsg9kfyvh35m", + "number": "4.14.3.1:8" + }, + { + "checksum": "16c08c00ce2a3df58129efe63027ec80880b95b07f01d9fd2c0c7f505d079150", + "id": "fls_ofqy10q4a9jk", + "link": "types-and-traits.html#fls_ofqy10q4a9jk", + "number": "4.14.3.1:9" }, { - "checksum": "67acc5c7291756502e81a1f9f2d7f298408c1401f9f66dda32df1ddd48a62627", - "id": "fls_gwc08xayno7q", - "link": "patterns.html#fls_gwc08xayno7q", - "number": "5.13:9" + "checksum": "90c63f8b0b6e4ccfd99d0308b04e2980a4b53f0de2e3a67cbec4ff8019bc7e2d", + "id": "fls_yofbo96tjppf", + "link": "types-and-traits.html#fls_yofbo96tjppf", + "number": "4.14.3.1:10" }, { - "checksum": "b73206cc6af5294d375b8c5cfa306af1436fc759e88f281b0fb4cda31caf322a", - "id": "fls_19iygu12s315", - "link": "patterns.html#fls_19iygu12s315", - "number": "5.13:10" + "checksum": "d8fc6c8065816c13c111ca417994ff7168f457d619f2299e9be1d36a9eb4e6dd", + "id": "fls_vf7cxiir91ps", + "link": "types-and-traits.html#fls_vf7cxiir91ps", + "number": "4.14.3.1:11" }, { - "checksum": "ef18480cbda2466675a65cc4f5fb16ba8e68efa31c489b887c095d58364c3a01", - "id": "fls_r307spfk6cs9", - "link": "patterns.html#fls_r307spfk6cs9", - "number": "5.13:11" + "checksum": "012616fbf6f9d9c37bc76e01c82464fb6d65c6787d308e10f7e7f8d4eb477716", + "id": "fls_g56br27hq2zj", + "link": "types-and-traits.html#fls_g56br27hq2zj", + "number": "4.14.3.1:12" }, { - "checksum": "7c61fa36bd4eb0e4840994dc694926592d011f93b99f0f7e8516293a35531091", - "id": "fls_qhdofvbso3gl", - "link": "patterns.html#fls_qhdofvbso3gl", - "number": "5.13:12" + "checksum": "c20fd04609ae44c467a3be462def3caaead0ee6b0c11a572851ac3bda8e9e7db", + "id": "fls_1j204m1wy333", + "link": "types-and-traits.html#fls_1j204m1wy333", + "number": "4.14.3.1:13" }, { - "checksum": "f8fbe2a911bd99e50865c967dcaca5bf0fa1430fee37b7fb4b641a1f7d527495", - "id": "fls_drb114dtvlpt", - "link": "patterns.html#fls_drb114dtvlpt", - "number": "5.13:13" + "checksum": "cbcb79548a5d9ac2995323208dcd3a9ccb79c785569231727fae4a32af08e791", + "id": "fls_6km3cbchuxr2", + "link": "types-and-traits.html#fls_6km3cbchuxr2", + "number": "4.14.3.1:14" }, { - "checksum": "094e9ddf51a91e3ddcad2423c543d0e1a3c8c9d3bd97c6d464e2ad41d548f326", - "id": "fls_uxysntb3u03j", - "link": "patterns.html#fls_uxysntb3u03j", - "number": "5.13:14" + "checksum": "809cb7417b66935ad1e1a2be5657a090e793aeeb0ff3a67c4e7777db5a76dd1a", + "id": "fls_crb6m6b3cdwh", + "link": "types-and-traits.html#fls_crb6m6b3cdwh", + "number": "4.14.3.1:15" }, { - "checksum": "8bd4950f12c3fc2761c334a84cb412e80a671197a21ca640ebe447e01836d826", - "id": "fls_wh201rmh6u6d", - "link": "patterns.html#fls_wh201rmh6u6d", - "number": "5.13:15" + "checksum": "0a93ba5afb72ab5a83c7159ad67dc0478704a55a3d3ef9325b44d0aee4a5a11b", + "id": "fls_ac9tdlfwp5et", + "link": "types-and-traits.html#fls_ac9tdlfwp5et", + "number": "4.14.3.1:16" }, { - "checksum": "aa4932214e845c7edd44605a7721fc1476f6b66f527e69354b6b3b8060bba84b", - "id": "fls_vstdqifqipbh", - "link": "patterns.html#fls_vstdqifqipbh", - "number": "5.13:16" + "checksum": "b0b24169a682ebd4af464f022caa0e0bc52dc45c2ab5969c01b1b6a2a313b7f4", + "id": "fls_qtjc7334wzhj", + "link": "types-and-traits.html#fls_qtjc7334wzhj", + "number": "4.14.3.1:17" + }, + { + "checksum": "98fed21cebc3cecb0b210e636cc45a37121a883718d0ba5e64b36abb86d70f58", + "id": "fls_vcmmkp9uruhr", + "link": "types-and-traits.html#fls_vcmmkp9uruhr", + "number": "4.14.3.1:18" } ], - "title": "Pattern Matching" + "title": "Function Lifetime Elision" }, { - "id": "fls_vnai6ag4qrdb", + "id": "fls_u5lqku2rs6uv", "informational": false, - "link": "patterns.html#identifier-pattern-matching", - "number": "5.13.1", + "link": "types-and-traits.html#static-lifetime-elision", + "number": "4.14.3.2", "paragraphs": [ { - "checksum": "dd60a44dea7a972ddea069491ecd9f406558f404e901527ddd0d2149eeb48ebd", - "id": "fls_4f3lzw64myhk", - "link": "patterns.html#fls_4f3lzw64myhk", - "number": "5.13.1:1" - }, - { - "checksum": "aa285e853a6324a841c40d2b74e066fb6002f1fb6707b20a81ff680616eae3fc", - "id": "fls_wauqwmdbcpna", - "link": "patterns.html#fls_wauqwmdbcpna", - "number": "5.13.1:2" + "checksum": "362c1e5e7ef9d1bb007461a6e77c25fd2bdd4146d43523089f709b4eecdb11f2", + "id": "fls_l4RDXaFwnQZ6", + "link": "types-and-traits.html#fls_l4RDXaFwnQZ6", + "number": "4.14.3.2:1" }, { - "checksum": "4aef617035a62c693b0db6e84d556a1afe10c28af4f9b0ca18065503767f6015", - "id": "fls_3jyog8n6x2aa", - "link": "patterns.html#fls_3jyog8n6x2aa", - "number": "5.13.1:3" + "checksum": "a4e8a63dc12eaffaf473b18138e9e501b7fedc18b8b3cb4105a41724426cd35a", + "id": "fls_8irr97rZWfSC", + "link": "types-and-traits.html#fls_8irr97rZWfSC", + "number": "4.14.3.2:2" }, { - "checksum": "0db8e23416d53e90ad73fa4df5b10d7e3ef8d53418d42a8860380a85d5d1163a", - "id": "fls_w637uvlbzsyo", - "link": "patterns.html#fls_w637uvlbzsyo", - "number": "5.13.1:4" + "checksum": "e2a84e07dc44c60162e59d18213737a3fbdbe13962c4e3f32178970f3d2927ea", + "id": "fls_37udexenqv3p", + "link": "types-and-traits.html#fls_37udexenqv3p", + "number": "4.14.3.2:3" }, { - "checksum": "1cf4f4074ffc3e6745944b1dcd726ac4b7d65f44568c669f04ddfcbe8068d57e", - "id": "fls_arz8ik3gf6u4", - "link": "patterns.html#fls_arz8ik3gf6u4", - "number": "5.13.1:5" + "checksum": "1eae2df72c51ffcad42ba90f5618089142bb9116967a5bcd8c0f9f34b11bfcc2", + "id": "fls_xi86he5vvill", + "link": "types-and-traits.html#fls_xi86he5vvill", + "number": "4.14.3.2:4" }, { - "checksum": "5003a32c5e8a49a9e7d778d70ff05edcf2411bd284272a8bf7bd04d2df3a40fe", - "id": "fls_u6o5ndnezwbe", - "link": "patterns.html#fls_u6o5ndnezwbe", - "number": "5.13.1:6" + "checksum": "686ca62c3062007fe2a2c7112962d70308c2004c4e6eed04508f5a30e22ed81d", + "id": "fls_2GKCEI9MwMn9", + "link": "types-and-traits.html#fls_2GKCEI9MwMn9", + "number": "4.14.3.2:5" }, { - "checksum": "de66751e709611b36e4517f705acf75d052724e2501a8c5ee5ab8c40757115e5", - "id": "fls_h1er04t0yta7", - "link": "patterns.html#fls_h1er04t0yta7", - "number": "5.13.1:7" + "checksum": "87777c1e9a852ae61349dd7554488c91f20c43dc493ec06547aa740dcba8ac22", + "id": "fls_f3yZ31dRuTPG", + "link": "types-and-traits.html#fls_f3yZ31dRuTPG", + "number": "4.14.3.2:6" } ], - "title": "Identifier Pattern Matching" + "title": "Static Lifetime Elision" }, { - "id": "fls_azzf1llv3wf", + "id": "fls_xtbohk2yk4la", "informational": false, - "link": "patterns.html#literal-pattern-matching", - "number": "5.13.2", + "link": "types-and-traits.html#trait-object-lifetime-elision", + "number": "4.14.3.3", "paragraphs": [ { - "checksum": "ffb06275d46d70835ab2e118cff4b0071d02544ee94a260f428fd2b5533bed1c", - "id": "fls_fqkhhgushje9", - "link": "patterns.html#fls_fqkhhgushje9", - "number": "5.13.2:1" + "checksum": "9792cf5e24351dd23a21b5298dab4d7c7f182536a15cd1082d892ea56f6fed7e", + "id": "fls_fuBYWRrgxlbQ", + "link": "types-and-traits.html#fls_fuBYWRrgxlbQ", + "number": "4.14.3.3:1" }, { - "checksum": "d04dcff15efbb13e790e1f78948bf446426570ec9618c3b921293adfc2012199", - "id": "fls_m01eo9sa55s", - "link": "patterns.html#fls_m01eo9sa55s", - "number": "5.13.2:2" + "checksum": "735f380f9e5867b7964548adbbe1ef171e6362a7165d946fa2f88f1c49835e00", + "id": "fls_URl9CeIVsiWs", + "link": "types-and-traits.html#fls_URl9CeIVsiWs", + "number": "4.14.3.3:2" }, { - "checksum": "fe6504c691ded75e993e4d66b216f7260d30ac52c0a60e35151af31c01905992", - "id": "fls_294jtwbfq3p9", - "link": "patterns.html#fls_294jtwbfq3p9", - "number": "5.13.2:3" - } - ], - "title": "Literal Pattern Matching" - }, - { - "id": "fls_5loglxds6zik", - "informational": false, - "link": "patterns.html#parenthesized-pattern-matching", - "number": "5.13.3", - "paragraphs": [ + "checksum": "b8dc302081286801fab56e83adfb0c6641a4aabc8e37c977089243aa1c6eb354", + "id": "fls_SHhw6lYHeYyQ", + "link": "types-and-traits.html#fls_SHhw6lYHeYyQ", + "number": "4.14.3.3:3" + }, { - "checksum": "7f3d87f0e2bf7a6dc15e6e0bcda2506008be955f016459a792ea2b99d16bacbf", - "id": "fls_jajvvwoy3399", - "link": "patterns.html#fls_jajvvwoy3399", - "number": "5.13.3:1" - } - ], - "title": "Parenthesized Pattern Matching" - }, - { - "id": "fls_d44aflefat88", - "informational": false, - "link": "patterns.html#path-pattern-matching", - "number": "5.13.4", - "paragraphs": [ + "checksum": "d14fa2465cadaa9f9404de8d4f1dc2c83ea23291fb5a15f281ccbc77646725a3", + "id": "fls_lC2rwdPLRwaf", + "link": "types-and-traits.html#fls_lC2rwdPLRwaf", + "number": "4.14.3.3:4" + }, { - "checksum": "b62c95bf3039a32f793c95cd983bcc5cd80b8faeb5d469cc963c3a487f437fae", - "id": "fls_4faltss0xbn4", - "link": "patterns.html#fls_4faltss0xbn4", - "number": "5.13.4:1" + "checksum": "5ce3569a487aec0ffe76efa0ebd2f980162b7540452d2800b92474ac6b7fe730", + "id": "fls_e36Hh4oJvfhv", + "link": "types-and-traits.html#fls_e36Hh4oJvfhv", + "number": "4.14.3.3:5" }, { - "checksum": "ee205e802f76cf743647cd0b8c7be7f9d118ea51ef62a9f8ccd237ee2a7c874c", - "id": "fls_fqt5w3qsykca", - "link": "patterns.html#fls_fqt5w3qsykca", - "number": "5.13.4:2" + "checksum": "416a9c7f994a10348666cc1428fe679ecfbb5cc34c3fd991214116fd5405123c", + "id": "fls_ptejalcnIQtm", + "link": "types-and-traits.html#fls_ptejalcnIQtm", + "number": "4.14.3.3:6" }, { - "checksum": "10a9ff549fbafbc317a299a46f8f61ed3ec121a3f1c97d94c1a3ae2aeee961f7", - "id": "fls_h3y8r4298s53", - "link": "patterns.html#fls_h3y8r4298s53", - "number": "5.13.4:3" + "checksum": "9a1ce8dbae4264be182622bdedc1cd24fb5bc4c9c3d2a9805d7e83d22bebdac8", + "id": "fls_rGbdKtTijby4", + "link": "types-and-traits.html#fls_rGbdKtTijby4", + "number": "4.14.3.3:7" + }, + { + "checksum": "3ded6c067304c60cab3a670cb056d96704403c4fd5c6719d0ccfba410623ced1", + "id": "fls_JhmQpUoExiNZ", + "link": "types-and-traits.html#fls_JhmQpUoExiNZ", + "number": "4.14.3.3:8" + }, + { + "checksum": "b9f5585c56ad53d5720ebad44d77024dd47add4769f1886a76551f07ddaeb2ca", + "id": "fls_cglZigwAnASl", + "link": "types-and-traits.html#fls_cglZigwAnASl", + "number": "4.14.3.3:9" + }, + { + "checksum": "d6269aed267968a9a469c7003f9ac3e0b12044648f1246e40ef2eea39d5610b4", + "id": "fls_MipY2emZFF6d", + "link": "types-and-traits.html#fls_MipY2emZFF6d", + "number": "4.14.3.3:10" + }, + { + "checksum": "8160bcf42506146875a69bae6334ab1868c2957f6f1b085629deedafa0ad1914", + "id": "fls_YPesUZqYHVUX", + "link": "types-and-traits.html#fls_YPesUZqYHVUX", + "number": "4.14.3.3:11" } ], - "title": "Path Pattern Matching" + "title": "Trait Object Lifetime Elision" }, { - "id": "fls_fyskeih6twyb", + "id": "fls_zqpv1ybddse1", "informational": false, - "link": "patterns.html#range-pattern-matching", - "number": "5.13.5", + "link": "types-and-traits.html#impl-header-lifetime-elision", + "number": "4.14.3.4", "paragraphs": [ { - "checksum": "74c808f389212d771098798db71f913b01fad773d198d3babf0a21ae6f4ce792", - "id": "fls_mrh9vfdek5fi", - "link": "patterns.html#fls_mrh9vfdek5fi", - "number": "5.13.5:1" + "checksum": "f6a449af6e0ee8f776a596f24acb1ee832b6830604a4f17b42249cef950b08b8", + "id": "fls_FUdsmzN0T8XP", + "link": "types-and-traits.html#fls_FUdsmzN0T8XP", + "number": "4.14.3.4:1" + }, + { + "checksum": "a0ad068ff94137edb05b75c066208d34c26cad7e567ccbea22214e4480f035f5", + "id": "fls_3p5BdLn3JbKz", + "link": "types-and-traits.html#fls_3p5BdLn3JbKz", + "number": "4.14.3.4:2" }, { - "checksum": "4e77c3805f9c1fde39877a54a3b99be7582ff7dde64d6f8b033465fcfb625c8c", - "id": "fls_7nxkgls0a5os", - "link": "patterns.html#fls_7nxkgls0a5os", - "number": "5.13.5:2" + "checksum": "ddc3e2f6339da6c1ddd05ef0d487698b078e2c06a7456b12337e4b04720ec88d", + "id": "fls_PfS5AlkN6ANl", + "link": "types-and-traits.html#fls_PfS5AlkN6ANl", + "number": "4.14.3.4:3" }, { - "checksum": "fd8fba85c57e8cde11619040e0cf63b8bee10a4ad77a5a663655cf44f7527d00", - "id": "fls_6kgj2fjccoig", - "link": "patterns.html#fls_6kgj2fjccoig", - "number": "5.13.5:3" + "checksum": "23b6ef8e94104fd9bfc08bddfadbc0e6b45e3af1816b852b6542fa4ef26a99b4", + "id": "fls_QV6zE5Mdor5E", + "link": "types-and-traits.html#fls_QV6zE5Mdor5E", + "number": "4.14.3.4:4" }, { - "checksum": "6ceef4a4fb5e7197f8e4fbeea43c666aa1ee532e0e198a0242be6ab5dca9d26c", - "id": "fls_EDL1Pi56KQ2H", - "link": "patterns.html#fls_EDL1Pi56KQ2H", - "number": "5.13.5:4" + "checksum": "7e879940c036b1acde707493ab1511307ccc68d03e5e0c62cfd4d9d230a8bc69", + "id": "fls_P2V1DqrdCjCi", + "link": "types-and-traits.html#fls_P2V1DqrdCjCi", + "number": "4.14.3.4:5" }, { - "checksum": "7ee404fd4fb97fe0f1daafdb86a76994a1056bc3cbc4571522f37b405f651572", - "id": "fls_n4t3xah1pk7i", - "link": "patterns.html#fls_n4t3xah1pk7i", - "number": "5.13.5:5" + "checksum": "c01105160b098b089c2c01853293cccd8231a07686ae97a7d7aba145d987c330", + "id": "fls_MBa35hfS8J9l", + "link": "types-and-traits.html#fls_MBa35hfS8J9l", + "number": "4.14.3.4:6" + }, + { + "checksum": "3b91fe9b60e23bf9452baf8b3ca1624e42e4109b50e88a340e7c1998b9a6772c", + "id": "fls_w0vwdmO8qV9j", + "link": "types-and-traits.html#fls_w0vwdmO8qV9j", + "number": "4.14.3.4:7" + }, + { + "checksum": "68c7b26da67383cc4c0a68039f093046d35f47ac193122f01475bcb83de41546", + "id": "fls_vImY3BrpNvNY", + "link": "types-and-traits.html#fls_vImY3BrpNvNY", + "number": "4.14.3.4:8" } ], - "title": "Range Pattern Matching" - }, + "title": "Impl Header Lifetime Elision" + } + ], + "title": "Types and Traits" + }, + { + "informational": true, + "link": "undefined-behavior.html", + "sections": [ { - "id": "fls_org6hqv397fp", + "id": "fls_ebwqh60suhin", "informational": false, - "link": "patterns.html#reference-pattern-matching", - "number": "5.13.6", + "link": "undefined-behavior.html", + "number": "C", "paragraphs": [ { - "checksum": "b6dad8a8eb45a793423595800323ed5f357d864241174e7198f4101b4d5c9c9b", - "id": "fls_ysfgdzaiww8z", - "link": "patterns.html#fls_ysfgdzaiww8z", - "number": "5.13.6:1" - }, - { - "checksum": "9932447e7ad4cb062a332bea07f5eb902fa9f0482de8917d2a3bee05f86c2fdf", - "id": "fls_7rxnxd4ybxbt", - "link": "patterns.html#fls_7rxnxd4ybxbt", - "number": "5.13.6:2" - }, - { - "checksum": "b76bff46c3fb725fb95fd036da6eec2951e358e24662e16fe7a2b277948422eb", - "id": "fls_l2nwz166curc", - "link": "patterns.html#fls_l2nwz166curc", - "number": "5.13.6:3" + "checksum": "1acc139f661c296d23b8c389774df3abe93a5aa6c58454656db3df577de1a38c", + "id": "fls_f9mkI99mzPxY", + "link": "undefined-behavior.html#fls_f9mkI99mzPxY", + "number": "C:1" } ], - "title": "Reference Pattern Matching" - }, + "title": "List of undefined behavior" + } + ], + "title": "List of undefined behavior" + }, + { + "informational": false, + "link": "unsafety.html", + "sections": [ { - "id": "fls_57ic33pwdvp3", + "id": "fls_jep7p27kaqlp", "informational": false, - "link": "patterns.html#slice-pattern-matching", - "number": "5.13.7", + "link": "unsafety.html", + "number": "19", "paragraphs": [ { - "checksum": "545a7b4ddaf1e7bfb2237db616a2cf9a24e53676e02591cec035c44ace0b0fd1", - "id": "fls_hzyv4ofu0ny", - "link": "patterns.html#fls_hzyv4ofu0ny", - "number": "5.13.7:1" + "checksum": "09ef0477163f852006ab72daea7582f28fd5b47b9ec3a4188da6a52a06c1cbb9", + "id": "fls_8kqo952gjhaf", + "link": "unsafety.html#fls_8kqo952gjhaf", + "number": "19:1" }, { - "checksum": "a08c2a45fe77ae5f047f5e6cf64b8f743c4433b940e5f5fb0245051c079e7e8a", - "id": "fls_x10AKxoXXbs8", - "link": "patterns.html#fls_x10AKxoXXbs8", - "number": "5.13.7:2" + "checksum": "72911c6efacc1ea81b757fea368ab51b658c5f44690af8bb4bf5076196f02dba", + "id": "fls_ovn9czwnwxue", + "link": "unsafety.html#fls_ovn9czwnwxue", + "number": "19:2" }, { - "checksum": "00b9a97b9280c3716ddfa172c721a2bf0e2081a9fa912cffde3cda55d1197952", - "id": "fls_69bnxrtj0nar", - "link": "patterns.html#fls_69bnxrtj0nar", - "number": "5.13.7:3" + "checksum": "ac90013bdfea2c44c2ea16d0d92eb8b7a3b32f33cdb44553018c9de58de2d0ab", + "id": "fls_pfhmcafsjyf7", + "link": "unsafety.html#fls_pfhmcafsjyf7", + "number": "19:3" }, { - "checksum": "c48b717f055594a928a658edaf02b1fabf8a096bdefdb3fbc8f24437462635da", - "id": "fls_twhwiy213ibf", - "link": "patterns.html#fls_twhwiy213ibf", - "number": "5.13.7:4" + "checksum": "509a721d7a591aac4ddac02ca26ef6fc26e0ae40b040578d4282304dbef76b45", + "id": "fls_jd1inwz7ulyw", + "link": "unsafety.html#fls_jd1inwz7ulyw", + "number": "19:4" }, { - "checksum": "3f7ea24067807e7941333db26d088679f21fbd2c64a2407cf545ff6861841872", - "id": "fls_ei7y4ul6n6hu", - "link": "patterns.html#fls_ei7y4ul6n6hu", - "number": "5.13.7:5" + "checksum": "9efb922ed5200526914bd3f95ddb869b8e555bd8367143db358605307ff7ea93", + "id": "fls_3ra8s1v1vbek", + "link": "unsafety.html#fls_3ra8s1v1vbek", + "number": "19:5" }, { - "checksum": "4925bc389627a9c0258c8b5aa732c62f27ea3c62e2b3c0bbfd6e8f9c1350920e", - "id": "fls_ad2jud5h1rfp", - "link": "patterns.html#fls_ad2jud5h1rfp", - "number": "5.13.7:6" + "checksum": "1b9334ca686a2f78808b0a5e1bf0b6793eef547e9367cf94416bb6a4009457bc", + "id": "fls_6ipl0xo5qjyl", + "link": "unsafety.html#fls_6ipl0xo5qjyl", + "number": "19:6" }, { - "checksum": "0a3498e1551afa3c7f492391c8187746fccf5ebb43ae48f55741d4dd39aaceab", - "id": "fls_pc97m47p34wq", - "link": "patterns.html#fls_pc97m47p34wq", - "number": "5.13.7:7" + "checksum": "08016621bfd496b3d4c162d71caf6c3033c213bf9b52662adf1137b696285b4b", + "id": "fls_ucghxcnpaq2t", + "link": "unsafety.html#fls_ucghxcnpaq2t", + "number": "19:7" }, { - "checksum": "ff3c524edac17baef16b63f1da2eead531f9c065f671838de8c437b87e87f859", - "id": "fls_kwQyiSoyAwZ8", - "link": "patterns.html#fls_kwQyiSoyAwZ8", - "number": "5.13.7:8" + "checksum": "7740b81deb4cc8d3a5d63073faa941c897de8931c90af82f402d60d8f5cf7ad6", + "id": "fls_ljocmnaz2m49", + "link": "unsafety.html#fls_ljocmnaz2m49", + "number": "19:8" }, { - "checksum": "624d69de485ef8707d6d98a26dcdfd92081861894d0c06dba07882fe19de3ee2", - "id": "fls_zAdtysiuUwBX", - "link": "patterns.html#fls_zAdtysiuUwBX", - "number": "5.13.7:9" + "checksum": "bb058285e28804f8335348bcfa3c28e0f032f4346bc2aaf52a67145036233241", + "id": "fls_s5nfhBFOk8Bu", + "link": "unsafety.html#fls_s5nfhBFOk8Bu", + "number": "19:9" }, { - "checksum": "8f94a20f9d18e18084cc5e7931d668de6254cce6b14813fce2f2a16d4a699598", - "id": "fls_SezcYXcSlEq7", - "link": "patterns.html#fls_SezcYXcSlEq7", - "number": "5.13.7:10" + "checksum": "14f513b2d197bbf061de106591d50be0e1b5efdd28b7b73e3c01a1fd27dc914d", + "id": "fls_jb6krd90tjmc", + "link": "unsafety.html#fls_jb6krd90tjmc", + "number": "19:10" }, { - "checksum": "f14008365b9b2173763090e4ac8e371acae7932276efa64dd5c4bef234a9b625", - "id": "fls_6xRXEt2pGnZi", - "link": "patterns.html#fls_6xRXEt2pGnZi", - "number": "5.13.7:11" + "checksum": "c476883b58a5e4480fa0505d41dc097727812f40edf56fd7a46f4fd3595ee2f3", + "id": "fls_ybnpe7ppq1vh", + "link": "unsafety.html#fls_ybnpe7ppq1vh", + "number": "19:11" } ], - "title": "Slice Pattern Matching" + "title": "Unsafety" + } + ], + "title": "Unsafety" + }, + { + "informational": false, + "link": "program-structure-and-compilation.html", + "sections": [ + { + "id": "fls_hdwwrsyunir", + "informational": false, + "link": "program-structure-and-compilation.html", + "number": "18", + "paragraphs": [], + "title": "Program Structure and Compilation" }, { - "id": "fls_asj8rgccvkoe", + "id": "fls_s35hob3i7lr", "informational": false, - "link": "patterns.html#record-struct-pattern-matching", - "number": "5.13.8", + "link": "program-structure-and-compilation.html#source-files", + "number": "18.1", "paragraphs": [ { - "checksum": "a64d7a8315d7d5f47ec10463a26465d1b20225bc5c426030b5af68285a662722", - "id": "fls_evuhau2rwm8i", - "link": "patterns.html#fls_evuhau2rwm8i", - "number": "5.13.8:1" - }, - { - "checksum": "202123ed4c141cd00114f671cc7b4a9caaafd86ff150c865e93c8e9dad4257fc", - "id": "fls_bde1hpvrosui", - "link": "patterns.html#fls_bde1hpvrosui", - "number": "5.13.8:2" - }, - { - "checksum": "d0da6696f0030559710cd9ee013efb4dbfb95b1df235fe5f469bfcd2d24034c4", - "id": "fls_447s4hc07ozn", - "link": "patterns.html#fls_447s4hc07ozn", - "number": "5.13.8:3" - }, - { - "checksum": "b11dc2c5dfcaf758c45e4029e582f72a53c74e7c2c98575fb2b7461ae1e7d805", - "id": "fls_vfdb1i5l41yk", - "link": "patterns.html#fls_vfdb1i5l41yk", - "number": "5.13.8:4" - }, - { - "checksum": "36081581a365a6503d56139e07b8e1749757cf878634d805e84663d3172cb005", - "id": "fls_yfk52fr7trw3", - "link": "patterns.html#fls_yfk52fr7trw3", - "number": "5.13.8:5" + "checksum": "55f6d3282e08235092b6157374cbc8fad302ec5965229b94dca69fbf5a239934", + "id": "fls_4vicosdeaqmp", + "link": "program-structure-and-compilation.html#fls_4vicosdeaqmp", + "number": "18.1:1" }, { - "checksum": "8b8f1725d0cf2ef6489bd548eec9d59fab537eca1f33a509f8141751325cba76", - "id": "fls_6sdcykdrpe5d", - "link": "patterns.html#fls_6sdcykdrpe5d", - "number": "5.13.8:6" + "checksum": "7555cb7065456e8c9abbac7b69fc1d5e7a6e631762739806e629635a718a19ac", + "id": "fls_ann3cha1xpek", + "link": "program-structure-and-compilation.html#fls_ann3cha1xpek", + "number": "18.1:2" } ], - "title": "Record Struct Pattern Matching" + "title": "Source Files" }, { - "id": "fls_eexupzdsu7f", + "id": "fls_e9hwvqsib5d5", "informational": false, - "link": "patterns.html#tuple-struct-pattern-matching", - "number": "5.13.9", + "link": "program-structure-and-compilation.html#modules", + "number": "18.2", "paragraphs": [ { - "checksum": "ed45c176d905341bee988af8640b3011dd6a97d9c8fe53b45410925a79d5e056", - "id": "fls_dexg9g9cct30", - "link": "patterns.html#fls_dexg9g9cct30", - "number": "5.13.9:1" - }, - { - "checksum": "03c31f4e25eebbe78169c4bc173609effc1e8269dfe4f263b29363409e316dab", - "id": "fls_boc7juqj69hw", - "link": "patterns.html#fls_boc7juqj69hw", - "number": "5.13.9:2" + "checksum": "ec4163446ba1d6053faea2c9be55ae44b49f9e489b2fb4266106708fe16bdda3", + "id": "fls_odd1hj3y1mgu", + "link": "program-structure-and-compilation.html#fls_odd1hj3y1mgu", + "number": "18.2:1" }, { - "checksum": "6b75afdc8b9b09fcd7df94b0c730c87e30d4bb2de200714adf3a08575e90a90a", - "id": "fls_4dr1stiw82v9", - "link": "patterns.html#fls_4dr1stiw82v9", - "number": "5.13.9:3" + "checksum": "a552ab77145836cc109d96b376f5e785089d1f7d95d6d51c25ecfddb3708b512", + "id": "fls_whgv72emrm47", + "link": "program-structure-and-compilation.html#fls_whgv72emrm47", + "number": "18.2:2" }, { - "checksum": "13343ca1b2a108f58c91e7fcdb4c9e4c975f5fa11d841db4951a8883a4bcc7d5", - "id": "fls_h14emtt6iyk3", - "link": "patterns.html#fls_h14emtt6iyk3", - "number": "5.13.9:4" - } - ], - "title": "Tuple Struct Pattern Matching" - }, - { - "id": "fls_rce8bb7nz2jy", - "informational": false, - "link": "patterns.html#tuple-pattern-matching", - "number": "5.13.10", - "paragraphs": [ - { - "checksum": "746ffe5085ba7cfbb59d3c44688b6f8584fa574128327bb37f2f051173efb07d", - "id": "fls_w4xypnrnhycb", - "link": "patterns.html#fls_w4xypnrnhycb", - "number": "5.13.10:1" + "checksum": "1233b4977ef4e5fd6c5969530904ff70690f361cc5fccddbf8952e1cc7d299f8", + "id": "fls_qypjjpcf8uwq", + "link": "program-structure-and-compilation.html#fls_qypjjpcf8uwq", + "number": "18.2:3" }, { - "checksum": "5f1c0e7fad583c2e4331c5805f1dbf89717118d493a01549a42dfeaa3f0279ca", - "id": "fls_vnx1bpval595", - "link": "patterns.html#fls_vnx1bpval595", - "number": "5.13.10:2" + "checksum": "f54b92d3867600666f831b9a1ca07de6acba61ac72937335bd0451a3de4cbb27", + "id": "fls_cavwpr1ybk37", + "link": "program-structure-and-compilation.html#fls_cavwpr1ybk37", + "number": "18.2:4" }, { - "checksum": "e86a7e2ae632d8ce6ffaeb404902846bb48e160aab297c2c2652d9b985f7982e", - "id": "fls_dzf32f40y7fr", - "link": "patterns.html#fls_dzf32f40y7fr", - "number": "5.13.10:3" + "checksum": "ddcd4deef3d419dcbf8b2c18aaa5d8bb453229bce34e4d7180d9e8f93d2bf303", + "id": "fls_plepew2319g4", + "link": "program-structure-and-compilation.html#fls_plepew2319g4", + "number": "18.2:5" }, { - "checksum": "37eed365431808077ba3f82d504515f343520002fd244c7649305e39f6c96d7e", - "id": "fls_krl32txvxxkz", - "link": "patterns.html#fls_krl32txvxxkz", - "number": "5.13.10:4" + "checksum": "fa07c91c0b0de63411270c072bdc923db3f1db860082d3eddad31ca6eed189c5", + "id": "fls_1aruwps62c4p", + "link": "program-structure-and-compilation.html#fls_1aruwps62c4p", + "number": "18.2:6" } ], - "title": "Tuple Pattern Matching" + "title": "Modules" }, { - "id": "fls_yc4xm4hrfyw7", + "id": "fls_maw4u1o8q37u", "informational": false, - "link": "patterns.html#underscore-pattern-matching", - "number": "5.13.11", + "link": "program-structure-and-compilation.html#crates", + "number": "18.3", "paragraphs": [ { - "checksum": "b9ccebce07421997dfd958ff40f0aef3a1b9e546fb3bcf0c2cefe7fbfdd64d01", - "id": "fls_dvk7r1gf7pwp", - "link": "patterns.html#fls_dvk7r1gf7pwp", - "number": "5.13.11:1" + "checksum": "c64331512493388d716b5e98082811475682dcffee41348b82483eb48cd91fc5", + "id": "fls_qwghk79ok5h0", + "link": "program-structure-and-compilation.html#fls_qwghk79ok5h0", + "number": "18.3:1" }, { - "checksum": "33c34bf81e9f7a3651a3718b8ab5b88c0ba416ea85e283b4fbd4a7e30e78b48a", - "id": "fls_e0uprihqn1y6", - "link": "patterns.html#fls_e0uprihqn1y6", - "number": "5.13.11:2" + "checksum": "076cdc6ed8a203ec16636f7d7a4f7bfc8fcf4ad833ca2a5eb22898fa6eeb576c", + "id": "fls_unxalgMqIr3v", + "link": "program-structure-and-compilation.html#fls_unxalgMqIr3v", + "number": "18.3:2" }, { - "checksum": "8e3a3ff978133c3bfffe09986eace47f5b4afb21f6c71af39fd93f4f27ad2860", - "id": "fls_ljcq2vyo052q", - "link": "patterns.html#fls_ljcq2vyo052q", - "number": "5.13.11:3" - } - ], - "title": "Underscore Pattern Matching" - } - ], - "title": "Patterns" - }, - { - "informational": true, - "link": "undefined-behavior.html", - "sections": [ - { - "id": "fls_ebwqh60suhin", - "informational": false, - "link": "undefined-behavior.html", - "number": "C", - "paragraphs": [ + "checksum": "cc519441f6acd2fc70f4251a27ea4b31fd340a96b04393a9725c4ee2ac8cb0f1", + "id": "fls_e7jGvXvTsFpC", + "link": "program-structure-and-compilation.html#fls_e7jGvXvTsFpC", + "number": "18.3:3" + }, { - "checksum": "1acc139f661c296d23b8c389774df3abe93a5aa6c58454656db3df577de1a38c", - "id": "fls_f9mkI99mzPxY", - "link": "undefined-behavior.html#fls_f9mkI99mzPxY", - "number": "C:1" - } - ], - "title": "List of undefined behavior" - } - ], - "title": "List of undefined behavior" - }, - { - "informational": false, - "link": "generics.html", - "sections": [ - { - "id": "fls_y2k5paj8m8ug", - "informational": false, - "link": "generics.html", - "number": "12", - "paragraphs": [], - "title": "Generics" - }, - { - "id": "fls_vhpwge5123cm", - "informational": false, - "link": "generics.html#generic-parameters", - "number": "12.1", - "paragraphs": [ + "checksum": "900926091f2fede279a11afbb6e26774c0925d89e0c2a22e8d66e4454f946b6d", + "id": "fls_kQiJPwb2Hjcc", + "link": "program-structure-and-compilation.html#fls_kQiJPwb2Hjcc", + "number": "18.3:4" + }, { - "checksum": "94863fbf1d8fdb0023847e7bb69f0c04a488fd3b0541bc6a43dfa580ab0d16ae", - "id": "fls_sye3d17l9bf5", - "link": "generics.html#fls_sye3d17l9bf5", - "number": "12.1:1" + "checksum": "3ac54406ea82315c87cf8183011163d2c68bb2596b63674f83fda32c046f21c9", + "id": "fls_9ub6ks8qrang", + "link": "program-structure-and-compilation.html#fls_9ub6ks8qrang", + "number": "18.3:5" }, { - "checksum": "a9b102aea4ead34bd9a1dfb0ee5d0b12b91ec2b099a59d0a4320521f772835eb", - "id": "fls_dalqke3rznrb", - "link": "generics.html#fls_dalqke3rznrb", - "number": "12.1:2" + "checksum": "8db6a4b2f241361fa8613dd0e294548a09dde19ef5946330c8eca66ccd46bf09", + "id": "fls_OyFwBtDGVimT", + "link": "program-structure-and-compilation.html#fls_OyFwBtDGVimT", + "number": "18.3:6" }, { - "checksum": "090ffb52196f4e68695b1c484beb6bd8507d06d2f679ee8177decd8b6b34ca79", - "id": "fls_pi6eukz7kc99", - "link": "generics.html#fls_pi6eukz7kc99", - "number": "12.1:3" + "checksum": "8b845c17714f7a58a93d5da09f978d3d87cfdbe7d4b7758b0f26740430c368de", + "id": "fls_jQqXxPyND1ds", + "link": "program-structure-and-compilation.html#fls_jQqXxPyND1ds", + "number": "18.3:7" }, { - "checksum": "59bebc9aaade7a8e216d5acd0230c4a561df1b66eb30289c51eac737ae0037fa", - "id": "fls_ixmgqupxvf73", - "link": "generics.html#fls_ixmgqupxvf73", - "number": "12.1:4" + "checksum": "f15de757cf59ea80d471a93296df48599ad3b3402b7f85e22194e0e38f546b11", + "id": "fls_d9nn4yuiw1ja", + "link": "program-structure-and-compilation.html#fls_d9nn4yuiw1ja", + "number": "18.3:8" }, { - "checksum": "ebd86684687d8a3b3d796280d1bd02eb718235e578e2e90f8b1ccf6fb6b90a97", - "id": "fls_z311nxou9yi3", - "link": "generics.html#fls_z311nxou9yi3", - "number": "12.1:5" + "checksum": "bc735aaea18f5347b09a4b7ac21ae36e48f125b2cfa1ecbdb0566b0070303199", + "id": "fls_Mf62VqAhoZ3c", + "link": "program-structure-and-compilation.html#fls_Mf62VqAhoZ3c", + "number": "18.3:9" }, { - "checksum": "09faa28093efe5bea8695465a1b6ccf9dd591a83377126e6f7ca32bffc783f0a", - "id": "fls_wmcp0n36jlbr", - "link": "generics.html#fls_wmcp0n36jlbr", - "number": "12.1:6" + "checksum": "ca2db7453a265830bd34e2a087ce0a6e08f97f20221a3ef1ab151f72892c5e8f", + "id": "fls_RJJmN4tP7j4m", + "link": "program-structure-and-compilation.html#fls_RJJmN4tP7j4m", + "number": "18.3:10" }, { - "checksum": "7dd9f9ccb26b4083d870e727d44fd7cb36573bf661c6ab932ad22dc96e4b6afb", - "id": "fls_h42kg56vsefx", - "link": "generics.html#fls_h42kg56vsefx", - "number": "12.1:7" + "checksum": "7285d85fe56e6377849b918e1d006071c4bb14a18476290ddc8cce3adcc11a30", + "id": "fls_h93C3wfbAoz1", + "link": "program-structure-and-compilation.html#fls_h93C3wfbAoz1", + "number": "18.3:11" + } + ], + "title": "Crates" + }, + { + "id": "fls_gklst7joeo33", + "informational": false, + "link": "program-structure-and-compilation.html#crate-imports", + "number": "18.4", + "paragraphs": [ + { + "checksum": "dbf3d3e47ad3d7220f2ffe8d071ff0607474ea6e8f4c3edd18a553ed36509d07", + "id": "fls_d0pa807s5d5h", + "link": "program-structure-and-compilation.html#fls_d0pa807s5d5h", + "number": "18.4:1" }, { - "checksum": "b76e6307e1cd2582b525354a84e5bc604d7d52679124f2807619890557dd350d", - "id": "fls_372h3oevejih", - "link": "generics.html#fls_372h3oevejih", - "number": "12.1:8" + "checksum": "fc37a08d96af01fa9eddc305c921ac70c13dabc6a3a41c58f09f74622cc29075", + "id": "fls_vfam3wzeAiah", + "link": "program-structure-and-compilation.html#fls_vfam3wzeAiah", + "number": "18.4:2" }, { - "checksum": "d022534e05abd6632faed1f9a99e08afd782e729b0e5c46a046e8fec4761e6f9", - "id": "fls_u8mqct93yimd", - "link": "generics.html#fls_u8mqct93yimd", - "number": "12.1:9" + "checksum": "c98120af9e1e4f6ac518733e42a91af54ae118579dc4ea7355c255e9b3e077e7", + "id": "fls_ft860vkz0lkc", + "link": "program-structure-and-compilation.html#fls_ft860vkz0lkc", + "number": "18.4:3" }, { - "checksum": "4159da8372c73ecd16af81e45a142b09a10de3d7f3881a9c6c554040938dbb10", - "id": "fls_vpcqgec83ybt", - "link": "generics.html#fls_vpcqgec83ybt", - "number": "12.1:10" + "checksum": "662c29b5a73d3fc1aa97c39557c1a8401d2f480bba1ec488dba0c4bf1833fed3", + "id": "fls_k90qtnf8kgu1", + "link": "program-structure-and-compilation.html#fls_k90qtnf8kgu1", + "number": "18.4:4" }, { - "checksum": "043b0fbba2086316df6f3152903a683a0584cc386e2b3f477ef71e328a293e4f", - "id": "fls_3SjMBlc0b7qo", - "link": "generics.html#fls_3SjMBlc0b7qo", - "number": "12.1:11" + "checksum": "294202b2babc6658a990fded4bd4d3b7339acad054913a48ad0243ba8a907189", + "id": "fls_siv8bl6s2ndu", + "link": "program-structure-and-compilation.html#fls_siv8bl6s2ndu", + "number": "18.4:5" }, { - "checksum": "54f245efbc506709a192db34e53895d362594afb8f4ee96986ce6afca49b04d9", - "id": "fls_p4yb8EAXlRU0", - "link": "generics.html#fls_p4yb8EAXlRU0", - "number": "12.1:12" + "checksum": "0cecb1f1189c7f1a06d3c8d132b1e51f25548a0bf043f3ed3a93308b36f1506f", + "id": "fls_7vz5n3x6jo1s", + "link": "program-structure-and-compilation.html#fls_7vz5n3x6jo1s", + "number": "18.4:6" }, { - "checksum": "ca1335fd71570adbb1aed7643857b961a8bb6086fb3b9f64925800c41ef14658", - "id": "fls_4a2qshaf5se7", - "link": "generics.html#fls_4a2qshaf5se7", - "number": "12.1:13" - }, + "checksum": "06a0e85326100e5036cbd423c273ca615cb80431541ea5e8806f3714ffb46193", + "id": "fls_3bgpc8m8yk4p", + "link": "program-structure-and-compilation.html#fls_3bgpc8m8yk4p", + "number": "18.4:7" + } + ], + "title": "Crate Imports" + }, + { + "id": "fls_5w50kf83oo1u", + "informational": false, + "link": "program-structure-and-compilation.html#compilation-roots", + "number": "18.5", + "paragraphs": [ { - "checksum": "05c11421678358c4279fc92f0982a56f16c452fb5b5d3c38d3ec77f99e14ca97", - "id": "fls_s0nrjwqg2wox", - "link": "generics.html#fls_s0nrjwqg2wox", - "number": "12.1:14" + "checksum": "3a9ce035689a8fb35cebf8668b6c24df7ee70eff17799938db3029dea0b1edf4", + "id": "fls_fhiqvgdamq5", + "link": "program-structure-and-compilation.html#fls_fhiqvgdamq5", + "number": "18.5:1" }, { - "checksum": "835447fa3ddfa7c5fd06897805b57986740381f9ecf8daf09fa6e6d1acb24377", - "id": "fls_2grtygcj8o3", - "link": "generics.html#fls_2grtygcj8o3", - "number": "12.1:15" + "checksum": "f870a40ce0c25e251e0e8856014efc6ad4611f1a28cdef8c9f460cfde2be372f", + "id": "fls_tk8tl2e0a34", + "link": "program-structure-and-compilation.html#fls_tk8tl2e0a34", + "number": "18.5:2" }, { - "checksum": "65063a7df97c072c5bd36c4029e9de91a88b81719d83ee267e7c338688871249", - "id": "fls_95eooah0vcqx", - "link": "generics.html#fls_95eooah0vcqx", - "number": "12.1:16" + "checksum": "232503cded53dd7221dcff57d6ac1d42bde0b44a3d83a1af322de4c453e185fa", + "id": "fls_bsyfxdk3ap1t", + "link": "program-structure-and-compilation.html#fls_bsyfxdk3ap1t", + "number": "18.5:3" + } + ], + "title": "Compilation Roots" + }, + { + "id": "fls_u1afezy1ye99", + "informational": false, + "link": "program-structure-and-compilation.html#conditional-compilation", + "number": "18.6", + "paragraphs": [ + { + "checksum": "1a29f8f4ec010a8c63819db415dfd3ccd84eaad03d75e2973e3fe3313e327406", + "id": "fls_9stc6nul6vq9", + "link": "program-structure-and-compilation.html#fls_9stc6nul6vq9", + "number": "18.6:1" }, { - "checksum": "b181eee5f3b1e0741189576664df8a3753448934eca0622dc2324ff23f54a85a", - "id": "fls_ahCqtkh0m5sR", - "link": "generics.html#fls_ahCqtkh0m5sR", - "number": "12.1:17" + "checksum": "b3978159779eed65918ef78768828461f8b34e7ba551de7a8c9048cac3b6edcc", + "id": "fls_a0u9nnaf6drz", + "link": "program-structure-and-compilation.html#fls_a0u9nnaf6drz", + "number": "18.6:2" }, { - "checksum": "a39d6db0400cfba65a04cbc96b9586a37ef6f64de62ec283e56c4de6319823e9", - "id": "fls_3qZRBp9j26w3", - "link": "generics.html#fls_3qZRBp9j26w3", - "number": "12.1:18" + "checksum": "547cdabd4f386341f56db0ee6332a762b67d119af87f9944ebe2697637857ef7", + "id": "fls_pf1v89h7pjhh", + "link": "program-structure-and-compilation.html#fls_pf1v89h7pjhh", + "number": "18.6:3" }, { - "checksum": "03a4ce65a6524d90af458d25675d8237300b3ed290fccc708255707b27a42460", - "id": "fls_x4s7p2v981r6", - "link": "generics.html#fls_x4s7p2v981r6", - "number": "12.1:19" + "checksum": "82d166eb360bad50b80bad46ce10a4d656966e94303a0fced35f59151561be1e", + "id": "fls_y56RGw3cbFex", + "link": "program-structure-and-compilation.html#fls_y56RGw3cbFex", + "number": "18.6:4" }, { - "checksum": "9d983e5bcc9005a2002c87f988931d048bb50221b71831ddf7af2d550d53e90a", - "id": "fls_jzfk9fspzqja", - "link": "generics.html#fls_jzfk9fspzqja", - "number": "12.1:20" + "checksum": "483be06af44f42bd953cf6480fb4ba9de360289eef475b6671f2fc106a97e109", + "id": "fls_h6b1fuw4nvi1", + "link": "program-structure-and-compilation.html#fls_h6b1fuw4nvi1", + "number": "18.6:5" + } + ], + "title": "Conditional Compilation" + }, + { + "id": "fls_8jb3sjqamdpu", + "informational": false, + "link": "program-structure-and-compilation.html#program-entry-point", + "number": "18.7", + "paragraphs": [ + { + "checksum": "2e5587a498db2567ffbf412e144bcbb85c9453ae0d1a9b9a2db81e2afa0338a5", + "id": "fls_dp64b08em9BJ", + "link": "program-structure-and-compilation.html#fls_dp64b08em9BJ", + "number": "18.7:1" }, { - "checksum": "b7abdeea8907fa3d7ecf4b6fab76bcf830d7e3beb440f7d181e32c35eaeed83f", - "id": "fls_6j616ydf2mnh", - "link": "generics.html#fls_6j616ydf2mnh", - "number": "12.1:21" + "checksum": "4211a854bc48f1108224e53ef3f2b18f213150fee9b6e0c6b0a8fdd3cde72ba6", + "id": "fls_sbGnkm8Ephiu", + "link": "program-structure-and-compilation.html#fls_sbGnkm8Ephiu", + "number": "18.7:2" }, { - "checksum": "3aa8b3cd82401a8ea45d3df4769bb90080dd5fec794696e299c905bf6ef1f6e9", - "id": "fls_hyi2jnp38v1n", - "link": "generics.html#fls_hyi2jnp38v1n", - "number": "12.1:22" + "checksum": "06806b5f2bb00b12d499429e29aa3f6916237f0c54e9136fd16474fae18c14fb", + "id": "fls_o4fxok23134r", + "link": "program-structure-and-compilation.html#fls_o4fxok23134r", + "number": "18.7:3" }, { - "checksum": "81cb87c32bbae85869876f23d65d0f1dfe05101ef4b92401e5bf21fcb3be05c2", - "id": "fls_sseo6u6pbcki", - "link": "generics.html#fls_sseo6u6pbcki", - "number": "12.1:23" + "checksum": "c791afca579a283e2645e4a3bf9473606a55028fcaec0a1fca0c5d87bf7db370", + "id": "fls_bk755pvc1l53", + "link": "program-structure-and-compilation.html#fls_bk755pvc1l53", + "number": "18.7:4" }, { - "checksum": "b515a8d6e6d86a8e289e2b7aa5bc1159cfbf1cab572c45f0cdeb32a01bbe3f39", - "id": "fls_62b59qvom3nm", - "link": "generics.html#fls_62b59qvom3nm", - "number": "12.1:24" + "checksum": "4eaf3f71fa346f0de97eaef7ae61b54e234608337d12733cee8930cd3a0a16be", + "id": "fls_a3je4wc53bmo", + "link": "program-structure-and-compilation.html#fls_a3je4wc53bmo", + "number": "18.7:5" }, { - "checksum": "dd989d910fcd0e9ef31de45877c6e06dccab6c17811741cfdcd8e3232a058440", - "id": "fls_oq76uff9gp0k", - "link": "generics.html#fls_oq76uff9gp0k", - "number": "12.1:25" + "checksum": "dc35e76e01d0f08b81b61a7c8861a422fa2dc8a5d44ce4557affba712d088d61", + "id": "fls_w8q15zp7kyl0", + "link": "program-structure-and-compilation.html#fls_w8q15zp7kyl0", + "number": "18.7:6" }, { - "checksum": "7e2a62316333f93e684c2cbdd31ae2f2247a93882350929bb5794b9012585c4a", - "id": "fls_ua3w16qo9o4", - "link": "generics.html#fls_ua3w16qo9o4", - "number": "12.1:26" + "checksum": "0605829262c1b010a633c97250c2141e26eabf10f42a901e0ffae239bc84909e", + "id": "fls_4psnfphsgdek", + "link": "program-structure-and-compilation.html#fls_4psnfphsgdek", + "number": "18.7:7" }, { - "checksum": "1e8333574e56710e3d837173743f166a40ae1198a9bbe2db35bb65a9387efbd7", - "id": "fls_w9ol06mldwb", - "link": "generics.html#fls_w9ol06mldwb", - "number": "12.1:27" + "checksum": "406115e1f2b804459a7155b5aa521a67fae4b9a11d3d9ee26b45671e0d705fad", + "id": "fls_m7xfrhqif74", + "link": "program-structure-and-compilation.html#fls_m7xfrhqif74", + "number": "18.7:8" }, { - "checksum": "b74efa68baa45a61d2fc231677ac14b4c52bf157f20599fd69f77c0e64bb0c52", - "id": "fls_g2pfrqhmeys8", - "link": "generics.html#fls_g2pfrqhmeys8", - "number": "12.1:28" + "checksum": "7527f425026d6c1455709270dbc862fa8c81edae9aa97bbf4c63b854ab77ea04", + "id": "fls_qq9fzrw4aykd", + "link": "program-structure-and-compilation.html#fls_qq9fzrw4aykd", + "number": "18.7:9" + } + ], + "title": "Program Entry Point" + } + ], + "title": "Program Structure and Compilation" + }, + { + "informational": false, + "link": "functions.html", + "sections": [ + { + "id": "fls_qcb1n9c0e5hz", + "informational": false, + "link": "functions.html", + "number": "9", + "paragraphs": [ + { + "checksum": "d85148abd62f72e649abbb14ec33bcf5dd833660e6d429978a1233e8aa19e739", + "id": "fls_gn1ngtx2tp2s", + "link": "functions.html#fls_gn1ngtx2tp2s", + "number": "9:1" }, { - "checksum": "3b21edfd98bd965b39de9b1be51cfe7cdc970789869e291378de930efadaf432", - "id": "fls_56jq9k9l31rt", - "link": "generics.html#fls_56jq9k9l31rt", - "number": "12.1:29" + "checksum": "0b7f0bc4680d19a82f2a03e22ab8228330095ac38f768e6138b1fa061209b0cd", + "id": "fls_bdx9gnnjxru3", + "link": "functions.html#fls_bdx9gnnjxru3", + "number": "9:2" }, { - "checksum": "a279226de7f4deabad4cad0f5ccc01b808665fe0b792c1440cd28ea0dee6a1af", - "id": "fls_sh669lnc5o1b", - "link": "generics.html#fls_sh669lnc5o1b", - "number": "12.1:30" + "checksum": "4ef42a17784ccf912f1917cdc894f87abaa5c3ae6bbc330e5d97884670ee69a3", + "id": "fls_87jnkimc15gi", + "link": "functions.html#fls_87jnkimc15gi", + "number": "9:3" }, { - "checksum": "c3b9ce106b0b3bf0edb649f3ad7591f172990a957c1509351873ac6bc8e2ff33", - "id": "fls_h6kx8dxh5u96", - "link": "generics.html#fls_h6kx8dxh5u96", - "number": "12.1:31" + "checksum": "1be13228840af974c47c9d4a16fb932edc3efcbbb1b5a5d8261d4eb3d4db1df5", + "id": "fls_nwywh1vjt6rr", + "link": "functions.html#fls_nwywh1vjt6rr", + "number": "9:4" }, { - "checksum": "b3ee8fb45410449e2ca276e3d66abc693b38dadb5e9e8ef1ceb39a221e3723a7", - "id": "fls_5r7ontjlmgwj", - "link": "generics.html#fls_5r7ontjlmgwj", - "number": "12.1:32" + "checksum": "e7bbdbb6cf00db43de1e27fe5b39e71062d7737351b11ee44c1f02ab45beabfb", + "id": "fls_uwuthzfgslif", + "link": "functions.html#fls_uwuthzfgslif", + "number": "9:5" }, { - "checksum": "3d225966adb08cb1aa29f3b24fe23f01e25214e9792f5d98b64e003b5304cda7", - "id": "fls_prbwj1pmng6k", - "link": "generics.html#fls_prbwj1pmng6k", - "number": "12.1:33" + "checksum": "d6307364051e9afc70d6e0e3e277a7a096ecb2eb1db51e356303d7ee87537a12", + "id": "fls_ymeo93t4mz4", + "link": "functions.html#fls_ymeo93t4mz4", + "number": "9:6" }, { - "checksum": "6a6f41cc694f562b1d6e82c8572ce7bf5293074d57b9ac4fa2295fca68a9c3b4", - "id": "fls_byqjs5fvy2bj", - "link": "generics.html#fls_byqjs5fvy2bj", - "number": "12.1:34" + "checksum": "2119a7ea0e7b937c1318f8b7cb342aea3ec7249cab773a35ff4b8a0701d43f4b", + "id": "fls_ijbt4tgnl95n", + "link": "functions.html#fls_ijbt4tgnl95n", + "number": "9:7" }, { - "checksum": "b444b7cac802870699dde273da603506e09e7c92cad9b7a76b012b2134d93623", - "id": "fls_reASCId0i117", - "link": "generics.html#fls_reASCId0i117", - "number": "12.1:35" + "checksum": "b7ee5ce366f421f0fae995e21d17b77d938b59f2f8a07975cb3aa3486b963304", + "id": "fls_AAYJDCNMJgTq", + "link": "functions.html#fls_AAYJDCNMJgTq", + "number": "9:8" }, { - "checksum": "262c55a1ca4176e7bb0383e3b3cd5532d1c40eef12bf5c590e822d1eb578980e", - "id": "fls_Ft5rGeL7QwJM", - "link": "generics.html#fls_Ft5rGeL7QwJM", - "number": "12.1:36" + "checksum": "29b6ace4daea786064b460a8bf0c7ddbe2b6800631188e26b46177257bf8fa33", + "id": "fls_PGtp39f6gJwU", + "link": "functions.html#fls_PGtp39f6gJwU", + "number": "9:9" }, { - "checksum": "3eb470b7a56fba5d570cf4b589b02985045858e64988244af2f47959e692a859", - "id": "fls_7epZNsTYNmgE", - "link": "generics.html#fls_7epZNsTYNmgE", - "number": "12.1:37" + "checksum": "2205b3649789767c105c262228a48536d8d9af2734351fcab5e8650eed044f19", + "id": "fls_yZ2yIXxmy2ri", + "link": "functions.html#fls_yZ2yIXxmy2ri", + "number": "9:10" }, { - "checksum": "3d51eaf5abfef6dc941672369b5996c25e3b6020e9194bfad120b423b897b112", - "id": "fls_TvuMMQnR0drL", - "link": "generics.html#fls_TvuMMQnR0drL", - "number": "12.1:38" + "checksum": "00ff6766b53e2501ce67b57b21ac801f24b86208afab3a509a0cd92439b2f346", + "id": "fls_35aSvBxBnIzm", + "link": "functions.html#fls_35aSvBxBnIzm", + "number": "9:11" }, { - "checksum": "34dda9625c7e9e6e463b2eb14643e6643f51c44406e3f2549783a1297f60000e", - "id": "fls_hidfwkwr2r73", - "link": "generics.html#fls_hidfwkwr2r73", - "number": "12.1:39" + "checksum": "d44af44daa180c96d7c066dd036be5c2217e9ef54e5440a28226f4155da1fbd9", + "id": "fls_Ogziu8S01qPQ", + "link": "functions.html#fls_Ogziu8S01qPQ", + "number": "9:12" }, { - "checksum": "560df02838ca1936d23bf28b88e4da6c469ed343b26bfb566756052758e64639", - "id": "fls_yaYedRNaxhTe", - "link": "generics.html#fls_yaYedRNaxhTe", - "number": "12.1:40" + "checksum": "e007c5d5fe350258fe85248f1a507b573c634bb18cf907d6b83babb1a8a98f5d", + "id": "fls_xCSsxYUZUFed", + "link": "functions.html#fls_xCSsxYUZUFed", + "number": "9:13" }, { - "checksum": "d94acf91ab922f3d4fab3151db5a559875aa4416a0b81a8c40460f9a8bb91bce", - "id": "fls_axMVssU7an8p", - "link": "generics.html#fls_axMVssU7an8p", - "number": "12.1:41" + "checksum": "afcad007b988dd4e9a7ae77d05318b5818905bf05921cd7d14070abe83ebb9b2", + "id": "fls_lxzinvqveuqh", + "link": "functions.html#fls_lxzinvqveuqh", + "number": "9:14" }, { - "checksum": "100a96f35cd7e62ed2a2f4d2e022d3849f148e226cee8eb17737c46aed7f6e4d", - "id": "fls_iAnCoxISuxxb", - "link": "generics.html#fls_iAnCoxISuxxb", - "number": "12.1:42" + "checksum": "af44866222e3ba28adab818deff3cff0fe079c936325cb39ed958ac977eaf988", + "id": "fls_kcAbTPZXQ5Y8", + "link": "functions.html#fls_kcAbTPZXQ5Y8", + "number": "9:15" }, { - "checksum": "3aeb3e42c07a42c3929a4700140366be5e8244d1bdc00cdc7204c4793592efd7", - "id": "fls_wUwJEGzjCUAT", - "link": "generics.html#fls_wUwJEGzjCUAT", - "number": "12.1:43" + "checksum": "a6f8a428953b804ae4d9a530c1659087e99139633acc2e99d4315bf792bf123a", + "id": "fls_PGDKWK7nPvgw", + "link": "functions.html#fls_PGDKWK7nPvgw", + "number": "9:16" }, { - "checksum": "cec64b26b4879d9cbe5f17b1fa6120340dc8781525f1a5bbfefca47d239ab328", - "id": "fls_BN5nUuvq9AwY", - "link": "generics.html#fls_BN5nUuvq9AwY", - "number": "12.1:44" + "checksum": "681f0328a6e3f3599494c7ab05e29ca62e56c28c57ecde0bbe7cd5b96d6db84d", + "id": "fls_o4uSLPo00KUg", + "link": "functions.html#fls_o4uSLPo00KUg", + "number": "9:17" }, { - "checksum": "23631de104768741b7048e7d01c59e1fffcba54b9dcef1ed7c615bb826fd202d", - "id": "fls_ouOm2TQKNsP3", - "link": "generics.html#fls_ouOm2TQKNsP3", - "number": "12.1:45" + "checksum": "2a3652b695660398bf0fcad1529e0346fed7c9fa6809502c46ecdb89c2bf93d6", + "id": "fls_icdzs1mjh0n4", + "link": "functions.html#fls_icdzs1mjh0n4", + "number": "9:18" }, { - "checksum": "a1c9458d5c17521a96ffec4d47ed6083ff2d3bff11ccd1eb479f8cf0b126b77e", - "id": "fls_m0bzw4jap6sg", - "link": "generics.html#fls_m0bzw4jap6sg", - "number": "12.1:46" + "checksum": "408643016febf0219e0809cd2e92e018cab38026b8e8612741262fc29b5daa00", + "id": "fls_OR85NVifPwjr", + "link": "functions.html#fls_OR85NVifPwjr", + "number": "9:19" }, { - "checksum": "42dcf2a32ea53ad56eb6ea1adf717442e8315af52daa703ab41a67eda8007371", - "id": "fls_vo7mgm34hwg2", - "link": "generics.html#fls_vo7mgm34hwg2", - "number": "12.1:47" - } - ], - "title": "Generic Parameters" - }, - { - "id": "fls_7nv8ualeaqe3", - "informational": false, - "link": "generics.html#where-clauses", - "number": "12.2", - "paragraphs": [ - { - "checksum": "64e83b1f5bdc0de2f6c5804ed902cd3cc1013057056ba936ee008c9f9fc6008f", - "id": "fls_3nqb7p5ifvio", - "link": "generics.html#fls_3nqb7p5ifvio", - "number": "12.2:1" + "checksum": "4bc2a2eb8ccee2c72bf5174dc901116ca5cf7ed223c6d8860b2bfbca11943925", + "id": "fls_4s2IdfYDzPrX", + "link": "functions.html#fls_4s2IdfYDzPrX", + "number": "9:20" }, { - "checksum": "a0586577beb2f969d6d4e05e56e5fd16a92fddb05d3708fbc5f6e39bb85718a5", - "id": "fls_fhy4rsmmbvyy", - "link": "generics.html#fls_fhy4rsmmbvyy", - "number": "12.2:2" + "checksum": "fca8a97c468b1b73283de8c4f98ad8e13873aba97f8c10a0392dab4826a7fe00", + "id": "fls_ZJJppPfiJRou", + "link": "functions.html#fls_ZJJppPfiJRou", + "number": "9:21" }, { - "checksum": "da1b8b29d9f0346fe7bd1d1c75f892569c746af9b54e1f7900d588a7e22887ac", - "id": "fls_V4PKFqtCsAv6", - "link": "generics.html#fls_V4PKFqtCsAv6", - "number": "12.2:3" + "checksum": "7dd1d3308749e298f5b2a22e71ac500637ce35b2d9cf6ccf24634f53e2885f12", + "id": "fls_jOyZh9ujWWHQ", + "link": "functions.html#fls_jOyZh9ujWWHQ", + "number": "9:22" }, { - "checksum": "1e4a1f1e63e1773a0421a5334c9e37808a94b3c0b255ce7c0595282179fb59fe", - "id": "fls_cslGPmVjujHD", - "link": "generics.html#fls_cslGPmVjujHD", - "number": "12.2:4" + "checksum": "99c1950124ddc4f31f88a1064b15e1cb78f9e374abb2eece4a801f0648c89300", + "id": "fls_Xdr0bFwxhWiB", + "link": "functions.html#fls_Xdr0bFwxhWiB", + "number": "9:23" }, { - "checksum": "d294be65a9a865ac01935be20857d638bd76b15bd0cadf121963475263530947", - "id": "fls_ytk74dyxuy6d", - "link": "generics.html#fls_ytk74dyxuy6d", - "number": "12.2:5" + "checksum": "898ff3d8792a073b155387b8ee5e51b48f03b4ad15845d393f6162730578c4a3", + "id": "fls_DpTFEHZAABdD", + "link": "functions.html#fls_DpTFEHZAABdD", + "number": "9:24" }, { - "checksum": "533a4ad543c2a168ed772a1a1f3a17b837f984e04ed0006caca9e6af274aa8e0", - "id": "fls_1xgw1dq60quz", - "link": "generics.html#fls_1xgw1dq60quz", - "number": "12.2:6" + "checksum": "421ab0ac3cb998e26b832f6f921db20f8d80617dce79045d9906b460e2dbbadb", + "id": "fls_b7FTlWfnX2OI", + "link": "functions.html#fls_b7FTlWfnX2OI", + "number": "9:25" }, { - "checksum": "3576fed5bb35f8d7f0dcbe136a5bc4184204ac3439c7f928a393292885b06fad", - "id": "fls_47s8i7pzb9gg", - "link": "generics.html#fls_47s8i7pzb9gg", - "number": "12.2:7" - } - ], - "title": "Where Clauses" - }, - { - "id": "fls_utuu8mdbuyxm", - "informational": false, - "link": "generics.html#generic-arguments", - "number": "12.3", - "paragraphs": [ + "checksum": "eda198b5564690046f7225520acae5222abaccf175ac28f4c3b8b13d0d0bbf14", + "id": "fls_6urL6fZ5cpaA", + "link": "functions.html#fls_6urL6fZ5cpaA", + "number": "9:26" + }, { - "checksum": "6d430096779c94fd8eb3ddbedf7572aa28ac912896c0110048a311cdd9df5966", - "id": "fls_3x6qd8vt5uus", - "link": "generics.html#fls_3x6qd8vt5uus", - "number": "12.3:1" + "checksum": "513827ea79b2bb852e3a03fc1d52ad23aaa08dccee280c50059a7e31df7d6139", + "id": "fls_TMOzb6cYIOlH", + "link": "functions.html#fls_TMOzb6cYIOlH", + "number": "9:27" }, { - "checksum": "b62123e2e8e883a4568f6e6c0ef85ad81748d23d32d49acc747eb1cfa2e22455", - "id": "fls_ky39fb2vcom6", - "link": "generics.html#fls_ky39fb2vcom6", - "number": "12.3:2" + "checksum": "15571a856ea4d62c8fe363b440ddf0f1a63e654322bf5c97a6fbc7973c16420e", + "id": "fls_eHPWHrvs7ETl", + "link": "functions.html#fls_eHPWHrvs7ETl", + "number": "9:28" }, { - "checksum": "f7ffa174a2e33bf090afe2aaf7920f3cbd827290f14b80321f47c88133659631", - "id": "fls_9n1ejjili06h", - "link": "generics.html#fls_9n1ejjili06h", - "number": "12.3:3" + "checksum": "42a5a6b2aef837c0262688bbc4f2dae48d988fd49bcbcbcd7a84175224069dd8", + "id": "fls_mjCrvmikm58M", + "link": "functions.html#fls_mjCrvmikm58M", + "number": "9:29" }, { - "checksum": "6233c170bc63c0936a92c1690c890775856ddd72117eac1e21fe2d129e41e462", - "id": "fls_9pda3ja0ihks", - "link": "generics.html#fls_9pda3ja0ihks", - "number": "12.3:4" + "checksum": "1b8b316d3d56bf10df2d8521160902a8137f08c3a5c8d39d5bf8f22900dac035", + "id": "fls_4EUb9zFatZ97", + "link": "functions.html#fls_4EUb9zFatZ97", + "number": "9:30" }, { - "checksum": "0b4345fd5441f93487f171af2a4808f0906463958768378cc9cddbdd025112be", - "id": "fls_mcUMWsYcxzmZ", - "link": "generics.html#fls_mcUMWsYcxzmZ", - "number": "12.3:5" + "checksum": "a2e6957d275225e3cb26e75af5b4e0d3d31afa9b43a77eaef4117c78d920ac52", + "id": "fls_4B4B5FIqAes9", + "link": "functions.html#fls_4B4B5FIqAes9", + "number": "9:31" }, { - "checksum": "c0dc22d848de08245f3a9c656abb0b3b78b94a6950405d8e33ac343efbc395b8", - "id": "fls_dxMfAI4EZVS5", - "link": "generics.html#fls_dxMfAI4EZVS5", - "number": "12.3:6" + "checksum": "b82bf4578023aa2218ec01202b93d193cbb48fd8b53d740a5c9d3bb482caaf10", + "id": "fls_vljy4mm0zca2", + "link": "functions.html#fls_vljy4mm0zca2", + "number": "9:32" }, { - "checksum": "ab86084835389c5804317b18cc5a85c36e5c031018783e0555e6ba607799deea", - "id": "fls_i3z9ueoe99zd", - "link": "generics.html#fls_i3z9ueoe99zd", - "number": "12.3:7" + "checksum": "b89caf540730b919c47a1f20bdd5dc9302aca3ec99300e25098600be2e706acf", + "id": "fls_EqJb3Jl3vK8K", + "link": "functions.html#fls_EqJb3Jl3vK8K", + "number": "9:33" }, { - "checksum": "63142cba165b2178690155f575392011f1134c14184e81e36820ddb73cae4f92", - "id": "fls_al4dhmqodvwc", - "link": "generics.html#fls_al4dhmqodvwc", - "number": "12.3:8" + "checksum": "e2ca22f3a68ea69c89d5499ab8bfaff4ea879242e37d601e8b78939484e3fbd9", + "id": "fls_C7dvzcXcpQCy", + "link": "functions.html#fls_C7dvzcXcpQCy", + "number": "9:34" }, { - "checksum": "a8831e92d9d0bfcb4b315dbcc507de27e5867c90165c66bf816329d11fcff100", - "id": "fls_10k9gdxlpuls", - "link": "generics.html#fls_10k9gdxlpuls", - "number": "12.3:9" + "checksum": "2f2202efc417e1cd3118f5b446cf8c8a375b6e5f8ebaed72a0201aa9d161578a", + "id": "fls_J8X8ahnJLrMo", + "link": "functions.html#fls_J8X8ahnJLrMo", + "number": "9:35" }, { - "checksum": "971129d56022b37c66ec71073289a4dedfa09aa435ddc1c7cae97b1a9d1eaf7a", - "id": "fls_d4vdvpihoeb1", - "link": "generics.html#fls_d4vdvpihoeb1", - "number": "12.3:10" + "checksum": "f01d77c3faa9dafff3f5a3ca3928a29d60e2867a2438140aaaa4cbceb25da31b", + "id": "fls_927nfm5mkbsp", + "link": "functions.html#fls_927nfm5mkbsp", + "number": "9:36" }, { - "checksum": "c8fdfaaca0e4c05cb61bbb042227c630e92a1a4a039e0f4d8a3c100941ff6a07", - "id": "fls_ukarc98ceesz", - "link": "generics.html#fls_ukarc98ceesz", - "number": "12.3:11" + "checksum": "a8e601de98599b419876278ab5521c5cc49ae6bfdacfe061350c283e236e211f", + "id": "fls_yfm0jh62oaxr", + "link": "functions.html#fls_yfm0jh62oaxr", + "number": "9:37" }, { - "checksum": "a874a0d306f839d2ffcec82a884d903ffe0ebbadaca947ebaaca90dbad22f79d", - "id": "fls_l88o2snx9qbt", - "link": "generics.html#fls_l88o2snx9qbt", - "number": "12.3:12" + "checksum": "45189d0422353cd31d5e3d1460ac0bf25731cc9f464574e6acf3ba0a8661dffb", + "id": "fls_bHwy8FLzEUi3", + "link": "functions.html#fls_bHwy8FLzEUi3", + "number": "9:38" }, { - "checksum": "4b7b540224edea54abc7c76442e8f3bcd837894f2694c4da61a31aa02132aff1", - "id": "fls_thpj9io9tyuy", - "link": "generics.html#fls_thpj9io9tyuy", - "number": "12.3:13" - } - ], - "title": "Generic Arguments" - }, - { - "id": "fls_i7g2n7hfg3ch", - "informational": false, - "link": "generics.html#generic-conformance", - "number": "12.4", - "paragraphs": [ + "checksum": "1952dd7798e9fd4d8fb5f525eedc05613f2ef1a660b93bfa8f24167e67f839bb", + "id": "fls_5Q861wb08DU3", + "link": "functions.html#fls_5Q861wb08DU3", + "number": "9:39" + }, { - "checksum": "d1612a32235e7d6f04d58b8aa1fd91f9a6a088559668f8ef79b51f4827cf4564", - "id": "fls_CBWyxBJeYeb2", - "link": "generics.html#fls_CBWyxBJeYeb2", - "number": "12.4:1" + "checksum": "a336a489c323ad22b7aa3059350a2400bb8e41f5eef6ce02b17682c438a9fd09", + "id": "fls_owdlsaaygtho", + "link": "functions.html#fls_owdlsaaygtho", + "number": "9:40" }, { - "checksum": "debb7f2966e0cb74a4f4216168dd052c70fd3d6a8c27fb2f3aa0c7fe47f76a98", - "id": "fls_ltch5eivxgaa", - "link": "generics.html#fls_ltch5eivxgaa", - "number": "12.4:2" + "checksum": "4355b34dd22393af15f6fad33ae3c69fc7091736b751b78d2b18cb15ceb7b3cc", + "id": "fls_2049qu3ji5x7", + "link": "functions.html#fls_2049qu3ji5x7", + "number": "9:41" }, { - "checksum": "639ff9a5038d7adda9b3bba839e6ae21c85e388acb2f212f48ea6695f318c6f2", - "id": "fls_gb3mpt5rxjoa", - "link": "generics.html#fls_gb3mpt5rxjoa", - "number": "12.4:3" + "checksum": "515666a67e5b60858568348fd8b4c6021e49484b778d6ccd6c357f9b792643d1", + "id": "fls_7mlanuh5mvpn", + "link": "functions.html#fls_7mlanuh5mvpn", + "number": "9:42" }, { - "checksum": "af4df9b3ddac7de4a5237a0d32e24c6c2da88af6502df4979d83552fec77309d", - "id": "fls_kdeltu9dsd0d", - "link": "generics.html#fls_kdeltu9dsd0d", - "number": "12.4:4" + "checksum": "4ccbc87469f1565c32bef31f5909da4c2b2f1febaf71e3b0216053540e2732c4", + "id": "fls_otr3hgp8lj1q", + "link": "functions.html#fls_otr3hgp8lj1q", + "number": "9:43" }, { - "checksum": "d89280e4cf06cc3484d31ca3e0ae0d2e71ffb5073be76a1508c6a15273bb4b3d", - "id": "fls_ws1h57fk1mkh", - "link": "generics.html#fls_ws1h57fk1mkh", - "number": "12.4:5" + "checksum": "45a75ca66fc48e3abbe4a9b46d1599f9ecbcfae9fbc5df35e1b5d8939f41f8c4", + "id": "fls_m3jiunibqj81", + "link": "functions.html#fls_m3jiunibqj81", + "number": "9:44" }, { - "checksum": "91bce3d2e51c484c884932370ea99b161b0bf86288418a7b19cdaf1ab3ce5812", - "id": "fls_w0ozotuwtr9", - "link": "generics.html#fls_w0ozotuwtr9", - "number": "12.4:6" + "checksum": "388876fffc5a50a274a155691b5fe0142ca2651fe1ce20d17301a9682d3638f6", + "id": "fls_7vogmqyd87ey", + "link": "functions.html#fls_7vogmqyd87ey", + "number": "9:45" }, { - "checksum": "5e02e16f6c0da4bb4819ffb8a5e26e721a99432cf64b2e762ed419f7ad104c14", - "id": "fls_91bylteu35bi", - "link": "generics.html#fls_91bylteu35bi", - "number": "12.4:7" + "checksum": "a0ec3be9336e08cd1fca0ed1ac63762201261a4c25b465370a9c89d2c35e0647", + "id": "fls_7ucwmzqtittv", + "link": "functions.html#fls_7ucwmzqtittv", + "number": "9:46" }, { - "checksum": "a93335cddb2ff0f239fd61961f0f4b84cf8c28eda2aab38546504a0036037239", - "id": "fls_j6xtrxc6aik", - "link": "generics.html#fls_j6xtrxc6aik", - "number": "12.4:8" + "checksum": "77e3105531c29da1bb23103569cc04ef5d3377e723b9f8adcf7603d91463ceae", + "id": "fls_nUADhgcfvvGC", + "link": "functions.html#fls_nUADhgcfvvGC", + "number": "9:47" }, { - "checksum": "c2a32969042fbf3a990f2fbb15ccc7cec1c1fbbec38d11829756846543bea7b7", - "id": "fls_us7d30cbwgpz", - "link": "generics.html#fls_us7d30cbwgpz", - "number": "12.4:9" + "checksum": "fbf5fb05e9efc647400ab22d0002c1bc09b999d239850236501817c8374a143c", + "id": "fls_5hn8fkf7rcvz", + "link": "functions.html#fls_5hn8fkf7rcvz", + "number": "9:48" + } + ], + "title": "Functions" + } + ], + "title": "Functions" + }, + { + "informational": false, + "link": "ffi.html", + "sections": [ + { + "id": "fls_osd6c4utyjb3", + "informational": false, + "link": "ffi.html", + "number": "21", + "paragraphs": [ + { + "checksum": "9dbb89c33be2fd11861ca905be788d903d72880fdf1dece1bc8a63747a947445", + "id": "fls_djlglv2eaihl", + "link": "ffi.html#fls_djlglv2eaihl", + "number": "21:1" }, { - "checksum": "c87ac00ad30f5b3d158b3eefc327e17f8c53e5f2a7004df7dc4efb8f5c1a2210", - "id": "fls_dp3hpvf0fmr8", - "link": "generics.html#fls_dp3hpvf0fmr8", - "number": "12.4:10" + "checksum": "33f23ecced9a34c5f1aaaa909380603062d763f33056c5a2998e61ba20e81e21", + "id": "fls_k1hiwghzxtfa", + "link": "ffi.html#fls_k1hiwghzxtfa", + "number": "21:2" }, { - "checksum": "7a2547656618d6f84add7a3e50e946935bb92bd29224f1ee7a67bf7953e55b77", - "id": "fls_mg45zcguxxg5", - "link": "generics.html#fls_mg45zcguxxg5", - "number": "12.4:11" + "checksum": "e6918e7d46ef5f24647902726d860e1abaf15c6b4f3e50771748abe1156acb4a", + "id": "fls_3cgtdk4698hm", + "link": "ffi.html#fls_3cgtdk4698hm", + "number": "21:3" }, { - "checksum": "9b6af5832d05ba38ea6da1de3aaaf043a9e42d0142f7294124fcbf2f0d95c818", - "id": "fls_mDgq5XjzKAl3", - "link": "generics.html#fls_mDgq5XjzKAl3", - "number": "12.4:12" + "checksum": "b297f2f7182e518adc328b76e8e51549d74fd016d982e945f3afad959b8e4fee", + "id": "fls_shzmgci4f7o5", + "link": "ffi.html#fls_shzmgci4f7o5", + "number": "21:4" }, { - "checksum": "55339f55c4a3fa7c05dfb3379cd23dd3de7da6d00309d6c5af63eb38de6204bf", - "id": "fls_YufUgB25ovh3", - "link": "generics.html#fls_YufUgB25ovh3", - "number": "12.4:13" + "checksum": "fc67b06bdfa241b1112995e95bea87b539fd31dee8c24e402fcd9d7af65eed2f", + "id": "fls_m7x5odt4nb23", + "link": "ffi.html#fls_m7x5odt4nb23", + "number": "21:5" }, { - "checksum": "07a280c09abe97494fb7a02a246eb3eecf3c280c763026b64c83005f8cace2fa", - "id": "fls_OhVxhJ23x7W2", - "link": "generics.html#fls_OhVxhJ23x7W2", - "number": "12.4:14" + "checksum": "ed6db46b8be18e820fbb6dfc0dd19df1bb2ac9687dacf002b00c217d8ddbf625", + "id": "fls_4akfvpq1yg4g", + "link": "ffi.html#fls_4akfvpq1yg4g", + "number": "21:6" }, { - "checksum": "139fbdc2aef75a36c1a73ea55319255892f7fac16e00d0f5da5ab0e832366b16", - "id": "fls_Kyar0jH9BqeW", - "link": "generics.html#fls_Kyar0jH9BqeW", - "number": "12.4:15" + "checksum": "a0f18eb2fe8db99bebeadf847111b16dfaa78ebdbaec8e8f7b364e6a40966ebf", + "id": "fls_9d8v0xeyi0f", + "link": "ffi.html#fls_9d8v0xeyi0f", + "number": "21:7" } ], - "title": "Generic Conformance" - } - ], - "title": "Generics" - }, - { - "informational": true, - "link": "general.html", - "sections": [ + "title": "FFI" + }, { - "id": "fls_48qldfwwh493", + "id": "fls_usgd0xlijoxv", "informational": false, - "link": "general.html", - "number": "1", + "link": "ffi.html#abi", + "number": "21.1", "paragraphs": [ { - "checksum": "b56c8be8103d8226bbdec210a97ca8f2c38405d36f65f17b15936a1a8284b26c", - "id": "fls_c4ry0kgmvk9z", - "link": "general.html#fls_c4ry0kgmvk9z", - "number": "1:1" + "checksum": "5907d37d1b47c67dacab00d10295ab91ec2974c77016fc225a28413fdcecf896", + "id": "fls_xangrq3tfze0", + "link": "ffi.html#fls_xangrq3tfze0", + "number": "21.1:1" }, { - "checksum": "4a8fd448c0e40fd0675f5d82c36b6f963f99259021c6022cc9bd5104c20fbdeb", - "id": "fls_gxqbj0qoiaio", - "link": "general.html#fls_gxqbj0qoiaio", - "number": "1:2" + "checksum": "303e206ff87fda7c72da6833ecc1035fb2e1bc4de71a73e3c1d6fce85b12b068", + "id": "fls_2w0xi6rxw3uz", + "link": "ffi.html#fls_2w0xi6rxw3uz", + "number": "21.1:2" }, { - "checksum": "02be35a62aff5e48d56d3fb05c75ade724aff55d6ad8278715bd7fac8adbe03f", - "id": "fls_u8k9zr8da30", - "link": "general.html#fls_u8k9zr8da30", - "number": "1:3" + "checksum": "a7324b60f99f6fb51284703d13f58460e71099e1fae889aca7365c5c7c1b5976", + "id": "fls_9zitf1fvvfk8", + "link": "ffi.html#fls_9zitf1fvvfk8", + "number": "21.1:3" }, { - "checksum": "11a39a0b407d0ca45db7639f588e85fe27910624c6c508283f983229a6aad5db", - "id": "fls_8mt9iigoboba", - "link": "general.html#fls_8mt9iigoboba", - "number": "1:4" + "checksum": "9deadcb79842b0adc5ae917673a5a388d2bd4ed4c1732bf5a25e4e650395ca87", + "id": "fls_x7ct9k82fpgn", + "link": "ffi.html#fls_x7ct9k82fpgn", + "number": "21.1:4" }, { - "checksum": "cd1b728c4e56af1dd90836a76afce9239d07ea2aa3095cbeedf8693a22f14caf", - "id": "fls_suhf2g3fatfa", - "link": "general.html#fls_suhf2g3fatfa", - "number": "1:5" + "checksum": "c336142c062c3b0f4238fbadf03105300bfcc6e13ec8c02ac9c02aa0bb4dcf50", + "id": "fls_LfjvLXvI6TFL", + "link": "ffi.html#fls_LfjvLXvI6TFL", + "number": "21.1:5" }, { - "checksum": "8b4dce11b14d66a6a1177ef131384957fe6a887c388d56a1902e09ee60f80af0", - "id": "fls_jjr5kbn0wuco", - "link": "general.html#fls_jjr5kbn0wuco", - "number": "1:6" + "checksum": "39df1027aa9c166e4e50895b0a788131e1f5f2323e7cfd31b5e06e5b3c128c65", + "id": "fls_a2d8ltpgtvn6", + "link": "ffi.html#fls_a2d8ltpgtvn6", + "number": "21.1:6" }, { - "checksum": "3a278c9aaaabec045a6eede8eb5a0beab745d8b84b098a39b6cdf2e167bc5178", - "id": "fls_4dfcjyprkzbx", - "link": "general.html#fls_4dfcjyprkzbx", - "number": "1:7" + "checksum": "8d4568b2885cee4938f3ce35c1c9f07aa864308806a5619f5de6eaa0b738a5a4", + "id": "fls_8m7pc3riokst", + "link": "ffi.html#fls_8m7pc3riokst", + "number": "21.1:7" + }, + { + "checksum": "1e90ce91871f63afbcb47fc5716511ce8ef86c066f3b8bb81d43bc7856c543a8", + "id": "fls_NQAzj5ai1La5", + "link": "ffi.html#fls_NQAzj5ai1La5", + "number": "21.1:8" }, { - "checksum": "1e02dbccc64818a6cebcc7d6331d8281a24621e813178d0f5be9d0b3356c7a22", - "id": "fls_tq9jcv5ddvwn", - "link": "general.html#fls_tq9jcv5ddvwn", - "number": "1:8" - } - ], - "title": "General" - }, - { - "id": "fls_fo1c7pg2mw1", - "informational": false, - "link": "general.html#scope", - "number": "1.1", - "paragraphs": [ - { - "checksum": "2ae4f7088e5e2b9b1e598a90db6d4b81803bb3f3c23163762643ad5bff9045ec", - "id": "fls_srdq4mota5pr", - "link": "general.html#fls_srdq4mota5pr", - "number": "1.1:1" + "checksum": "bde0533495e75b4ceb809bb02e397e0d5daa1d7c3d1977865fcaf0ff5f4d989a", + "id": "fls_r2drzo3dixe4", + "link": "ffi.html#fls_r2drzo3dixe4", + "number": "21.1:9" }, { - "checksum": "b3cdec704c28577193c03cd2eb90cec360e5651ed07b259d112673a67dc0200d", - "id": "fls_dv1qish8svc", - "link": "general.html#fls_dv1qish8svc", - "number": "1.1:2" - } - ], - "title": "Scope" - }, - { - "id": "fls_10yukmkhl0ng", - "informational": false, - "link": "general.html#extent", - "number": "1.1.1", - "paragraphs": [ + "checksum": "907e87e17820da8559bd8a486992acf18956b1456ecc61cb90278065e01374ca", + "id": "fls_z2kzyin8dyr7", + "link": "ffi.html#fls_z2kzyin8dyr7", + "number": "21.1:10" + }, { - "checksum": "07358ef439d78086f970b0606b6da177cb6cc2e64318f27af92832c21a67be43", - "id": "fls_x78yd1sszydv", - "link": "general.html#fls_x78yd1sszydv", - "number": "1.1.1:1" + "checksum": "c2d1059fcd1026468253d1771f735c5e6a5bab43177231babeb00190e68633a2", + "id": "fls_j6pqchx27ast", + "link": "ffi.html#fls_j6pqchx27ast", + "number": "21.1:11" }, { - "checksum": "818ed40aac2e52250ea0014e5de7fbcbcf1e8f325b74a015ac0085699523a43d", - "id": "fls_9e032738udnb", - "link": "general.html#fls_9e032738udnb", - "number": "1.1.1:2" + "checksum": "1454a6149650f43e10f4254d309ba83ae39d3052b579c45d26bed5c429ce1bc4", + "id": "fls_dbbfqaqa80r8", + "link": "ffi.html#fls_dbbfqaqa80r8", + "number": "21.1:12" }, { - "checksum": "85861dc839d41712fe2a39712eb1dcfd1dc88ba30f3782f92bd1986fe355c196", - "id": "fls_jk7scu5xs17z", - "link": "general.html#fls_jk7scu5xs17z", - "number": "1.1.1:3" + "checksum": "3015fb781cf27af98cfdb17a199fc06a282c4995bf513e4f23460aa87a24fefd", + "id": "fls_UippZpUyYpHl", + "link": "ffi.html#fls_UippZpUyYpHl", + "number": "21.1:13" }, { - "checksum": "dc11c0150f1c1ea89b5e2e31e3d90c45c62fa5d26005eb9ed3a16e3f7a3fb68c", - "id": "fls_jiryupa5fxgf", - "link": "general.html#fls_jiryupa5fxgf", - "number": "1.1.1:4" + "checksum": "1da7fe0a23d93d524fed3110b8c116e9b7fe7f1563c51830ffd8b98ce562b9dd", + "id": "fls_36qrs2fxxvi7", + "link": "ffi.html#fls_36qrs2fxxvi7", + "number": "21.1:14" }, { - "checksum": "4ce6c16caad38e7ccb47adc109089c53cc047073ebba0a06457cbeded05f68e0", - "id": "fls_sph1a3sapinh", - "link": "general.html#fls_sph1a3sapinh", - "number": "1.1.1:5" + "checksum": "d0f9c8d00a857e8d69b98fadc01f1222ca7838660e92dbf4c571d07df302a222", + "id": "fls_CIyK8BYzzo26", + "link": "ffi.html#fls_CIyK8BYzzo26", + "number": "21.1:15" }, { - "checksum": "7c348ba84b24afee5a0a39c44f15af8c05a07208877750c9a766d44ab767da7b", - "id": "fls_7tm19jxtffc8", - "link": "general.html#fls_7tm19jxtffc8", - "number": "1.1.1:6" + "checksum": "f392bf3391787b024699ac732bffa8db2661c5d3f6e13335913304f6b7f38b20", + "id": "fls_JHlqXjn4Sf07", + "link": "ffi.html#fls_JHlqXjn4Sf07", + "number": "21.1:16" }, { - "checksum": "585a289861cd9b12366dc2314022378903323fea3cdbd6863a2e5bd96ed69446", - "id": "fls_5pbrl8lhuth1", - "link": "general.html#fls_5pbrl8lhuth1", - "number": "1.1.1:7" + "checksum": "42ee2288dd6a0bde172c2aa19bdd6c764e4c21913a22a4d9d65065b478aa14ba", + "id": "fls_6rtj6rwqxojh", + "link": "ffi.html#fls_6rtj6rwqxojh", + "number": "21.1:17" }, { - "checksum": "fc97ac93c58134f54e07f9f6ff5334dbf3afdc8b1e707525ddd8eb232a915c74", - "id": "fls_o8fc3e53vp7g", - "link": "general.html#fls_o8fc3e53vp7g", - "number": "1.1.1:8" + "checksum": "a81529042a5b1033d4dfd23dc355a422cafc5e8a94dac4134dcc1cdbe6d6b01d", + "id": "fls_d3nmpc5mtg27", + "link": "ffi.html#fls_d3nmpc5mtg27", + "number": "21.1:18" }, { - "checksum": "8adc0c63aa968e623b7251ecc0776057325470f81d39879bb6dfb23d1985a407", - "id": "fls_rw0y5t13y6gs", - "link": "general.html#fls_rw0y5t13y6gs", - "number": "1.1.1:9" + "checksum": "fab1088ce02afa3c8566f0f5033efbd1b6cfec69fc639a64273cc39e32f479e5", + "id": "fls_7t7yxh94wnbl", + "link": "ffi.html#fls_7t7yxh94wnbl", + "number": "21.1:19" }, { - "checksum": "2f2276e84ea3e0251d343a2c859bafeefc4eb09d8d7222774ce538650c01980b", - "id": "fls_x7c3o621qj9z", - "link": "general.html#fls_x7c3o621qj9z", - "number": "1.1.1:10" + "checksum": "5d748e814d1f01129f8b66ffb05acc435c309ab75eeff0b4e74925cc1e8b786b", + "id": "fls_ccFdnlX5HIYk", + "link": "ffi.html#fls_ccFdnlX5HIYk", + "number": "21.1:20" }, { - "checksum": "cd7a1f68c81b43b8536b355596a13da9ddc2e33d7b37a18af00eee2456c3fd06", - "id": "fls_5y2b6yjcl1vz", - "link": "general.html#fls_5y2b6yjcl1vz", - "number": "1.1.1:11" + "checksum": "aa45e16b2c9fb1e7d7ecf23ca38806bc2f369b1d1be518e74d28e24e6b54a8b4", + "id": "fls_sxj4vy39sj4g", + "link": "ffi.html#fls_sxj4vy39sj4g", + "number": "21.1:21" }, { - "checksum": "a695d7fe1b36b6516b8530ec034376b8c43e7aa53cd0795f48e39482be5a38cf", - "id": "fls_8dennhk2dha", - "link": "general.html#fls_8dennhk2dha", - "number": "1.1.1:12" + "checksum": "563032c0e9c472e10f9c487c70925926c24107c3258cc003fb04dd03b00c21af", + "id": "fls_tyjs1x4j8ovp", + "link": "ffi.html#fls_tyjs1x4j8ovp", + "number": "21.1:22" }, { - "checksum": "65c2d0a375d2b29b8836f90cfdf174fc8fe72f5198073097bb9401add5d0880b", - "id": "fls_j2gs3hrbxtyx", - "link": "general.html#fls_j2gs3hrbxtyx", - "number": "1.1.1:13" + "checksum": "985fd96881b7c12b952e2f2a81cdc5713cd19ec157141a68232ac19172c96907", + "id": "fls_xrCRprWS13R1", + "link": "ffi.html#fls_xrCRprWS13R1", + "number": "21.1:23" }, { - "checksum": "94c8f2e37b34e95fffa62df717bbcd7d87653a8506a65f52d91e1706e0dfe9d8", - "id": "fls_gy2c7vfwkd8j", - "link": "general.html#fls_gy2c7vfwkd8j", - "number": "1.1.1:14" + "checksum": "2eb97e47f68715623197b729da4072c1fd6dd94cc8a38e41f135832c8ac7312e", + "id": "fls_M4LqHf8hbPA8", + "link": "ffi.html#fls_M4LqHf8hbPA8", + "number": "21.1:24" } ], - "title": "Extent" + "title": "ABI" }, { - "id": "fls_xscgklvg1wd2", + "id": "fls_tmoh3y9oyqsy", "informational": false, - "link": "general.html#structure", - "number": "1.1.2", + "link": "ffi.html#external-blocks", + "number": "21.2", "paragraphs": [ { - "checksum": "9d1641a50a597e4ce0553ceca94bbf6c254d57933639a13b9b13f852c10f4640", - "id": "fls_6lrqailxjb02", - "link": "general.html#fls_6lrqailxjb02", - "number": "1.1.2:1" + "checksum": "5690487aad48826d0fbca987e21993dde238a0492461e8b3e35e3f49b1810f50", + "id": "fls_4dje9t5y2dia", + "link": "ffi.html#fls_4dje9t5y2dia", + "number": "21.2:1" }, { - "checksum": "13aa072e39fbda618587953663ec86d1a188fe0eb4db97252b906ca8e9c2fe51", - "id": "fls_tys7ciqnp8bn", - "link": "general.html#fls_tys7ciqnp8bn", - "number": "1.1.2:2" + "checksum": "e9d30e0c8d7251366fcba4b5a93322f61b5d415d8ea85b870b7a8de0eae5728d", + "id": "fls_8ltVLtAfvy0m", + "link": "ffi.html#fls_8ltVLtAfvy0m", + "number": "21.2:2" }, { - "checksum": "934ba26ee073ed0a5cbaf9b98cf9005d3786c907b9de25f60f31c18a1d5ddb1c", - "id": "fls_3ubhkaheu8i1", - "link": "general.html#fls_3ubhkaheu8i1", - "number": "1.1.2:3" + "checksum": "8fa9f3206c1f1e9af22942ab5569d11a808e99fcb06365a0c2f5be68d1af8497", + "id": "fls_Nz0l16hMxqTd", + "link": "ffi.html#fls_Nz0l16hMxqTd", + "number": "21.2:3" }, { - "checksum": "b9623aca56a3fe17319f63a0b550b6405ad21f99012d17ae6f6ce41d1f1605d4", - "id": "fls_xw3grr2g5zgi", - "link": "general.html#fls_xw3grr2g5zgi", - "number": "1.1.2:4" + "checksum": "be19b76230f53b9d539f838eb6c73119e8210045835c5c82f58a9bd1876de4b7", + "id": "fls_4XOoiFloXM7t", + "link": "ffi.html#fls_4XOoiFloXM7t", + "number": "21.2:4" }, { - "checksum": "11c90805ef23752a416f5a6a8b69230febfdf97b68edaf26f6517bd359492443", - "id": "fls_6srbinvnyd54", - "link": "general.html#fls_6srbinvnyd54", - "number": "1.1.2:5" + "checksum": "dd7c836fa590b14a7da0c87f48b6eb0d11c83010e9f4ac1e0615ebecc9ba7eb3", + "id": "fls_PBsepNHImJKH", + "link": "ffi.html#fls_PBsepNHImJKH", + "number": "21.2:5" + } + ], + "title": "External Blocks" + }, + { + "id": "fls_yztwtek0y34v", + "informational": false, + "link": "ffi.html#external-functions", + "number": "21.3", + "paragraphs": [ + { + "checksum": "0104ba914d1d2a6dafc2b7914cc94254e1ab3678864a25c1864a89740a54294f", + "id": "fls_v24ino4hix3m", + "link": "ffi.html#fls_v24ino4hix3m", + "number": "21.3:1" }, { - "checksum": "fdcfcebd0c9395a151b773b35ef5d5e2720a08080c54f279652f7668a0182897", - "id": "fls_ciixfg9jhv42", - "link": "general.html#fls_ciixfg9jhv42", - "number": "1.1.2:6" + "checksum": "f42fee85a3cead90e0424d6ae591a72b9d777fa19fdaee7718a36a484f0a8166", + "id": "fls_l88r9fj82650", + "link": "ffi.html#fls_l88r9fj82650", + "number": "21.3:2" }, { - "checksum": "25a56f970c722a9217989c3250b9b75798453ac229d32dc74b97c2a6c84a2109", - "id": "fls_ej94lm2682kg", - "link": "general.html#fls_ej94lm2682kg", - "number": "1.1.2:7" + "checksum": "9abb006e22c228a40953b442c54e63fd9a10cc675390d5e7d61b1fb7221ec353", + "id": "fls_qwchgvvnp0qe", + "link": "ffi.html#fls_qwchgvvnp0qe", + "number": "21.3:3" }, { - "checksum": "1531d2cfb29ab712e3adf7fc798c2342b8977bae5175dda990efd578592d2454", - "id": "fls_xgk91jrbpyoc", - "link": "general.html#fls_xgk91jrbpyoc", - "number": "1.1.2:8" + "checksum": "af35e63fe34e63713827f5525caee153a033f726c0cf64300b5612781daf29fc", + "id": "fls_w00qi1gx204e", + "link": "ffi.html#fls_w00qi1gx204e", + "number": "21.3:4" }, { - "checksum": "db3c5f8206004bb2cca82ca219a417ec2a4fe259773ae667ab3d10114d5740ee", - "id": "fls_jc4upf6685bw", - "link": "general.html#fls_jc4upf6685bw", - "number": "1.1.2:9" + "checksum": "9ff777cfdc4b91eb8472493cfbc9e96b5e66329d621fb37d3c8603b420f63f79", + "id": "fls_m7tu4w4lk8v", + "link": "ffi.html#fls_m7tu4w4lk8v", + "number": "21.3:5" }, { - "checksum": "1594b619675d7802bccfee40ca10262be9916e29f70d8f7fecbf5dddd00246f4", - "id": "fls_oxzjqxgejx9t", - "link": "general.html#fls_oxzjqxgejx9t", - "number": "1.1.2:10" + "checksum": "69809b6272d266f36a20ad706492a4e3561e0afa136d36145d8804f83456d978", + "id": "fls_rdu4723vp0oo", + "link": "ffi.html#fls_rdu4723vp0oo", + "number": "21.3:6" }, { - "checksum": "acf02a4a250adb8e29284b356a6f4a386a936d32f0e85d3b304e708863f10929", - "id": "fls_gmx688d6ek1o", - "link": "general.html#fls_gmx688d6ek1o", - "number": "1.1.2:11" + "checksum": "8a5942ade1d1568f8a2e335a8767ab295816829fc64e2c4ef250f53feee9ec69", + "id": "fls_9div9yusw64h", + "link": "ffi.html#fls_9div9yusw64h", + "number": "21.3:7" }, { - "checksum": "7f053104dd4947498f1bb5121b9fe28a23d78ead65d37dd580df0111d558d8dd", - "id": "fls_5zdjikp1jhc", - "link": "general.html#fls_5zdjikp1jhc", - "number": "1.1.2:12" + "checksum": "0e21072dc94fa05d0ff274b95c7cdfe5e96a5fddabfd26720dd20bcd7aecbf38", + "id": "fls_juob30rst11r", + "link": "ffi.html#fls_juob30rst11r", + "number": "21.3:8" + } + ], + "title": "External Functions" + }, + { + "id": "fls_s4yt19sptl7d", + "informational": false, + "link": "ffi.html#external-statics", + "number": "21.4", + "paragraphs": [ + { + "checksum": "a342af289e2343c6a6c563bb75a44dd7f003efc17cc5dddaf9db25fc1395f17c", + "id": "fls_8ddsytjr4il6", + "link": "ffi.html#fls_8ddsytjr4il6", + "number": "21.4:1" }, { - "checksum": "44025e9b54bdddecd35c847f5628ffddbfc9e4721cd733f41c9186c904ef15b1", - "id": "fls_as5bhc5t285g", - "link": "general.html#fls_as5bhc5t285g", - "number": "1.1.2:13" + "checksum": "7456ec21405a4f2629a47ca17fd031b08d4bf3cde66139fe81c3b11248264ca1", + "id": "fls_H0cg9XMaGz0y", + "link": "ffi.html#fls_H0cg9XMaGz0y", + "number": "21.4:2" }, { - "checksum": "6efef41c7a703e3b3562b12e95047110a954b19026060d22099de222c142ed8b", - "id": "fls_70qjvaqoz007", - "link": "general.html#fls_70qjvaqoz007", - "number": "1.1.2:14" + "checksum": "6bfbc90ac6b0ebe2bf403b473eb2876a892442339359bafeb4fa203aa258570c", + "id": "fls_fo9with6xumo", + "link": "ffi.html#fls_fo9with6xumo", + "number": "21.4:3" }, { - "checksum": "525edc88be77e71d6777eef00907902bcd024abb3c401a92d67f1ca997971c5a", - "id": "fls_o4rdsbc7u98", - "link": "general.html#fls_o4rdsbc7u98", - "number": "1.1.2:15" + "checksum": "1e3dcb44d797a493ed568db4d4a7c292794fa22ef50cc810f94492b86ec20c75", + "id": "fls_tr7purzcldn0", + "link": "ffi.html#fls_tr7purzcldn0", + "number": "21.4:4" }, { - "checksum": "3ab99fa52627e6796ca7e01a7242052885523e5e0f93b9dd98ec22d483688e60", - "id": "fls_w8j575w2hmc8", - "link": "general.html#fls_w8j575w2hmc8", - "number": "1.1.2:16" + "checksum": "0fb23c674fc5c6699fc7b3869fdc36f00d967880169275042498442dc58a8872", + "id": "fls_en2h09ehj0j3", + "link": "ffi.html#fls_en2h09ehj0j3", + "number": "21.4:5" } ], - "title": "Structure" + "title": "External Statics" + } + ], + "title": "FFI" + }, + { + "informational": true, + "link": "glossary.html", + "sections": [], + "title": "Glossary" + }, + { + "informational": false, + "link": "generics.html", + "sections": [ + { + "id": "fls_y2k5paj8m8ug", + "informational": false, + "link": "generics.html", + "number": "12", + "paragraphs": [], + "title": "Generics" }, { - "id": "fls_99b7xi1bkgih", + "id": "fls_vhpwge5123cm", "informational": false, - "link": "general.html#conformity", - "number": "1.1.3", + "link": "generics.html#generic-parameters", + "number": "12.1", "paragraphs": [ { - "checksum": "42aa78f31bd39d9d54d95d846fc09f42910052efc6f2cb7eb50a7b34706f7c97", - "id": "fls_kdyqtnc6loam", - "link": "general.html#fls_kdyqtnc6loam", - "number": "1.1.3:1" + "checksum": "94863fbf1d8fdb0023847e7bb69f0c04a488fd3b0541bc6a43dfa580ab0d16ae", + "id": "fls_sye3d17l9bf5", + "link": "generics.html#fls_sye3d17l9bf5", + "number": "12.1:1" }, { - "checksum": "1b1e377b795c593c09c9e640deb4d270fad22997bd2c697ee3bb4b3c4109ecca", - "id": "fls_dBKu9jgx3OyH", - "link": "general.html#fls_dBKu9jgx3OyH", - "number": "1.1.3:2" + "checksum": "a9b102aea4ead34bd9a1dfb0ee5d0b12b91ec2b099a59d0a4320521f772835eb", + "id": "fls_dalqke3rznrb", + "link": "generics.html#fls_dalqke3rznrb", + "number": "12.1:2" }, { - "checksum": "8ade643b2872a946f749a6125849c9d9af29cb64ca9c611b05332fae6c4ceacb", - "id": "fls_faRvWyJJpno8", - "link": "general.html#fls_faRvWyJJpno8", - "number": "1.1.3:3" + "checksum": "090ffb52196f4e68695b1c484beb6bd8507d06d2f679ee8177decd8b6b34ca79", + "id": "fls_pi6eukz7kc99", + "link": "generics.html#fls_pi6eukz7kc99", + "number": "12.1:3" }, { - "checksum": "581e966a0c27c9ea6c0cbfb6d1bf739d24fb579f2495fc002947b3e93977c231", - "id": "fls_GZmxrO61eiJ1", - "link": "general.html#fls_GZmxrO61eiJ1", - "number": "1.1.3:4" + "checksum": "59bebc9aaade7a8e216d5acd0230c4a561df1b66eb30289c51eac737ae0037fa", + "id": "fls_ixmgqupxvf73", + "link": "generics.html#fls_ixmgqupxvf73", + "number": "12.1:4" }, { - "checksum": "1f8c2956cb9f8b946f0f56053a038a9ab8efaf6dd7881bb5d1d65e1c87b05900", - "id": "fls_nnmx2qsu14ft", - "link": "general.html#fls_nnmx2qsu14ft", - "number": "1.1.3:5" + "checksum": "ebd86684687d8a3b3d796280d1bd02eb718235e578e2e90f8b1ccf6fb6b90a97", + "id": "fls_z311nxou9yi3", + "link": "generics.html#fls_z311nxou9yi3", + "number": "12.1:5" }, { - "checksum": "adb8b69470c26508c51b735aa65a30d464dae4eecafb85f3cad7b62450557213", - "id": "fls_gu3331rmv2ho", - "link": "general.html#fls_gu3331rmv2ho", - "number": "1.1.3:6" + "checksum": "09faa28093efe5bea8695465a1b6ccf9dd591a83377126e6f7ca32bffc783f0a", + "id": "fls_wmcp0n36jlbr", + "link": "generics.html#fls_wmcp0n36jlbr", + "number": "12.1:6" }, { - "checksum": "030c86e984b66288b589337553d3375d3fdecc0dfab304381123495642596b20", - "id": "fls_AR8ZIYlDRSNs", - "link": "general.html#fls_AR8ZIYlDRSNs", - "number": "1.1.3:7" + "checksum": "7dd9f9ccb26b4083d870e727d44fd7cb36573bf661c6ab932ad22dc96e4b6afb", + "id": "fls_h42kg56vsefx", + "link": "generics.html#fls_h42kg56vsefx", + "number": "12.1:7" }, { - "checksum": "09785f4e7f3719f15b9790de8e8388bce97f35323591af71ed34b453c9b2ef30", - "id": "fls_xAYhvEh7WWel", - "link": "general.html#fls_xAYhvEh7WWel", - "number": "1.1.3:8" + "checksum": "b76e6307e1cd2582b525354a84e5bc604d7d52679124f2807619890557dd350d", + "id": "fls_372h3oevejih", + "link": "generics.html#fls_372h3oevejih", + "number": "12.1:8" }, { - "checksum": "01a005ca17491af3519bb4e745fbef2a83ce55109d5787034a94659c1dd03f36", - "id": "fls_QvFpU8v5p8Hb", - "link": "general.html#fls_QvFpU8v5p8Hb", - "number": "1.1.3:9" + "checksum": "d022534e05abd6632faed1f9a99e08afd782e729b0e5c46a046e8fec4761e6f9", + "id": "fls_u8mqct93yimd", + "link": "generics.html#fls_u8mqct93yimd", + "number": "12.1:9" }, { - "checksum": "849ea928df375ac126378ee4fd16534119a4f03f42721cd25145edc4d7850294", - "id": "fls_pl0fyjcwslqm", - "link": "general.html#fls_pl0fyjcwslqm", - "number": "1.1.3:10" + "checksum": "4159da8372c73ecd16af81e45a142b09a10de3d7f3881a9c6c554040938dbb10", + "id": "fls_vpcqgec83ybt", + "link": "generics.html#fls_vpcqgec83ybt", + "number": "12.1:10" }, { - "checksum": "534de23af559805d2b96e284fdd9afc9838e9e3f65b120c5401aa7cf130d439b", - "id": "fls_lkdm0mdghppv", - "link": "general.html#fls_lkdm0mdghppv", - "number": "1.1.3:11" + "checksum": "043b0fbba2086316df6f3152903a683a0584cc386e2b3f477ef71e328a293e4f", + "id": "fls_3SjMBlc0b7qo", + "link": "generics.html#fls_3SjMBlc0b7qo", + "number": "12.1:11" }, { - "checksum": "f87533ba7b47cb56f59c27e8d99d031d3c5c35b16c4d3ae271ddb9b3ee18c33b", - "id": "fls_d07x1mbhgpsd", - "link": "general.html#fls_d07x1mbhgpsd", - "number": "1.1.3:12" - } - ], - "title": "Conformity" - }, - { - "id": "fls_79rl6ylmct07", - "informational": false, - "link": "general.html#method-of-description-and-syntax-notation", - "number": "1.1.4", - "paragraphs": [ + "checksum": "54f245efbc506709a192db34e53895d362594afb8f4ee96986ce6afca49b04d9", + "id": "fls_p4yb8EAXlRU0", + "link": "generics.html#fls_p4yb8EAXlRU0", + "number": "12.1:12" + }, { - "checksum": "8811c1ef8e9707861f67d171dc813cc290188343fde68d68304072ab9d422c41", - "id": "fls_mc4a28do6kcp", - "link": "general.html#fls_mc4a28do6kcp", - "number": "1.1.4:1" + "checksum": "ca1335fd71570adbb1aed7643857b961a8bb6086fb3b9f64925800c41ef14658", + "id": "fls_4a2qshaf5se7", + "link": "generics.html#fls_4a2qshaf5se7", + "number": "12.1:13" }, { - "checksum": "e62cf2d485b81b81793c685b6fef64ad6d407da40349a45129bfd298fb8d0258", - "id": "fls_ioyp4wux6skt", - "link": "general.html#fls_ioyp4wux6skt", - "number": "1.1.4:2" + "checksum": "05c11421678358c4279fc92f0982a56f16c452fb5b5d3c38d3ec77f99e14ca97", + "id": "fls_s0nrjwqg2wox", + "link": "generics.html#fls_s0nrjwqg2wox", + "number": "12.1:14" }, { - "checksum": "5be24f2bcda1727c3b6e1ad27d2d7c8ffe6e487d292f2b48bd528e411c2ae1be", - "id": "fls_jsflt7691ye4", - "link": "general.html#fls_jsflt7691ye4", - "number": "1.1.4:3" + "checksum": "835447fa3ddfa7c5fd06897805b57986740381f9ecf8daf09fa6e6d1acb24377", + "id": "fls_2grtygcj8o3", + "link": "generics.html#fls_2grtygcj8o3", + "number": "12.1:15" }, { - "checksum": "15f27fa3164d4f34e687dff0589a9406f64fd304a5cbac1d917e215a3420b24d", - "id": "fls_98fm7z04lq9", - "link": "general.html#fls_98fm7z04lq9", - "number": "1.1.4:4" + "checksum": "65063a7df97c072c5bd36c4029e9de91a88b81719d83ee267e7c338688871249", + "id": "fls_95eooah0vcqx", + "link": "generics.html#fls_95eooah0vcqx", + "number": "12.1:16" + }, + { + "checksum": "b181eee5f3b1e0741189576664df8a3753448934eca0622dc2324ff23f54a85a", + "id": "fls_ahCqtkh0m5sR", + "link": "generics.html#fls_ahCqtkh0m5sR", + "number": "12.1:17" + }, + { + "checksum": "a39d6db0400cfba65a04cbc96b9586a37ef6f64de62ec283e56c4de6319823e9", + "id": "fls_3qZRBp9j26w3", + "link": "generics.html#fls_3qZRBp9j26w3", + "number": "12.1:18" + }, + { + "checksum": "03a4ce65a6524d90af458d25675d8237300b3ed290fccc708255707b27a42460", + "id": "fls_x4s7p2v981r6", + "link": "generics.html#fls_x4s7p2v981r6", + "number": "12.1:19" + }, + { + "checksum": "9d983e5bcc9005a2002c87f988931d048bb50221b71831ddf7af2d550d53e90a", + "id": "fls_jzfk9fspzqja", + "link": "generics.html#fls_jzfk9fspzqja", + "number": "12.1:20" }, { - "checksum": "bf197e4e3db74f8f9189b2d39392b793c47f8e26908e5ff493a91d3567e2e58d", - "id": "fls_ceb5a8t6cakr", - "link": "general.html#fls_ceb5a8t6cakr", - "number": "1.1.4:5" + "checksum": "b7abdeea8907fa3d7ecf4b6fab76bcf830d7e3beb440f7d181e32c35eaeed83f", + "id": "fls_6j616ydf2mnh", + "link": "generics.html#fls_6j616ydf2mnh", + "number": "12.1:21" }, { - "checksum": "e53521056d56c3f85475811368708e304047e64d39a20604584fa7f38d9c7f86", - "id": "fls_pts29mb5ld68", - "link": "general.html#fls_pts29mb5ld68", - "number": "1.1.4:6" + "checksum": "3aa8b3cd82401a8ea45d3df4769bb90080dd5fec794696e299c905bf6ef1f6e9", + "id": "fls_hyi2jnp38v1n", + "link": "generics.html#fls_hyi2jnp38v1n", + "number": "12.1:22" }, { - "checksum": "48013ee45666937c8ad55fb39de161c383ead549216e3bdc9e2882e83bcf0a90", - "id": "fls_gqjo5oh7vn3b", - "link": "general.html#fls_gqjo5oh7vn3b", - "number": "1.1.4:7" + "checksum": "81cb87c32bbae85869876f23d65d0f1dfe05101ef4b92401e5bf21fcb3be05c2", + "id": "fls_sseo6u6pbcki", + "link": "generics.html#fls_sseo6u6pbcki", + "number": "12.1:23" }, { - "checksum": "f6d36c515bd4436ca87447bc369bb7b1c69b50ca9215b1d6981dabce39f33593", - "id": "fls_1dz634xp8xp5", - "link": "general.html#fls_1dz634xp8xp5", - "number": "1.1.4:8" + "checksum": "b515a8d6e6d86a8e289e2b7aa5bc1159cfbf1cab572c45f0cdeb32a01bbe3f39", + "id": "fls_62b59qvom3nm", + "link": "generics.html#fls_62b59qvom3nm", + "number": "12.1:24" }, { - "checksum": "949f3857c5bbb056b780f44c08df02afc05d59db42be92d94a86c97a20ae4943", - "id": "fls_pp9vtjlyblrl", - "link": "general.html#fls_pp9vtjlyblrl", - "number": "1.1.4:9" + "checksum": "dd989d910fcd0e9ef31de45877c6e06dccab6c17811741cfdcd8e3232a058440", + "id": "fls_oq76uff9gp0k", + "link": "generics.html#fls_oq76uff9gp0k", + "number": "12.1:25" }, { - "checksum": "d7b7b2e401f1bc24ffd16c1f417d18c3d12336cc2d2325ecc1c07d77ea0e513e", - "id": "fls_6e2vd9fvhsmk", - "link": "general.html#fls_6e2vd9fvhsmk", - "number": "1.1.4:10" + "checksum": "7e2a62316333f93e684c2cbdd31ae2f2247a93882350929bb5794b9012585c4a", + "id": "fls_ua3w16qo9o4", + "link": "generics.html#fls_ua3w16qo9o4", + "number": "12.1:26" }, { - "checksum": "0eda08ae37976f0d7d15570d790c9bc3107ea4533794e554872ea0d77af06d9c", - "id": "fls_4onq0kkrt6qv", - "link": "general.html#fls_4onq0kkrt6qv", - "number": "1.1.4:11" + "checksum": "1e8333574e56710e3d837173743f166a40ae1198a9bbe2db35bb65a9387efbd7", + "id": "fls_w9ol06mldwb", + "link": "generics.html#fls_w9ol06mldwb", + "number": "12.1:27" }, { - "checksum": "bf1b757e4fec099109dc75529b8709e0177c379698e8c762c5c27fe8fe0ac2d3", - "id": "fls_qu4rsmnq659w", - "link": "general.html#fls_qu4rsmnq659w", - "number": "1.1.4:12" + "checksum": "b74efa68baa45a61d2fc231677ac14b4c52bf157f20599fd69f77c0e64bb0c52", + "id": "fls_g2pfrqhmeys8", + "link": "generics.html#fls_g2pfrqhmeys8", + "number": "12.1:28" }, { - "checksum": "ca3586f81ab3962899f5a1a79386ef8637003025a729a5b76bebdb5d1e43cb28", - "id": "fls_rllu7aksf17e", - "link": "general.html#fls_rllu7aksf17e", - "number": "1.1.4:13" + "checksum": "3b21edfd98bd965b39de9b1be51cfe7cdc970789869e291378de930efadaf432", + "id": "fls_56jq9k9l31rt", + "link": "generics.html#fls_56jq9k9l31rt", + "number": "12.1:29" }, { - "checksum": "87380f5c66b721c614a404b1852a6c1f93b93d02b04517c8be6e401f85832f82", - "id": "fls_blvsfqeevosr", - "link": "general.html#fls_blvsfqeevosr", - "number": "1.1.4:14" + "checksum": "a279226de7f4deabad4cad0f5ccc01b808665fe0b792c1440cd28ea0dee6a1af", + "id": "fls_sh669lnc5o1b", + "link": "generics.html#fls_sh669lnc5o1b", + "number": "12.1:30" }, { - "checksum": "d05cec36bfece44c3203af30f098975bd0f3d0cccd9687f0956cd9eca66c810f", - "id": "fls_lwcjq3wzjyvb", - "link": "general.html#fls_lwcjq3wzjyvb", - "number": "1.1.4:15" + "checksum": "c3b9ce106b0b3bf0edb649f3ad7591f172990a957c1509351873ac6bc8e2ff33", + "id": "fls_h6kx8dxh5u96", + "link": "generics.html#fls_h6kx8dxh5u96", + "number": "12.1:31" }, { - "checksum": "9a579e74f8de12da445c5197e2555aab86fc739d0defc1f685c0aa5dfb44cebc", - "id": "fls_v7wd5yk00im6", - "link": "general.html#fls_v7wd5yk00im6", - "number": "1.1.4:16" + "checksum": "b3ee8fb45410449e2ca276e3d66abc693b38dadb5e9e8ef1ceb39a221e3723a7", + "id": "fls_5r7ontjlmgwj", + "link": "generics.html#fls_5r7ontjlmgwj", + "number": "12.1:32" }, { - "checksum": "afb0e775413f09199a584413d501b6ec88d107c1be87cac6e07bc33ecce6ad74", - "id": "fls_nf8alga8uz6c", - "link": "general.html#fls_nf8alga8uz6c", - "number": "1.1.4:17" - } - ], - "title": "Method of Description and Syntax Notation" - }, - { - "id": "fls_9cd746qe40ag", - "informational": false, - "link": "general.html#versioning", - "number": "1.2", - "paragraphs": [ + "checksum": "3d225966adb08cb1aa29f3b24fe23f01e25214e9792f5d98b64e003b5304cda7", + "id": "fls_prbwj1pmng6k", + "link": "generics.html#fls_prbwj1pmng6k", + "number": "12.1:33" + }, { - "checksum": "ac30f10574356a3a3472c8858b8b13e8b8ef034312c5823a3674a594b4e4e468", - "id": "fls_l80e3kdwnldc", - "link": "general.html#fls_l80e3kdwnldc", - "number": "1.2:1" - } - ], - "title": "Versioning" - }, - { - "id": "fls_ijzgf4h0mp3c", - "informational": false, - "link": "general.html#definitions", - "number": "1.3", - "paragraphs": [ + "checksum": "6a6f41cc694f562b1d6e82c8572ce7bf5293074d57b9ac4fa2295fca68a9c3b4", + "id": "fls_byqjs5fvy2bj", + "link": "generics.html#fls_byqjs5fvy2bj", + "number": "12.1:34" + }, { - "checksum": "34f82930e1b6b4af471b30519f81d43725341882ad0a3e8e36d85ab7b3f5713b", - "id": "fls_sm2kexes5pr7", - "link": "general.html#fls_sm2kexes5pr7", - "number": "1.3:1" + "checksum": "b444b7cac802870699dde273da603506e09e7c92cad9b7a76b012b2134d93623", + "id": "fls_reASCId0i117", + "link": "generics.html#fls_reASCId0i117", + "number": "12.1:35" }, { - "checksum": "397ee14d96917a0a9033a587347a2246e4118f1270d64cce0f7eefeeaf565980", - "id": "fls_2o98zw29xc46", - "link": "general.html#fls_2o98zw29xc46", - "number": "1.3:2" + "checksum": "262c55a1ca4176e7bb0383e3b3cd5532d1c40eef12bf5c590e822d1eb578980e", + "id": "fls_Ft5rGeL7QwJM", + "link": "generics.html#fls_Ft5rGeL7QwJM", + "number": "12.1:36" }, { - "checksum": "969a1f3b691a3118abcd815618a71feba07fdd5be4c2a12ce37d0b49595d013a", - "id": "fls_lon5qffd65fi", - "link": "general.html#fls_lon5qffd65fi", - "number": "1.3:3" + "checksum": "3eb470b7a56fba5d570cf4b589b02985045858e64988244af2f47959e692a859", + "id": "fls_7epZNsTYNmgE", + "link": "generics.html#fls_7epZNsTYNmgE", + "number": "12.1:37" }, { - "checksum": "0d6e2d5075e9ccebe0f79ee2d1d393c07fae6b8d4b6cbf7fbf59b1348185fc5d", - "id": "fls_qeolgxvcy75", - "link": "general.html#fls_qeolgxvcy75", - "number": "1.3:4" + "checksum": "3d51eaf5abfef6dc941672369b5996c25e3b6020e9194bfad120b423b897b112", + "id": "fls_TvuMMQnR0drL", + "link": "generics.html#fls_TvuMMQnR0drL", + "number": "12.1:38" }, { - "checksum": "70b86c11c1708aa3d4d6b52af002352aebaef77835c4b45ebc4233a789844230", - "id": "fls_h2m244agxaxs", - "link": "general.html#fls_h2m244agxaxs", - "number": "1.3:5" + "checksum": "34dda9625c7e9e6e463b2eb14643e6643f51c44406e3f2549783a1297f60000e", + "id": "fls_hidfwkwr2r73", + "link": "generics.html#fls_hidfwkwr2r73", + "number": "12.1:39" }, { - "checksum": "c669dced28f1bb44dbac7e17e855e184b1f14baecfa15e22b21d2379a5998be6", - "id": "fls_47svine904xk", - "link": "general.html#fls_47svine904xk", - "number": "1.3:6" - } - ], - "title": "Definitions" - } - ], - "title": "General" - }, - { - "informational": false, - "link": "ffi.html", - "sections": [ - { - "id": "fls_osd6c4utyjb3", - "informational": false, - "link": "ffi.html", - "number": "21", - "paragraphs": [ + "checksum": "560df02838ca1936d23bf28b88e4da6c469ed343b26bfb566756052758e64639", + "id": "fls_yaYedRNaxhTe", + "link": "generics.html#fls_yaYedRNaxhTe", + "number": "12.1:40" + }, { - "checksum": "9dbb89c33be2fd11861ca905be788d903d72880fdf1dece1bc8a63747a947445", - "id": "fls_djlglv2eaihl", - "link": "ffi.html#fls_djlglv2eaihl", - "number": "21:1" + "checksum": "d94acf91ab922f3d4fab3151db5a559875aa4416a0b81a8c40460f9a8bb91bce", + "id": "fls_axMVssU7an8p", + "link": "generics.html#fls_axMVssU7an8p", + "number": "12.1:41" }, { - "checksum": "33f23ecced9a34c5f1aaaa909380603062d763f33056c5a2998e61ba20e81e21", - "id": "fls_k1hiwghzxtfa", - "link": "ffi.html#fls_k1hiwghzxtfa", - "number": "21:2" + "checksum": "100a96f35cd7e62ed2a2f4d2e022d3849f148e226cee8eb17737c46aed7f6e4d", + "id": "fls_iAnCoxISuxxb", + "link": "generics.html#fls_iAnCoxISuxxb", + "number": "12.1:42" }, { - "checksum": "e6918e7d46ef5f24647902726d860e1abaf15c6b4f3e50771748abe1156acb4a", - "id": "fls_3cgtdk4698hm", - "link": "ffi.html#fls_3cgtdk4698hm", - "number": "21:3" + "checksum": "3aeb3e42c07a42c3929a4700140366be5e8244d1bdc00cdc7204c4793592efd7", + "id": "fls_wUwJEGzjCUAT", + "link": "generics.html#fls_wUwJEGzjCUAT", + "number": "12.1:43" }, { - "checksum": "b297f2f7182e518adc328b76e8e51549d74fd016d982e945f3afad959b8e4fee", - "id": "fls_shzmgci4f7o5", - "link": "ffi.html#fls_shzmgci4f7o5", - "number": "21:4" + "checksum": "cec64b26b4879d9cbe5f17b1fa6120340dc8781525f1a5bbfefca47d239ab328", + "id": "fls_BN5nUuvq9AwY", + "link": "generics.html#fls_BN5nUuvq9AwY", + "number": "12.1:44" }, { - "checksum": "fc67b06bdfa241b1112995e95bea87b539fd31dee8c24e402fcd9d7af65eed2f", - "id": "fls_m7x5odt4nb23", - "link": "ffi.html#fls_m7x5odt4nb23", - "number": "21:5" + "checksum": "23631de104768741b7048e7d01c59e1fffcba54b9dcef1ed7c615bb826fd202d", + "id": "fls_ouOm2TQKNsP3", + "link": "generics.html#fls_ouOm2TQKNsP3", + "number": "12.1:45" }, { - "checksum": "ed6db46b8be18e820fbb6dfc0dd19df1bb2ac9687dacf002b00c217d8ddbf625", - "id": "fls_4akfvpq1yg4g", - "link": "ffi.html#fls_4akfvpq1yg4g", - "number": "21:6" + "checksum": "a1c9458d5c17521a96ffec4d47ed6083ff2d3bff11ccd1eb479f8cf0b126b77e", + "id": "fls_m0bzw4jap6sg", + "link": "generics.html#fls_m0bzw4jap6sg", + "number": "12.1:46" }, { - "checksum": "a0f18eb2fe8db99bebeadf847111b16dfaa78ebdbaec8e8f7b364e6a40966ebf", - "id": "fls_9d8v0xeyi0f", - "link": "ffi.html#fls_9d8v0xeyi0f", - "number": "21:7" + "checksum": "42dcf2a32ea53ad56eb6ea1adf717442e8315af52daa703ab41a67eda8007371", + "id": "fls_vo7mgm34hwg2", + "link": "generics.html#fls_vo7mgm34hwg2", + "number": "12.1:47" } ], - "title": "FFI" + "title": "Generic Parameters" }, { - "id": "fls_usgd0xlijoxv", + "id": "fls_7nv8ualeaqe3", "informational": false, - "link": "ffi.html#abi", - "number": "21.1", + "link": "generics.html#where-clauses", + "number": "12.2", "paragraphs": [ { - "checksum": "5907d37d1b47c67dacab00d10295ab91ec2974c77016fc225a28413fdcecf896", - "id": "fls_xangrq3tfze0", - "link": "ffi.html#fls_xangrq3tfze0", - "number": "21.1:1" - }, - { - "checksum": "303e206ff87fda7c72da6833ecc1035fb2e1bc4de71a73e3c1d6fce85b12b068", - "id": "fls_2w0xi6rxw3uz", - "link": "ffi.html#fls_2w0xi6rxw3uz", - "number": "21.1:2" - }, - { - "checksum": "a7324b60f99f6fb51284703d13f58460e71099e1fae889aca7365c5c7c1b5976", - "id": "fls_9zitf1fvvfk8", - "link": "ffi.html#fls_9zitf1fvvfk8", - "number": "21.1:3" - }, - { - "checksum": "9deadcb79842b0adc5ae917673a5a388d2bd4ed4c1732bf5a25e4e650395ca87", - "id": "fls_x7ct9k82fpgn", - "link": "ffi.html#fls_x7ct9k82fpgn", - "number": "21.1:4" - }, - { - "checksum": "c336142c062c3b0f4238fbadf03105300bfcc6e13ec8c02ac9c02aa0bb4dcf50", - "id": "fls_LfjvLXvI6TFL", - "link": "ffi.html#fls_LfjvLXvI6TFL", - "number": "21.1:5" + "checksum": "64e83b1f5bdc0de2f6c5804ed902cd3cc1013057056ba936ee008c9f9fc6008f", + "id": "fls_3nqb7p5ifvio", + "link": "generics.html#fls_3nqb7p5ifvio", + "number": "12.2:1" }, { - "checksum": "39df1027aa9c166e4e50895b0a788131e1f5f2323e7cfd31b5e06e5b3c128c65", - "id": "fls_a2d8ltpgtvn6", - "link": "ffi.html#fls_a2d8ltpgtvn6", - "number": "21.1:6" + "checksum": "a0586577beb2f969d6d4e05e56e5fd16a92fddb05d3708fbc5f6e39bb85718a5", + "id": "fls_fhy4rsmmbvyy", + "link": "generics.html#fls_fhy4rsmmbvyy", + "number": "12.2:2" }, { - "checksum": "8d4568b2885cee4938f3ce35c1c9f07aa864308806a5619f5de6eaa0b738a5a4", - "id": "fls_8m7pc3riokst", - "link": "ffi.html#fls_8m7pc3riokst", - "number": "21.1:7" + "checksum": "da1b8b29d9f0346fe7bd1d1c75f892569c746af9b54e1f7900d588a7e22887ac", + "id": "fls_V4PKFqtCsAv6", + "link": "generics.html#fls_V4PKFqtCsAv6", + "number": "12.2:3" }, { - "checksum": "1e90ce91871f63afbcb47fc5716511ce8ef86c066f3b8bb81d43bc7856c543a8", - "id": "fls_NQAzj5ai1La5", - "link": "ffi.html#fls_NQAzj5ai1La5", - "number": "21.1:8" + "checksum": "1e4a1f1e63e1773a0421a5334c9e37808a94b3c0b255ce7c0595282179fb59fe", + "id": "fls_cslGPmVjujHD", + "link": "generics.html#fls_cslGPmVjujHD", + "number": "12.2:4" }, { - "checksum": "bde0533495e75b4ceb809bb02e397e0d5daa1d7c3d1977865fcaf0ff5f4d989a", - "id": "fls_r2drzo3dixe4", - "link": "ffi.html#fls_r2drzo3dixe4", - "number": "21.1:9" + "checksum": "d294be65a9a865ac01935be20857d638bd76b15bd0cadf121963475263530947", + "id": "fls_ytk74dyxuy6d", + "link": "generics.html#fls_ytk74dyxuy6d", + "number": "12.2:5" }, { - "checksum": "907e87e17820da8559bd8a486992acf18956b1456ecc61cb90278065e01374ca", - "id": "fls_z2kzyin8dyr7", - "link": "ffi.html#fls_z2kzyin8dyr7", - "number": "21.1:10" + "checksum": "533a4ad543c2a168ed772a1a1f3a17b837f984e04ed0006caca9e6af274aa8e0", + "id": "fls_1xgw1dq60quz", + "link": "generics.html#fls_1xgw1dq60quz", + "number": "12.2:6" }, { - "checksum": "c2d1059fcd1026468253d1771f735c5e6a5bab43177231babeb00190e68633a2", - "id": "fls_j6pqchx27ast", - "link": "ffi.html#fls_j6pqchx27ast", - "number": "21.1:11" - }, + "checksum": "3576fed5bb35f8d7f0dcbe136a5bc4184204ac3439c7f928a393292885b06fad", + "id": "fls_47s8i7pzb9gg", + "link": "generics.html#fls_47s8i7pzb9gg", + "number": "12.2:7" + } + ], + "title": "Where Clauses" + }, + { + "id": "fls_utuu8mdbuyxm", + "informational": false, + "link": "generics.html#generic-arguments", + "number": "12.3", + "paragraphs": [ { - "checksum": "1454a6149650f43e10f4254d309ba83ae39d3052b579c45d26bed5c429ce1bc4", - "id": "fls_dbbfqaqa80r8", - "link": "ffi.html#fls_dbbfqaqa80r8", - "number": "21.1:12" + "checksum": "6d430096779c94fd8eb3ddbedf7572aa28ac912896c0110048a311cdd9df5966", + "id": "fls_3x6qd8vt5uus", + "link": "generics.html#fls_3x6qd8vt5uus", + "number": "12.3:1" }, { - "checksum": "3015fb781cf27af98cfdb17a199fc06a282c4995bf513e4f23460aa87a24fefd", - "id": "fls_UippZpUyYpHl", - "link": "ffi.html#fls_UippZpUyYpHl", - "number": "21.1:13" + "checksum": "b62123e2e8e883a4568f6e6c0ef85ad81748d23d32d49acc747eb1cfa2e22455", + "id": "fls_ky39fb2vcom6", + "link": "generics.html#fls_ky39fb2vcom6", + "number": "12.3:2" }, { - "checksum": "1da7fe0a23d93d524fed3110b8c116e9b7fe7f1563c51830ffd8b98ce562b9dd", - "id": "fls_36qrs2fxxvi7", - "link": "ffi.html#fls_36qrs2fxxvi7", - "number": "21.1:14" + "checksum": "f7ffa174a2e33bf090afe2aaf7920f3cbd827290f14b80321f47c88133659631", + "id": "fls_9n1ejjili06h", + "link": "generics.html#fls_9n1ejjili06h", + "number": "12.3:3" }, { - "checksum": "d0f9c8d00a857e8d69b98fadc01f1222ca7838660e92dbf4c571d07df302a222", - "id": "fls_CIyK8BYzzo26", - "link": "ffi.html#fls_CIyK8BYzzo26", - "number": "21.1:15" + "checksum": "6233c170bc63c0936a92c1690c890775856ddd72117eac1e21fe2d129e41e462", + "id": "fls_9pda3ja0ihks", + "link": "generics.html#fls_9pda3ja0ihks", + "number": "12.3:4" }, { - "checksum": "f392bf3391787b024699ac732bffa8db2661c5d3f6e13335913304f6b7f38b20", - "id": "fls_JHlqXjn4Sf07", - "link": "ffi.html#fls_JHlqXjn4Sf07", - "number": "21.1:16" + "checksum": "0b4345fd5441f93487f171af2a4808f0906463958768378cc9cddbdd025112be", + "id": "fls_mcUMWsYcxzmZ", + "link": "generics.html#fls_mcUMWsYcxzmZ", + "number": "12.3:5" }, { - "checksum": "42ee2288dd6a0bde172c2aa19bdd6c764e4c21913a22a4d9d65065b478aa14ba", - "id": "fls_6rtj6rwqxojh", - "link": "ffi.html#fls_6rtj6rwqxojh", - "number": "21.1:17" + "checksum": "c0dc22d848de08245f3a9c656abb0b3b78b94a6950405d8e33ac343efbc395b8", + "id": "fls_dxMfAI4EZVS5", + "link": "generics.html#fls_dxMfAI4EZVS5", + "number": "12.3:6" }, { - "checksum": "a81529042a5b1033d4dfd23dc355a422cafc5e8a94dac4134dcc1cdbe6d6b01d", - "id": "fls_d3nmpc5mtg27", - "link": "ffi.html#fls_d3nmpc5mtg27", - "number": "21.1:18" + "checksum": "ab86084835389c5804317b18cc5a85c36e5c031018783e0555e6ba607799deea", + "id": "fls_i3z9ueoe99zd", + "link": "generics.html#fls_i3z9ueoe99zd", + "number": "12.3:7" }, { - "checksum": "fab1088ce02afa3c8566f0f5033efbd1b6cfec69fc639a64273cc39e32f479e5", - "id": "fls_7t7yxh94wnbl", - "link": "ffi.html#fls_7t7yxh94wnbl", - "number": "21.1:19" + "checksum": "63142cba165b2178690155f575392011f1134c14184e81e36820ddb73cae4f92", + "id": "fls_al4dhmqodvwc", + "link": "generics.html#fls_al4dhmqodvwc", + "number": "12.3:8" }, { - "checksum": "5d748e814d1f01129f8b66ffb05acc435c309ab75eeff0b4e74925cc1e8b786b", - "id": "fls_ccFdnlX5HIYk", - "link": "ffi.html#fls_ccFdnlX5HIYk", - "number": "21.1:20" + "checksum": "a8831e92d9d0bfcb4b315dbcc507de27e5867c90165c66bf816329d11fcff100", + "id": "fls_10k9gdxlpuls", + "link": "generics.html#fls_10k9gdxlpuls", + "number": "12.3:9" }, { - "checksum": "aa45e16b2c9fb1e7d7ecf23ca38806bc2f369b1d1be518e74d28e24e6b54a8b4", - "id": "fls_sxj4vy39sj4g", - "link": "ffi.html#fls_sxj4vy39sj4g", - "number": "21.1:21" + "checksum": "971129d56022b37c66ec71073289a4dedfa09aa435ddc1c7cae97b1a9d1eaf7a", + "id": "fls_d4vdvpihoeb1", + "link": "generics.html#fls_d4vdvpihoeb1", + "number": "12.3:10" }, { - "checksum": "563032c0e9c472e10f9c487c70925926c24107c3258cc003fb04dd03b00c21af", - "id": "fls_tyjs1x4j8ovp", - "link": "ffi.html#fls_tyjs1x4j8ovp", - "number": "21.1:22" + "checksum": "c8fdfaaca0e4c05cb61bbb042227c630e92a1a4a039e0f4d8a3c100941ff6a07", + "id": "fls_ukarc98ceesz", + "link": "generics.html#fls_ukarc98ceesz", + "number": "12.3:11" }, { - "checksum": "985fd96881b7c12b952e2f2a81cdc5713cd19ec157141a68232ac19172c96907", - "id": "fls_xrCRprWS13R1", - "link": "ffi.html#fls_xrCRprWS13R1", - "number": "21.1:23" + "checksum": "a874a0d306f839d2ffcec82a884d903ffe0ebbadaca947ebaaca90dbad22f79d", + "id": "fls_l88o2snx9qbt", + "link": "generics.html#fls_l88o2snx9qbt", + "number": "12.3:12" }, { - "checksum": "2eb97e47f68715623197b729da4072c1fd6dd94cc8a38e41f135832c8ac7312e", - "id": "fls_M4LqHf8hbPA8", - "link": "ffi.html#fls_M4LqHf8hbPA8", - "number": "21.1:24" + "checksum": "4b7b540224edea54abc7c76442e8f3bcd837894f2694c4da61a31aa02132aff1", + "id": "fls_thpj9io9tyuy", + "link": "generics.html#fls_thpj9io9tyuy", + "number": "12.3:13" } ], - "title": "ABI" + "title": "Generic Arguments" }, { - "id": "fls_tmoh3y9oyqsy", + "id": "fls_i7g2n7hfg3ch", "informational": false, - "link": "ffi.html#external-blocks", - "number": "21.2", + "link": "generics.html#generic-conformance", + "number": "12.4", "paragraphs": [ { - "checksum": "5690487aad48826d0fbca987e21993dde238a0492461e8b3e35e3f49b1810f50", - "id": "fls_4dje9t5y2dia", - "link": "ffi.html#fls_4dje9t5y2dia", - "number": "21.2:1" - }, - { - "checksum": "e9d30e0c8d7251366fcba4b5a93322f61b5d415d8ea85b870b7a8de0eae5728d", - "id": "fls_8ltVLtAfvy0m", - "link": "ffi.html#fls_8ltVLtAfvy0m", - "number": "21.2:2" - }, - { - "checksum": "8fa9f3206c1f1e9af22942ab5569d11a808e99fcb06365a0c2f5be68d1af8497", - "id": "fls_Nz0l16hMxqTd", - "link": "ffi.html#fls_Nz0l16hMxqTd", - "number": "21.2:3" + "checksum": "d1612a32235e7d6f04d58b8aa1fd91f9a6a088559668f8ef79b51f4827cf4564", + "id": "fls_CBWyxBJeYeb2", + "link": "generics.html#fls_CBWyxBJeYeb2", + "number": "12.4:1" }, { - "checksum": "be19b76230f53b9d539f838eb6c73119e8210045835c5c82f58a9bd1876de4b7", - "id": "fls_4XOoiFloXM7t", - "link": "ffi.html#fls_4XOoiFloXM7t", - "number": "21.2:4" + "checksum": "debb7f2966e0cb74a4f4216168dd052c70fd3d6a8c27fb2f3aa0c7fe47f76a98", + "id": "fls_ltch5eivxgaa", + "link": "generics.html#fls_ltch5eivxgaa", + "number": "12.4:2" }, { - "checksum": "dd7c836fa590b14a7da0c87f48b6eb0d11c83010e9f4ac1e0615ebecc9ba7eb3", - "id": "fls_PBsepNHImJKH", - "link": "ffi.html#fls_PBsepNHImJKH", - "number": "21.2:5" - } - ], - "title": "External Blocks" - }, - { - "id": "fls_yztwtek0y34v", - "informational": false, - "link": "ffi.html#external-functions", - "number": "21.3", - "paragraphs": [ - { - "checksum": "0104ba914d1d2a6dafc2b7914cc94254e1ab3678864a25c1864a89740a54294f", - "id": "fls_v24ino4hix3m", - "link": "ffi.html#fls_v24ino4hix3m", - "number": "21.3:1" + "checksum": "639ff9a5038d7adda9b3bba839e6ae21c85e388acb2f212f48ea6695f318c6f2", + "id": "fls_gb3mpt5rxjoa", + "link": "generics.html#fls_gb3mpt5rxjoa", + "number": "12.4:3" }, { - "checksum": "f42fee85a3cead90e0424d6ae591a72b9d777fa19fdaee7718a36a484f0a8166", - "id": "fls_l88r9fj82650", - "link": "ffi.html#fls_l88r9fj82650", - "number": "21.3:2" + "checksum": "af4df9b3ddac7de4a5237a0d32e24c6c2da88af6502df4979d83552fec77309d", + "id": "fls_kdeltu9dsd0d", + "link": "generics.html#fls_kdeltu9dsd0d", + "number": "12.4:4" }, { - "checksum": "9abb006e22c228a40953b442c54e63fd9a10cc675390d5e7d61b1fb7221ec353", - "id": "fls_qwchgvvnp0qe", - "link": "ffi.html#fls_qwchgvvnp0qe", - "number": "21.3:3" + "checksum": "d89280e4cf06cc3484d31ca3e0ae0d2e71ffb5073be76a1508c6a15273bb4b3d", + "id": "fls_ws1h57fk1mkh", + "link": "generics.html#fls_ws1h57fk1mkh", + "number": "12.4:5" }, { - "checksum": "af35e63fe34e63713827f5525caee153a033f726c0cf64300b5612781daf29fc", - "id": "fls_w00qi1gx204e", - "link": "ffi.html#fls_w00qi1gx204e", - "number": "21.3:4" + "checksum": "91bce3d2e51c484c884932370ea99b161b0bf86288418a7b19cdaf1ab3ce5812", + "id": "fls_w0ozotuwtr9", + "link": "generics.html#fls_w0ozotuwtr9", + "number": "12.4:6" }, { - "checksum": "9ff777cfdc4b91eb8472493cfbc9e96b5e66329d621fb37d3c8603b420f63f79", - "id": "fls_m7tu4w4lk8v", - "link": "ffi.html#fls_m7tu4w4lk8v", - "number": "21.3:5" + "checksum": "5e02e16f6c0da4bb4819ffb8a5e26e721a99432cf64b2e762ed419f7ad104c14", + "id": "fls_91bylteu35bi", + "link": "generics.html#fls_91bylteu35bi", + "number": "12.4:7" }, { - "checksum": "69809b6272d266f36a20ad706492a4e3561e0afa136d36145d8804f83456d978", - "id": "fls_rdu4723vp0oo", - "link": "ffi.html#fls_rdu4723vp0oo", - "number": "21.3:6" + "checksum": "a93335cddb2ff0f239fd61961f0f4b84cf8c28eda2aab38546504a0036037239", + "id": "fls_j6xtrxc6aik", + "link": "generics.html#fls_j6xtrxc6aik", + "number": "12.4:8" }, { - "checksum": "8a5942ade1d1568f8a2e335a8767ab295816829fc64e2c4ef250f53feee9ec69", - "id": "fls_9div9yusw64h", - "link": "ffi.html#fls_9div9yusw64h", - "number": "21.3:7" + "checksum": "c2a32969042fbf3a990f2fbb15ccc7cec1c1fbbec38d11829756846543bea7b7", + "id": "fls_us7d30cbwgpz", + "link": "generics.html#fls_us7d30cbwgpz", + "number": "12.4:9" }, { - "checksum": "0e21072dc94fa05d0ff274b95c7cdfe5e96a5fddabfd26720dd20bcd7aecbf38", - "id": "fls_juob30rst11r", - "link": "ffi.html#fls_juob30rst11r", - "number": "21.3:8" - } - ], - "title": "External Functions" - }, - { - "id": "fls_s4yt19sptl7d", - "informational": false, - "link": "ffi.html#external-statics", - "number": "21.4", - "paragraphs": [ + "checksum": "c87ac00ad30f5b3d158b3eefc327e17f8c53e5f2a7004df7dc4efb8f5c1a2210", + "id": "fls_dp3hpvf0fmr8", + "link": "generics.html#fls_dp3hpvf0fmr8", + "number": "12.4:10" + }, { - "checksum": "a342af289e2343c6a6c563bb75a44dd7f003efc17cc5dddaf9db25fc1395f17c", - "id": "fls_8ddsytjr4il6", - "link": "ffi.html#fls_8ddsytjr4il6", - "number": "21.4:1" + "checksum": "7a2547656618d6f84add7a3e50e946935bb92bd29224f1ee7a67bf7953e55b77", + "id": "fls_mg45zcguxxg5", + "link": "generics.html#fls_mg45zcguxxg5", + "number": "12.4:11" }, { - "checksum": "7456ec21405a4f2629a47ca17fd031b08d4bf3cde66139fe81c3b11248264ca1", - "id": "fls_H0cg9XMaGz0y", - "link": "ffi.html#fls_H0cg9XMaGz0y", - "number": "21.4:2" + "checksum": "9b6af5832d05ba38ea6da1de3aaaf043a9e42d0142f7294124fcbf2f0d95c818", + "id": "fls_mDgq5XjzKAl3", + "link": "generics.html#fls_mDgq5XjzKAl3", + "number": "12.4:12" }, { - "checksum": "6bfbc90ac6b0ebe2bf403b473eb2876a892442339359bafeb4fa203aa258570c", - "id": "fls_fo9with6xumo", - "link": "ffi.html#fls_fo9with6xumo", - "number": "21.4:3" + "checksum": "55339f55c4a3fa7c05dfb3379cd23dd3de7da6d00309d6c5af63eb38de6204bf", + "id": "fls_YufUgB25ovh3", + "link": "generics.html#fls_YufUgB25ovh3", + "number": "12.4:13" }, { - "checksum": "1e3dcb44d797a493ed568db4d4a7c292794fa22ef50cc810f94492b86ec20c75", - "id": "fls_tr7purzcldn0", - "link": "ffi.html#fls_tr7purzcldn0", - "number": "21.4:4" + "checksum": "07a280c09abe97494fb7a02a246eb3eecf3c280c763026b64c83005f8cace2fa", + "id": "fls_OhVxhJ23x7W2", + "link": "generics.html#fls_OhVxhJ23x7W2", + "number": "12.4:14" }, { - "checksum": "0fb23c674fc5c6699fc7b3869fdc36f00d967880169275042498442dc58a8872", - "id": "fls_en2h09ehj0j3", - "link": "ffi.html#fls_en2h09ehj0j3", - "number": "21.4:5" + "checksum": "139fbdc2aef75a36c1a73ea55319255892f7fac16e00d0f5da5ab0e832366b16", + "id": "fls_Kyar0jH9BqeW", + "link": "generics.html#fls_Kyar0jH9BqeW", + "number": "12.4:15" } ], - "title": "External Statics" + "title": "Generic Conformance" } ], - "title": "FFI" + "title": "Generics" }, { "informational": false, @@ -26132,647 +26117,668 @@ "title": "Expressions" }, { - "informational": false, - "link": "implementations.html", + "informational": true, + "link": "general.html", "sections": [ { - "id": "fls_fk2m2irwpeof", + "id": "fls_48qldfwwh493", "informational": false, - "link": "implementations.html", - "number": "11", + "link": "general.html", + "number": "1", "paragraphs": [ { - "checksum": "68d68054bfe8ffe57002321f94f09f2c21faac92f10052af0b8ebb6bb2e4bdd4", - "id": "fls_ivxpoxggy7s6", - "link": "implementations.html#fls_ivxpoxggy7s6", - "number": "11:1" - }, - { - "checksum": "a88885418df7946e8facb892e33e099cc8789c354fbf7bd977f460e40ec2089d", - "id": "fls_yopmjbnw8tbl", - "link": "implementations.html#fls_yopmjbnw8tbl", - "number": "11:2" - }, - { - "checksum": "28523ce67d9161886a6bbdc6415a2a18a12cac0bd7cf96d2e560d4acaa243c96", - "id": "fls_eIHc8Y9fBtr0", - "link": "implementations.html#fls_eIHc8Y9fBtr0", - "number": "11:3" - }, - { - "checksum": "073aeb6b836eee108b5b6d5a5ded4f1b8df8ab96ec075686dca1d2b7fc6c1b34", - "id": "fls_Mcpdzzcw43M7", - "link": "implementations.html#fls_Mcpdzzcw43M7", - "number": "11:4" - }, - { - "checksum": "6d7038fd93f535f1a95c93f6917f3cebd5b30ad181afde8d89173856ec36d502", - "id": "fls_v0n0bna40dqr", - "link": "implementations.html#fls_v0n0bna40dqr", - "number": "11:5" - }, - { - "checksum": "e18c1caa38c5924e033a0026fc017b1c792df55f87c598c5783efe25cb62819f", - "id": "fls_797etpdk5dyb", - "link": "implementations.html#fls_797etpdk5dyb", - "number": "11:6" - }, - { - "checksum": "74cac3914710c2f1a1f624a3c5a1c9aaf33da46b9946695f9bee4eaeb22c5e42", - "id": "fls_ry3an0mwb63g", - "link": "implementations.html#fls_ry3an0mwb63g", - "number": "11:7" + "checksum": "b56c8be8103d8226bbdec210a97ca8f2c38405d36f65f17b15936a1a8284b26c", + "id": "fls_c4ry0kgmvk9z", + "link": "general.html#fls_c4ry0kgmvk9z", + "number": "1:1" }, { - "checksum": "dd34aa75e7947124723372145385a391a6527acf3d57821cf6bfb3530926d0df", - "id": "fls_8pwr7ibvhmhu", - "link": "implementations.html#fls_8pwr7ibvhmhu", - "number": "11:8" + "checksum": "4a8fd448c0e40fd0675f5d82c36b6f963f99259021c6022cc9bd5104c20fbdeb", + "id": "fls_gxqbj0qoiaio", + "link": "general.html#fls_gxqbj0qoiaio", + "number": "1:2" }, { - "checksum": "ba2818ea733c9b43c0160165759dc06495876220780d457240575201aebbf397", - "id": "fls_47x0ep8of8wr", - "link": "implementations.html#fls_47x0ep8of8wr", - "number": "11:9" + "checksum": "02be35a62aff5e48d56d3fb05c75ade724aff55d6ad8278715bd7fac8adbe03f", + "id": "fls_u8k9zr8da30", + "link": "general.html#fls_u8k9zr8da30", + "number": "1:3" }, { - "checksum": "8223ac177e0dcc171dbac1fb21b191141842026a92038b9694be40797d2bdad8", - "id": "fls_agitlryvyc16", - "link": "implementations.html#fls_agitlryvyc16", - "number": "11:10" + "checksum": "11a39a0b407d0ca45db7639f588e85fe27910624c6c508283f983229a6aad5db", + "id": "fls_8mt9iigoboba", + "link": "general.html#fls_8mt9iigoboba", + "number": "1:4" }, { - "checksum": "0ecbb270fa78be720b325ddc3197182ea1ca39be3069bd7d37f201389c9b3991", - "id": "fls_mx5xjcejwa6u", - "link": "implementations.html#fls_mx5xjcejwa6u", - "number": "11:11" + "checksum": "cd1b728c4e56af1dd90836a76afce9239d07ea2aa3095cbeedf8693a22f14caf", + "id": "fls_suhf2g3fatfa", + "link": "general.html#fls_suhf2g3fatfa", + "number": "1:5" }, { - "checksum": "7ec52a2c02f0e55bea6e0a3c728c22e5720e34bdeabc0740d52707774946acbe", - "id": "fls_z78dg261oob6", - "link": "implementations.html#fls_z78dg261oob6", - "number": "11:12" + "checksum": "8b4dce11b14d66a6a1177ef131384957fe6a887c388d56a1902e09ee60f80af0", + "id": "fls_jjr5kbn0wuco", + "link": "general.html#fls_jjr5kbn0wuco", + "number": "1:6" }, { - "checksum": "1f973a701dba719c9b5c7879e8f7e39c271e60ebd4708b781a5ec557ca35bb6b", - "id": "fls_89yNjGNB7KI3", - "link": "implementations.html#fls_89yNjGNB7KI3", - "number": "11:13" + "checksum": "3a278c9aaaabec045a6eede8eb5a0beab745d8b84b098a39b6cdf2e167bc5178", + "id": "fls_4dfcjyprkzbx", + "link": "general.html#fls_4dfcjyprkzbx", + "number": "1:7" }, { - "checksum": "94597e560f592f649b38ef27778a62819bc7e5c2e01faa0ef53fa29225d8083c", - "id": "fls_yuyesijndu9n", - "link": "implementations.html#fls_yuyesijndu9n", - "number": "11:14" - }, + "checksum": "1e02dbccc64818a6cebcc7d6331d8281a24621e813178d0f5be9d0b3356c7a22", + "id": "fls_tq9jcv5ddvwn", + "link": "general.html#fls_tq9jcv5ddvwn", + "number": "1:8" + } + ], + "title": "General" + }, + { + "id": "fls_fo1c7pg2mw1", + "informational": false, + "link": "general.html#scope", + "number": "1.1", + "paragraphs": [ { - "checksum": "7093aa443b2d63b8c3ad1d99dc26a6f1df03d367428b954e62e372eab3d4f569", - "id": "fls_o62i75sjzp9y", - "link": "implementations.html#fls_o62i75sjzp9y", - "number": "11:15" + "checksum": "2ae4f7088e5e2b9b1e598a90db6d4b81803bb3f3c23163762643ad5bff9045ec", + "id": "fls_srdq4mota5pr", + "link": "general.html#fls_srdq4mota5pr", + "number": "1.1:1" }, { - "checksum": "8ad70788c23781cfbb4cc77f8e9b280870f2088b7f3522cf5adf7a4030b29b48", - "id": "fls_a2utf0tmuhy4", - "link": "implementations.html#fls_a2utf0tmuhy4", - "number": "11:16" + "checksum": "b3cdec704c28577193c03cd2eb90cec360e5651ed07b259d112673a67dc0200d", + "id": "fls_dv1qish8svc", + "link": "general.html#fls_dv1qish8svc", + "number": "1.1:2" } ], - "title": "Implementations" + "title": "Scope" }, { - "id": "fls_46ork6fz5o2e", + "id": "fls_10yukmkhl0ng", "informational": false, - "link": "implementations.html#implementation-coherence", - "number": "11.1", + "link": "general.html#extent", + "number": "1.1.1", "paragraphs": [ { - "checksum": "f593f63f6406327889e137ab55ec05d98a6d439176ec14afafae261241a14f1c", - "id": "fls_fv1l4yjuut7p", - "link": "implementations.html#fls_fv1l4yjuut7p", - "number": "11.1:1" - }, - { - "checksum": "1452e1e4d864f7a4178e6f1be77e9694766805cd6a0556f67f5314caf0a5d8de", - "id": "fls_swdusjwzgksx", - "link": "implementations.html#fls_swdusjwzgksx", - "number": "11.1:2" - }, - { - "checksum": "9763aff40d5e4ca431a133884a391a71d1e3434b2dd8e6ab1c928ba846346526", - "id": "fls_ir7hp941ky8t", - "link": "implementations.html#fls_ir7hp941ky8t", - "number": "11.1:3" - }, - { - "checksum": "3a7e9191eb41e4bba88408faba2f7f149137b7df440996588e945fad99c82867", - "id": "fls_3tbm20k2ixol", - "link": "implementations.html#fls_3tbm20k2ixol", - "number": "11.1:4" + "checksum": "07358ef439d78086f970b0606b6da177cb6cc2e64318f27af92832c21a67be43", + "id": "fls_x78yd1sszydv", + "link": "general.html#fls_x78yd1sszydv", + "number": "1.1.1:1" }, { - "checksum": "46b1fe27e9a10a5a463205624c88ba78b4851fbf47c3d1e31d45026df49b1774", - "id": "fls_lscc9ileg3gm", - "link": "implementations.html#fls_lscc9ileg3gm", - "number": "11.1:5" + "checksum": "818ed40aac2e52250ea0014e5de7fbcbcf1e8f325b74a015ac0085699523a43d", + "id": "fls_9e032738udnb", + "link": "general.html#fls_9e032738udnb", + "number": "1.1.1:2" }, { - "checksum": "90514614f2da8202c20d849b7c71d72ae95423a582d7fcb612d03343c5ce1b8a", - "id": "fls_9klwbsh3vlxu", - "link": "implementations.html#fls_9klwbsh3vlxu", - "number": "11.1:6" + "checksum": "85861dc839d41712fe2a39712eb1dcfd1dc88ba30f3782f92bd1986fe355c196", + "id": "fls_jk7scu5xs17z", + "link": "general.html#fls_jk7scu5xs17z", + "number": "1.1.1:3" }, { - "checksum": "79f486e433c0cc3589e42f1efbdc6df12866131778c2b136b5be7d27ff1b67ec", - "id": "fls_9gmc1tcscq9v", - "link": "implementations.html#fls_9gmc1tcscq9v", - "number": "11.1:7" + "checksum": "dc11c0150f1c1ea89b5e2e31e3d90c45c62fa5d26005eb9ed3a16e3f7a3fb68c", + "id": "fls_jiryupa5fxgf", + "link": "general.html#fls_jiryupa5fxgf", + "number": "1.1.1:4" }, { - "checksum": "6b6dac78cb7d28fe378157a5827ca98ef8996833871c9acbbad28e04b8781132", - "id": "fls_UkQhjEWSJpDq", - "link": "implementations.html#fls_UkQhjEWSJpDq", - "number": "11.1:8" + "checksum": "4ce6c16caad38e7ccb47adc109089c53cc047073ebba0a06457cbeded05f68e0", + "id": "fls_sph1a3sapinh", + "link": "general.html#fls_sph1a3sapinh", + "number": "1.1.1:5" }, { - "checksum": "cbf8117eb6e59dd31ff3217f236513679c4236d8d108c4451693dfdeb766d3d2", - "id": "fls_fSybUG40hA5r", - "link": "implementations.html#fls_fSybUG40hA5r", - "number": "11.1:9" + "checksum": "7c348ba84b24afee5a0a39c44f15af8c05a07208877750c9a766d44ab767da7b", + "id": "fls_7tm19jxtffc8", + "link": "general.html#fls_7tm19jxtffc8", + "number": "1.1.1:6" }, { - "checksum": "35936abe2e47a3f84e6b97c5b8773f0e57035b2a2311e2ec92b001e91f057096", - "id": "fls_z8APl0CEF7a0", - "link": "implementations.html#fls_z8APl0CEF7a0", - "number": "11.1:10" + "checksum": "585a289861cd9b12366dc2314022378903323fea3cdbd6863a2e5bd96ed69446", + "id": "fls_5pbrl8lhuth1", + "link": "general.html#fls_5pbrl8lhuth1", + "number": "1.1.1:7" }, { - "checksum": "16c8bf3ad79c585e2a19ab5043608ba5cdfbe59b334ba09913fe7cb101ef98a0", - "id": "fls_RJJafhpVsi6M", - "link": "implementations.html#fls_RJJafhpVsi6M", - "number": "11.1:11" + "checksum": "fc97ac93c58134f54e07f9f6ff5334dbf3afdc8b1e707525ddd8eb232a915c74", + "id": "fls_o8fc3e53vp7g", + "link": "general.html#fls_o8fc3e53vp7g", + "number": "1.1.1:8" }, { - "checksum": "bd35a79daaebf148b48f637a939718b4e78fe6d95ec05e092dabf33c372e5045", - "id": "fls_dtUJxhNkl8Ty", - "link": "implementations.html#fls_dtUJxhNkl8Ty", - "number": "11.1:12" + "checksum": "8adc0c63aa968e623b7251ecc0776057325470f81d39879bb6dfb23d1985a407", + "id": "fls_rw0y5t13y6gs", + "link": "general.html#fls_rw0y5t13y6gs", + "number": "1.1.1:9" }, { - "checksum": "8e6a64bbe4e6692fa29ab6cab825bed32aa92944541ab8b3cab95f46ec47ae1a", - "id": "fls_zJKovQrXQWdU", - "link": "implementations.html#fls_zJKovQrXQWdU", - "number": "11.1:13" + "checksum": "2f2276e84ea3e0251d343a2c859bafeefc4eb09d8d7222774ce538650c01980b", + "id": "fls_x7c3o621qj9z", + "link": "general.html#fls_x7c3o621qj9z", + "number": "1.1.1:10" }, { - "checksum": "881eb7081d32bca6dd31a402732083c1a10cfbb0703d65d4903481c9df0fa6cc", - "id": "fls_V6R8yQtsqNyv", - "link": "implementations.html#fls_V6R8yQtsqNyv", - "number": "11.1:14" + "checksum": "cd7a1f68c81b43b8536b355596a13da9ddc2e33d7b37a18af00eee2456c3fd06", + "id": "fls_5y2b6yjcl1vz", + "link": "general.html#fls_5y2b6yjcl1vz", + "number": "1.1.1:11" }, { - "checksum": "0bff693cd5c7bd24006d97966faf22d8ad51776dd27b155aa3c4c53705f1e230", - "id": "fls_CpC6XQN1iWqU", - "link": "implementations.html#fls_CpC6XQN1iWqU", - "number": "11.1:15" + "checksum": "a695d7fe1b36b6516b8530ec034376b8c43e7aa53cd0795f48e39482be5a38cf", + "id": "fls_8dennhk2dha", + "link": "general.html#fls_8dennhk2dha", + "number": "1.1.1:12" }, { - "checksum": "3a1f51af690831f150b9917ff7ed785fc0ad8572bfdb024cbbe419e2b1536063", - "id": "fls_dj7YGw4e4i4H", - "link": "implementations.html#fls_dj7YGw4e4i4H", - "number": "11.1:16" + "checksum": "65c2d0a375d2b29b8836f90cfdf174fc8fe72f5198073097bb9401add5d0880b", + "id": "fls_j2gs3hrbxtyx", + "link": "general.html#fls_j2gs3hrbxtyx", + "number": "1.1.1:13" }, { - "checksum": "a69a7eba188277ac25d75030529f9289094778fd2d87d38a1ed7ab9c1871938e", - "id": "fls_koy70k770ayu", - "link": "implementations.html#fls_koy70k770ayu", - "number": "11.1:17" + "checksum": "94c8f2e37b34e95fffa62df717bbcd7d87653a8506a65f52d91e1706e0dfe9d8", + "id": "fls_gy2c7vfwkd8j", + "link": "general.html#fls_gy2c7vfwkd8j", + "number": "1.1.1:14" } ], - "title": "Implementation Coherence" + "title": "Extent" }, { - "id": "fls_e1pgdlv81vul", + "id": "fls_xscgklvg1wd2", "informational": false, - "link": "implementations.html#implementation-conformance", - "number": "11.2", + "link": "general.html#structure", + "number": "1.1.2", "paragraphs": [ { - "checksum": "235dcfef5a150c402f42c33dd6eabd7189375ddb2bb8e3cd3cc162fe22a53e9a", - "id": "fls_YyUSuAYG4lX6", - "link": "implementations.html#fls_YyUSuAYG4lX6", - "number": "11.2:1" + "checksum": "9d1641a50a597e4ce0553ceca94bbf6c254d57933639a13b9b13f852c10f4640", + "id": "fls_6lrqailxjb02", + "link": "general.html#fls_6lrqailxjb02", + "number": "1.1.2:1" }, { - "checksum": "5f1a4f88e1dd9fd77d93c773080c7b2e8fa3179f7d8e4de72e39d0ff16a2a387", - "id": "fls_v31idwjau90d", - "link": "implementations.html#fls_v31idwjau90d", - "number": "11.2:2" + "checksum": "13aa072e39fbda618587953663ec86d1a188fe0eb4db97252b906ca8e9c2fe51", + "id": "fls_tys7ciqnp8bn", + "link": "general.html#fls_tys7ciqnp8bn", + "number": "1.1.2:2" }, { - "checksum": "f18161b732608a6732ea2886b4c609dc56b7dde6df16c57cf321542c887aa1d8", - "id": "fls_k3wfh5japmyw", - "link": "implementations.html#fls_k3wfh5japmyw", - "number": "11.2:3" + "checksum": "934ba26ee073ed0a5cbaf9b98cf9005d3786c907b9de25f60f31c18a1d5ddb1c", + "id": "fls_3ubhkaheu8i1", + "link": "general.html#fls_3ubhkaheu8i1", + "number": "1.1.2:3" }, { - "checksum": "428b016d82301c34cac20a058d8e9f87628abcd977fc4d1ff1557bc1bf2d24f8", - "id": "fls_11qrqfuc3rmh", - "link": "implementations.html#fls_11qrqfuc3rmh", - "number": "11.2:4" + "checksum": "b9623aca56a3fe17319f63a0b550b6405ad21f99012d17ae6f6ce41d1f1605d4", + "id": "fls_xw3grr2g5zgi", + "link": "general.html#fls_xw3grr2g5zgi", + "number": "1.1.2:4" }, { - "checksum": "7e921c53c7799271e80ccbadf2d47a340d1ef1bd9326fe4e2bdf54e5dcf2d127", - "id": "fls_qmhduwunxww0", - "link": "implementations.html#fls_qmhduwunxww0", - "number": "11.2:5" + "checksum": "11c90805ef23752a416f5a6a8b69230febfdf97b68edaf26f6517bd359492443", + "id": "fls_6srbinvnyd54", + "link": "general.html#fls_6srbinvnyd54", + "number": "1.1.2:5" }, { - "checksum": "aa4594177b713ab6ee2d502e2c0f20256ac5a5e546fab64bcc52db72ee407c5d", - "id": "fls_2500ivh0cc3y", - "link": "implementations.html#fls_2500ivh0cc3y", - "number": "11.2:6" + "checksum": "fdcfcebd0c9395a151b773b35ef5d5e2720a08080c54f279652f7668a0182897", + "id": "fls_ciixfg9jhv42", + "link": "general.html#fls_ciixfg9jhv42", + "number": "1.1.2:6" }, { - "checksum": "f3fb2ce2caeb76b89e224e1c88594d2dc95ddc66e180f8cb6d50b428f01eedcf", - "id": "fls_18gimgfy0kw9", - "link": "implementations.html#fls_18gimgfy0kw9", - "number": "11.2:7" + "checksum": "25a56f970c722a9217989c3250b9b75798453ac229d32dc74b97c2a6c84a2109", + "id": "fls_ej94lm2682kg", + "link": "general.html#fls_ej94lm2682kg", + "number": "1.1.2:7" }, { - "checksum": "0b6f276c677931c78d760ff5def7e67a7e4cbd817e1f21f0343edb674c80e0b4", - "id": "fls_fi4qmauirlsm", - "link": "implementations.html#fls_fi4qmauirlsm", - "number": "11.2:8" + "checksum": "1531d2cfb29ab712e3adf7fc798c2342b8977bae5175dda990efd578592d2454", + "id": "fls_xgk91jrbpyoc", + "link": "general.html#fls_xgk91jrbpyoc", + "number": "1.1.2:8" }, { - "checksum": "31bad81c5be19c117a8675a891a00247edb13fe1ab49a36e1f439da9b3123b6f", - "id": "fls_2s8lh3k4rw6u", - "link": "implementations.html#fls_2s8lh3k4rw6u", - "number": "11.2:9" + "checksum": "db3c5f8206004bb2cca82ca219a417ec2a4fe259773ae667ab3d10114d5740ee", + "id": "fls_jc4upf6685bw", + "link": "general.html#fls_jc4upf6685bw", + "number": "1.1.2:9" }, { - "checksum": "fe5486ca02f9ffadaed847c11ea13af8afba0eb18ea38527ec5b8cc5d45cbf83", - "id": "fls_bb874uu2alt3", - "link": "implementations.html#fls_bb874uu2alt3", - "number": "11.2:10" + "checksum": "1594b619675d7802bccfee40ca10262be9916e29f70d8f7fecbf5dddd00246f4", + "id": "fls_oxzjqxgejx9t", + "link": "general.html#fls_oxzjqxgejx9t", + "number": "1.1.2:10" }, { - "checksum": "43150bf6a5f61f75ca365a77174d7d659f67e1c8cd00dcd74b3f72cdc7282045", - "id": "fls_so8em6rphkhv", - "link": "implementations.html#fls_so8em6rphkhv", - "number": "11.2:11" + "checksum": "acf02a4a250adb8e29284b356a6f4a386a936d32f0e85d3b304e708863f10929", + "id": "fls_gmx688d6ek1o", + "link": "general.html#fls_gmx688d6ek1o", + "number": "1.1.2:11" }, { - "checksum": "2928f339f8abfbe9b10de44c5ab57f730fdc2e7881dc3cce685844a2ada75373", - "id": "fls_ldu9bmb9cy10", - "link": "implementations.html#fls_ldu9bmb9cy10", - "number": "11.2:12" + "checksum": "7f053104dd4947498f1bb5121b9fe28a23d78ead65d37dd580df0111d558d8dd", + "id": "fls_5zdjikp1jhc", + "link": "general.html#fls_5zdjikp1jhc", + "number": "1.1.2:12" }, { - "checksum": "1a07917d9eeb24596f2800e26f4930f6ecc94fc0027f33f7cce840c026ee28a5", - "id": "fls_5cr6un2gzdft", - "link": "implementations.html#fls_5cr6un2gzdft", - "number": "11.2:13" + "checksum": "44025e9b54bdddecd35c847f5628ffddbfc9e4721cd733f41c9186c904ef15b1", + "id": "fls_as5bhc5t285g", + "link": "general.html#fls_as5bhc5t285g", + "number": "1.1.2:13" + }, + { + "checksum": "6efef41c7a703e3b3562b12e95047110a954b19026060d22099de222c142ed8b", + "id": "fls_70qjvaqoz007", + "link": "general.html#fls_70qjvaqoz007", + "number": "1.1.2:14" }, { - "checksum": "dc51b8a6f90c2e32e2ec099057c7d4745f9082e4521e0f0cb64ba03ac87c95ed", - "id": "fls_pshfe3ioh0mg", - "link": "implementations.html#fls_pshfe3ioh0mg", - "number": "11.2:14" + "checksum": "525edc88be77e71d6777eef00907902bcd024abb3c401a92d67f1ca997971c5a", + "id": "fls_o4rdsbc7u98", + "link": "general.html#fls_o4rdsbc7u98", + "number": "1.1.2:15" }, { - "checksum": "bc54fae0d59dee07617ca65931de1869df56a18c8afe575dcc287c8d2d9305ed", - "id": "fls_8yq1g7nzv9px", - "link": "implementations.html#fls_8yq1g7nzv9px", - "number": "11.2:15" + "checksum": "3ab99fa52627e6796ca7e01a7242052885523e5e0f93b9dd98ec22d483688e60", + "id": "fls_w8j575w2hmc8", + "link": "general.html#fls_w8j575w2hmc8", + "number": "1.1.2:16" } ], - "title": "Implementation Conformance" - } - ], - "title": "Implementations" - }, - { - "informational": false, - "link": "functions.html", - "sections": [ + "title": "Structure" + }, { - "id": "fls_qcb1n9c0e5hz", + "id": "fls_99b7xi1bkgih", "informational": false, - "link": "functions.html", - "number": "9", + "link": "general.html#conformity", + "number": "1.1.3", "paragraphs": [ { - "checksum": "d85148abd62f72e649abbb14ec33bcf5dd833660e6d429978a1233e8aa19e739", - "id": "fls_gn1ngtx2tp2s", - "link": "functions.html#fls_gn1ngtx2tp2s", - "number": "9:1" - }, - { - "checksum": "0b7f0bc4680d19a82f2a03e22ab8228330095ac38f768e6138b1fa061209b0cd", - "id": "fls_bdx9gnnjxru3", - "link": "functions.html#fls_bdx9gnnjxru3", - "number": "9:2" - }, - { - "checksum": "4ef42a17784ccf912f1917cdc894f87abaa5c3ae6bbc330e5d97884670ee69a3", - "id": "fls_87jnkimc15gi", - "link": "functions.html#fls_87jnkimc15gi", - "number": "9:3" + "checksum": "42aa78f31bd39d9d54d95d846fc09f42910052efc6f2cb7eb50a7b34706f7c97", + "id": "fls_kdyqtnc6loam", + "link": "general.html#fls_kdyqtnc6loam", + "number": "1.1.3:1" }, { - "checksum": "1be13228840af974c47c9d4a16fb932edc3efcbbb1b5a5d8261d4eb3d4db1df5", - "id": "fls_nwywh1vjt6rr", - "link": "functions.html#fls_nwywh1vjt6rr", - "number": "9:4" + "checksum": "1b1e377b795c593c09c9e640deb4d270fad22997bd2c697ee3bb4b3c4109ecca", + "id": "fls_dBKu9jgx3OyH", + "link": "general.html#fls_dBKu9jgx3OyH", + "number": "1.1.3:2" }, { - "checksum": "e7bbdbb6cf00db43de1e27fe5b39e71062d7737351b11ee44c1f02ab45beabfb", - "id": "fls_uwuthzfgslif", - "link": "functions.html#fls_uwuthzfgslif", - "number": "9:5" + "checksum": "8ade643b2872a946f749a6125849c9d9af29cb64ca9c611b05332fae6c4ceacb", + "id": "fls_faRvWyJJpno8", + "link": "general.html#fls_faRvWyJJpno8", + "number": "1.1.3:3" }, { - "checksum": "d6307364051e9afc70d6e0e3e277a7a096ecb2eb1db51e356303d7ee87537a12", - "id": "fls_ymeo93t4mz4", - "link": "functions.html#fls_ymeo93t4mz4", - "number": "9:6" + "checksum": "581e966a0c27c9ea6c0cbfb6d1bf739d24fb579f2495fc002947b3e93977c231", + "id": "fls_GZmxrO61eiJ1", + "link": "general.html#fls_GZmxrO61eiJ1", + "number": "1.1.3:4" }, { - "checksum": "2119a7ea0e7b937c1318f8b7cb342aea3ec7249cab773a35ff4b8a0701d43f4b", - "id": "fls_ijbt4tgnl95n", - "link": "functions.html#fls_ijbt4tgnl95n", - "number": "9:7" + "checksum": "1f8c2956cb9f8b946f0f56053a038a9ab8efaf6dd7881bb5d1d65e1c87b05900", + "id": "fls_nnmx2qsu14ft", + "link": "general.html#fls_nnmx2qsu14ft", + "number": "1.1.3:5" }, { - "checksum": "b7ee5ce366f421f0fae995e21d17b77d938b59f2f8a07975cb3aa3486b963304", - "id": "fls_AAYJDCNMJgTq", - "link": "functions.html#fls_AAYJDCNMJgTq", - "number": "9:8" + "checksum": "adb8b69470c26508c51b735aa65a30d464dae4eecafb85f3cad7b62450557213", + "id": "fls_gu3331rmv2ho", + "link": "general.html#fls_gu3331rmv2ho", + "number": "1.1.3:6" }, { - "checksum": "29b6ace4daea786064b460a8bf0c7ddbe2b6800631188e26b46177257bf8fa33", - "id": "fls_PGtp39f6gJwU", - "link": "functions.html#fls_PGtp39f6gJwU", - "number": "9:9" + "checksum": "030c86e984b66288b589337553d3375d3fdecc0dfab304381123495642596b20", + "id": "fls_AR8ZIYlDRSNs", + "link": "general.html#fls_AR8ZIYlDRSNs", + "number": "1.1.3:7" }, { - "checksum": "2205b3649789767c105c262228a48536d8d9af2734351fcab5e8650eed044f19", - "id": "fls_yZ2yIXxmy2ri", - "link": "functions.html#fls_yZ2yIXxmy2ri", - "number": "9:10" + "checksum": "09785f4e7f3719f15b9790de8e8388bce97f35323591af71ed34b453c9b2ef30", + "id": "fls_xAYhvEh7WWel", + "link": "general.html#fls_xAYhvEh7WWel", + "number": "1.1.3:8" }, { - "checksum": "00ff6766b53e2501ce67b57b21ac801f24b86208afab3a509a0cd92439b2f346", - "id": "fls_35aSvBxBnIzm", - "link": "functions.html#fls_35aSvBxBnIzm", - "number": "9:11" + "checksum": "01a005ca17491af3519bb4e745fbef2a83ce55109d5787034a94659c1dd03f36", + "id": "fls_QvFpU8v5p8Hb", + "link": "general.html#fls_QvFpU8v5p8Hb", + "number": "1.1.3:9" }, { - "checksum": "d44af44daa180c96d7c066dd036be5c2217e9ef54e5440a28226f4155da1fbd9", - "id": "fls_Ogziu8S01qPQ", - "link": "functions.html#fls_Ogziu8S01qPQ", - "number": "9:12" + "checksum": "849ea928df375ac126378ee4fd16534119a4f03f42721cd25145edc4d7850294", + "id": "fls_pl0fyjcwslqm", + "link": "general.html#fls_pl0fyjcwslqm", + "number": "1.1.3:10" }, { - "checksum": "e007c5d5fe350258fe85248f1a507b573c634bb18cf907d6b83babb1a8a98f5d", - "id": "fls_xCSsxYUZUFed", - "link": "functions.html#fls_xCSsxYUZUFed", - "number": "9:13" + "checksum": "534de23af559805d2b96e284fdd9afc9838e9e3f65b120c5401aa7cf130d439b", + "id": "fls_lkdm0mdghppv", + "link": "general.html#fls_lkdm0mdghppv", + "number": "1.1.3:11" }, { - "checksum": "afcad007b988dd4e9a7ae77d05318b5818905bf05921cd7d14070abe83ebb9b2", - "id": "fls_lxzinvqveuqh", - "link": "functions.html#fls_lxzinvqveuqh", - "number": "9:14" - }, + "checksum": "f87533ba7b47cb56f59c27e8d99d031d3c5c35b16c4d3ae271ddb9b3ee18c33b", + "id": "fls_d07x1mbhgpsd", + "link": "general.html#fls_d07x1mbhgpsd", + "number": "1.1.3:12" + } + ], + "title": "Conformity" + }, + { + "id": "fls_79rl6ylmct07", + "informational": false, + "link": "general.html#method-of-description-and-syntax-notation", + "number": "1.1.4", + "paragraphs": [ { - "checksum": "af44866222e3ba28adab818deff3cff0fe079c936325cb39ed958ac977eaf988", - "id": "fls_kcAbTPZXQ5Y8", - "link": "functions.html#fls_kcAbTPZXQ5Y8", - "number": "9:15" + "checksum": "8811c1ef8e9707861f67d171dc813cc290188343fde68d68304072ab9d422c41", + "id": "fls_mc4a28do6kcp", + "link": "general.html#fls_mc4a28do6kcp", + "number": "1.1.4:1" }, { - "checksum": "a6f8a428953b804ae4d9a530c1659087e99139633acc2e99d4315bf792bf123a", - "id": "fls_PGDKWK7nPvgw", - "link": "functions.html#fls_PGDKWK7nPvgw", - "number": "9:16" + "checksum": "e62cf2d485b81b81793c685b6fef64ad6d407da40349a45129bfd298fb8d0258", + "id": "fls_ioyp4wux6skt", + "link": "general.html#fls_ioyp4wux6skt", + "number": "1.1.4:2" }, { - "checksum": "681f0328a6e3f3599494c7ab05e29ca62e56c28c57ecde0bbe7cd5b96d6db84d", - "id": "fls_o4uSLPo00KUg", - "link": "functions.html#fls_o4uSLPo00KUg", - "number": "9:17" + "checksum": "5be24f2bcda1727c3b6e1ad27d2d7c8ffe6e487d292f2b48bd528e411c2ae1be", + "id": "fls_jsflt7691ye4", + "link": "general.html#fls_jsflt7691ye4", + "number": "1.1.4:3" }, { - "checksum": "2a3652b695660398bf0fcad1529e0346fed7c9fa6809502c46ecdb89c2bf93d6", - "id": "fls_icdzs1mjh0n4", - "link": "functions.html#fls_icdzs1mjh0n4", - "number": "9:18" + "checksum": "15f27fa3164d4f34e687dff0589a9406f64fd304a5cbac1d917e215a3420b24d", + "id": "fls_98fm7z04lq9", + "link": "general.html#fls_98fm7z04lq9", + "number": "1.1.4:4" }, { - "checksum": "408643016febf0219e0809cd2e92e018cab38026b8e8612741262fc29b5daa00", - "id": "fls_OR85NVifPwjr", - "link": "functions.html#fls_OR85NVifPwjr", - "number": "9:19" + "checksum": "bf197e4e3db74f8f9189b2d39392b793c47f8e26908e5ff493a91d3567e2e58d", + "id": "fls_ceb5a8t6cakr", + "link": "general.html#fls_ceb5a8t6cakr", + "number": "1.1.4:5" }, { - "checksum": "4bc2a2eb8ccee2c72bf5174dc901116ca5cf7ed223c6d8860b2bfbca11943925", - "id": "fls_4s2IdfYDzPrX", - "link": "functions.html#fls_4s2IdfYDzPrX", - "number": "9:20" + "checksum": "e53521056d56c3f85475811368708e304047e64d39a20604584fa7f38d9c7f86", + "id": "fls_pts29mb5ld68", + "link": "general.html#fls_pts29mb5ld68", + "number": "1.1.4:6" }, { - "checksum": "fca8a97c468b1b73283de8c4f98ad8e13873aba97f8c10a0392dab4826a7fe00", - "id": "fls_ZJJppPfiJRou", - "link": "functions.html#fls_ZJJppPfiJRou", - "number": "9:21" + "checksum": "48013ee45666937c8ad55fb39de161c383ead549216e3bdc9e2882e83bcf0a90", + "id": "fls_gqjo5oh7vn3b", + "link": "general.html#fls_gqjo5oh7vn3b", + "number": "1.1.4:7" }, { - "checksum": "7dd1d3308749e298f5b2a22e71ac500637ce35b2d9cf6ccf24634f53e2885f12", - "id": "fls_jOyZh9ujWWHQ", - "link": "functions.html#fls_jOyZh9ujWWHQ", - "number": "9:22" + "checksum": "f6d36c515bd4436ca87447bc369bb7b1c69b50ca9215b1d6981dabce39f33593", + "id": "fls_1dz634xp8xp5", + "link": "general.html#fls_1dz634xp8xp5", + "number": "1.1.4:8" }, { - "checksum": "99c1950124ddc4f31f88a1064b15e1cb78f9e374abb2eece4a801f0648c89300", - "id": "fls_Xdr0bFwxhWiB", - "link": "functions.html#fls_Xdr0bFwxhWiB", - "number": "9:23" + "checksum": "949f3857c5bbb056b780f44c08df02afc05d59db42be92d94a86c97a20ae4943", + "id": "fls_pp9vtjlyblrl", + "link": "general.html#fls_pp9vtjlyblrl", + "number": "1.1.4:9" }, { - "checksum": "898ff3d8792a073b155387b8ee5e51b48f03b4ad15845d393f6162730578c4a3", - "id": "fls_DpTFEHZAABdD", - "link": "functions.html#fls_DpTFEHZAABdD", - "number": "9:24" + "checksum": "d7b7b2e401f1bc24ffd16c1f417d18c3d12336cc2d2325ecc1c07d77ea0e513e", + "id": "fls_6e2vd9fvhsmk", + "link": "general.html#fls_6e2vd9fvhsmk", + "number": "1.1.4:10" }, { - "checksum": "421ab0ac3cb998e26b832f6f921db20f8d80617dce79045d9906b460e2dbbadb", - "id": "fls_b7FTlWfnX2OI", - "link": "functions.html#fls_b7FTlWfnX2OI", - "number": "9:25" + "checksum": "0eda08ae37976f0d7d15570d790c9bc3107ea4533794e554872ea0d77af06d9c", + "id": "fls_4onq0kkrt6qv", + "link": "general.html#fls_4onq0kkrt6qv", + "number": "1.1.4:11" }, { - "checksum": "eda198b5564690046f7225520acae5222abaccf175ac28f4c3b8b13d0d0bbf14", - "id": "fls_6urL6fZ5cpaA", - "link": "functions.html#fls_6urL6fZ5cpaA", - "number": "9:26" + "checksum": "bf1b757e4fec099109dc75529b8709e0177c379698e8c762c5c27fe8fe0ac2d3", + "id": "fls_qu4rsmnq659w", + "link": "general.html#fls_qu4rsmnq659w", + "number": "1.1.4:12" }, { - "checksum": "513827ea79b2bb852e3a03fc1d52ad23aaa08dccee280c50059a7e31df7d6139", - "id": "fls_TMOzb6cYIOlH", - "link": "functions.html#fls_TMOzb6cYIOlH", - "number": "9:27" + "checksum": "ca3586f81ab3962899f5a1a79386ef8637003025a729a5b76bebdb5d1e43cb28", + "id": "fls_rllu7aksf17e", + "link": "general.html#fls_rllu7aksf17e", + "number": "1.1.4:13" }, { - "checksum": "15571a856ea4d62c8fe363b440ddf0f1a63e654322bf5c97a6fbc7973c16420e", - "id": "fls_eHPWHrvs7ETl", - "link": "functions.html#fls_eHPWHrvs7ETl", - "number": "9:28" + "checksum": "87380f5c66b721c614a404b1852a6c1f93b93d02b04517c8be6e401f85832f82", + "id": "fls_blvsfqeevosr", + "link": "general.html#fls_blvsfqeevosr", + "number": "1.1.4:14" }, { - "checksum": "42a5a6b2aef837c0262688bbc4f2dae48d988fd49bcbcbcd7a84175224069dd8", - "id": "fls_mjCrvmikm58M", - "link": "functions.html#fls_mjCrvmikm58M", - "number": "9:29" + "checksum": "d05cec36bfece44c3203af30f098975bd0f3d0cccd9687f0956cd9eca66c810f", + "id": "fls_lwcjq3wzjyvb", + "link": "general.html#fls_lwcjq3wzjyvb", + "number": "1.1.4:15" }, { - "checksum": "1b8b316d3d56bf10df2d8521160902a8137f08c3a5c8d39d5bf8f22900dac035", - "id": "fls_4EUb9zFatZ97", - "link": "functions.html#fls_4EUb9zFatZ97", - "number": "9:30" + "checksum": "9a579e74f8de12da445c5197e2555aab86fc739d0defc1f685c0aa5dfb44cebc", + "id": "fls_v7wd5yk00im6", + "link": "general.html#fls_v7wd5yk00im6", + "number": "1.1.4:16" }, { - "checksum": "a2e6957d275225e3cb26e75af5b4e0d3d31afa9b43a77eaef4117c78d920ac52", - "id": "fls_4B4B5FIqAes9", - "link": "functions.html#fls_4B4B5FIqAes9", - "number": "9:31" - }, + "checksum": "afb0e775413f09199a584413d501b6ec88d107c1be87cac6e07bc33ecce6ad74", + "id": "fls_nf8alga8uz6c", + "link": "general.html#fls_nf8alga8uz6c", + "number": "1.1.4:17" + } + ], + "title": "Method of Description and Syntax Notation" + }, + { + "id": "fls_9cd746qe40ag", + "informational": false, + "link": "general.html#versioning", + "number": "1.2", + "paragraphs": [ { - "checksum": "b82bf4578023aa2218ec01202b93d193cbb48fd8b53d740a5c9d3bb482caaf10", - "id": "fls_vljy4mm0zca2", - "link": "functions.html#fls_vljy4mm0zca2", - "number": "9:32" + "checksum": "ac30f10574356a3a3472c8858b8b13e8b8ef034312c5823a3674a594b4e4e468", + "id": "fls_l80e3kdwnldc", + "link": "general.html#fls_l80e3kdwnldc", + "number": "1.2:1" + } + ], + "title": "Versioning" + }, + { + "id": "fls_ijzgf4h0mp3c", + "informational": false, + "link": "general.html#definitions", + "number": "1.3", + "paragraphs": [ + { + "checksum": "34f82930e1b6b4af471b30519f81d43725341882ad0a3e8e36d85ab7b3f5713b", + "id": "fls_sm2kexes5pr7", + "link": "general.html#fls_sm2kexes5pr7", + "number": "1.3:1" }, { - "checksum": "b89caf540730b919c47a1f20bdd5dc9302aca3ec99300e25098600be2e706acf", - "id": "fls_EqJb3Jl3vK8K", - "link": "functions.html#fls_EqJb3Jl3vK8K", - "number": "9:33" + "checksum": "397ee14d96917a0a9033a587347a2246e4118f1270d64cce0f7eefeeaf565980", + "id": "fls_2o98zw29xc46", + "link": "general.html#fls_2o98zw29xc46", + "number": "1.3:2" }, { - "checksum": "e2ca22f3a68ea69c89d5499ab8bfaff4ea879242e37d601e8b78939484e3fbd9", - "id": "fls_C7dvzcXcpQCy", - "link": "functions.html#fls_C7dvzcXcpQCy", - "number": "9:34" + "checksum": "969a1f3b691a3118abcd815618a71feba07fdd5be4c2a12ce37d0b49595d013a", + "id": "fls_lon5qffd65fi", + "link": "general.html#fls_lon5qffd65fi", + "number": "1.3:3" }, { - "checksum": "2f2202efc417e1cd3118f5b446cf8c8a375b6e5f8ebaed72a0201aa9d161578a", - "id": "fls_J8X8ahnJLrMo", - "link": "functions.html#fls_J8X8ahnJLrMo", - "number": "9:35" + "checksum": "0d6e2d5075e9ccebe0f79ee2d1d393c07fae6b8d4b6cbf7fbf59b1348185fc5d", + "id": "fls_qeolgxvcy75", + "link": "general.html#fls_qeolgxvcy75", + "number": "1.3:4" }, { - "checksum": "f01d77c3faa9dafff3f5a3ca3928a29d60e2867a2438140aaaa4cbceb25da31b", - "id": "fls_927nfm5mkbsp", - "link": "functions.html#fls_927nfm5mkbsp", - "number": "9:36" + "checksum": "70b86c11c1708aa3d4d6b52af002352aebaef77835c4b45ebc4233a789844230", + "id": "fls_h2m244agxaxs", + "link": "general.html#fls_h2m244agxaxs", + "number": "1.3:5" }, { - "checksum": "a8e601de98599b419876278ab5521c5cc49ae6bfdacfe061350c283e236e211f", - "id": "fls_yfm0jh62oaxr", - "link": "functions.html#fls_yfm0jh62oaxr", - "number": "9:37" + "checksum": "c669dced28f1bb44dbac7e17e855e184b1f14baecfa15e22b21d2379a5998be6", + "id": "fls_47svine904xk", + "link": "general.html#fls_47svine904xk", + "number": "1.3:6" + } + ], + "title": "Definitions" + } + ], + "title": "General" + }, + { + "informational": false, + "link": "exceptions-and-errors.html", + "sections": [ + { + "id": "fls_dzq9cdz4ibsz", + "informational": false, + "link": "exceptions-and-errors.html", + "number": "16", + "paragraphs": [ + { + "checksum": "d0dadbee084de3030e81aaf2325ea8878e99a72e77800a2662c1fe290bbc9d62", + "id": "fls_vsk4vhnuiyyz", + "link": "exceptions-and-errors.html#fls_vsk4vhnuiyyz", + "number": "16:1" }, { - "checksum": "45189d0422353cd31d5e3d1460ac0bf25731cc9f464574e6acf3ba0a8661dffb", - "id": "fls_bHwy8FLzEUi3", - "link": "functions.html#fls_bHwy8FLzEUi3", - "number": "9:38" + "checksum": "f2e56f24b5565d180411f7693f63a3b70001ae9c0e4e605e1bd11ea5c207f968", + "id": "fls_ebangxc36t74", + "link": "exceptions-and-errors.html#fls_ebangxc36t74", + "number": "16:2" }, { - "checksum": "1952dd7798e9fd4d8fb5f525eedc05613f2ef1a660b93bfa8f24167e67f839bb", - "id": "fls_5Q861wb08DU3", - "link": "functions.html#fls_5Q861wb08DU3", - "number": "9:39" + "checksum": "1fd314e20308ba8d525a721f6a9bf050a337f71186510503044dc84efe09173e", + "id": "fls_ckeitwiv326r", + "link": "exceptions-and-errors.html#fls_ckeitwiv326r", + "number": "16:3" }, { - "checksum": "a336a489c323ad22b7aa3059350a2400bb8e41f5eef6ce02b17682c438a9fd09", - "id": "fls_owdlsaaygtho", - "link": "functions.html#fls_owdlsaaygtho", - "number": "9:40" + "checksum": "b346390e8119d57549b96dcb345a29235552cdc69a61d7d2c8c883cf6d34ae61", + "id": "fls_eg0orgibg98m", + "link": "exceptions-and-errors.html#fls_eg0orgibg98m", + "number": "16:4" }, { - "checksum": "4355b34dd22393af15f6fad33ae3c69fc7091736b751b78d2b18cb15ceb7b3cc", - "id": "fls_2049qu3ji5x7", - "link": "functions.html#fls_2049qu3ji5x7", - "number": "9:41" + "checksum": "83c2582339039d863409873a919aa3040ed86f444b3a5f5729e9fe0d65313c5e", + "id": "fls_ko1x0gp9e7y3", + "link": "exceptions-and-errors.html#fls_ko1x0gp9e7y3", + "number": "16:5" }, { - "checksum": "515666a67e5b60858568348fd8b4c6021e49484b778d6ccd6c357f9b792643d1", - "id": "fls_7mlanuh5mvpn", - "link": "functions.html#fls_7mlanuh5mvpn", - "number": "9:42" + "checksum": "475f3e3082b2760a32589c7540d5123f0459abea622ba1ae15aa53068c337d7b", + "id": "fls_gwu4cn4ziabe", + "link": "exceptions-and-errors.html#fls_gwu4cn4ziabe", + "number": "16:6" + } + ], + "title": "Exceptions and Errors" + }, + { + "id": "fls_k02nt1m5fq1z", + "informational": false, + "link": "exceptions-and-errors.html#panic", + "number": "16.1", + "paragraphs": [ + { + "checksum": "f4d500cf3ddd573cf21ec19b03d0e534bb8caa50bdf5d11eecd095eeaae539f2", + "id": "fls_a554v4n0khye", + "link": "exceptions-and-errors.html#fls_a554v4n0khye", + "number": "16.1:1" }, { - "checksum": "4ccbc87469f1565c32bef31f5909da4c2b2f1febaf71e3b0216053540e2732c4", - "id": "fls_otr3hgp8lj1q", - "link": "functions.html#fls_otr3hgp8lj1q", - "number": "9:43" + "checksum": "d0ec8ad699ff1c4778bd35b0577f2de16559c4f7a8a67503e9d8b5ffb3feefe0", + "id": "fls_i9njhpte5l0t", + "link": "exceptions-and-errors.html#fls_i9njhpte5l0t", + "number": "16.1:2" }, { - "checksum": "45a75ca66fc48e3abbe4a9b46d1599f9ecbcfae9fbc5df35e1b5d8939f41f8c4", - "id": "fls_m3jiunibqj81", - "link": "functions.html#fls_m3jiunibqj81", - "number": "9:44" + "checksum": "df2cd62113f2dc6ca452da3c12891fd958bc64f17397066b0a128c09a0fef520", + "id": "fls_n6q7bksyn1m", + "link": "exceptions-and-errors.html#fls_n6q7bksyn1m", + "number": "16.1:3" }, { - "checksum": "388876fffc5a50a274a155691b5fe0142ca2651fe1ce20d17301a9682d3638f6", - "id": "fls_7vogmqyd87ey", - "link": "functions.html#fls_7vogmqyd87ey", - "number": "9:45" + "checksum": "7ce748762310bab19fffad785d50307098ea7d0e69ac336f7218ce22e6205dfa", + "id": "fls_xmtt04lw517w", + "link": "exceptions-and-errors.html#fls_xmtt04lw517w", + "number": "16.1:4" + } + ], + "title": "Panic" + }, + { + "id": "fls_hi1iz0gbnksi", + "informational": false, + "link": "exceptions-and-errors.html#abort", + "number": "16.2", + "paragraphs": [ + { + "checksum": "7a15948904b2de411b7c6338a4b97095cd8046a23ef8462801fa784b9b75ba03", + "id": "fls_9a1izu3omkbn", + "link": "exceptions-and-errors.html#fls_9a1izu3omkbn", + "number": "16.2:1" }, { - "checksum": "a0ec3be9336e08cd1fca0ed1ac63762201261a4c25b465370a9c89d2c35e0647", - "id": "fls_7ucwmzqtittv", - "link": "functions.html#fls_7ucwmzqtittv", - "number": "9:46" + "checksum": "890c034f6aea73b0142607337332d673a009ec8f47e288cd2cfdab3f5c91cee4", + "id": "fls_iq6olct3rw4u", + "link": "exceptions-and-errors.html#fls_iq6olct3rw4u", + "number": "16.2:2" }, { - "checksum": "77e3105531c29da1bb23103569cc04ef5d3377e723b9f8adcf7603d91463ceae", - "id": "fls_nUADhgcfvvGC", - "link": "functions.html#fls_nUADhgcfvvGC", - "number": "9:47" + "checksum": "77bbb6c647c37714e1fa9e1f81b3f63485155de50d4d371608e8f006a638a216", + "id": "fls_wd2q6ft9yzrg", + "link": "exceptions-and-errors.html#fls_wd2q6ft9yzrg", + "number": "16.2:3" }, { - "checksum": "fbf5fb05e9efc647400ab22d0002c1bc09b999d239850236501817c8374a143c", - "id": "fls_5hn8fkf7rcvz", - "link": "functions.html#fls_5hn8fkf7rcvz", - "number": "9:48" + "checksum": "b5c6b6bfc6de8e05e13b1fa486c385523fa6472c257f7c8c4b2b86ec91d6664f", + "id": "fls_7bnrbjb0pq5n", + "link": "exceptions-and-errors.html#fls_7bnrbjb0pq5n", + "number": "16.2:4" } ], - "title": "Functions" + "title": "Abort" } ], - "title": "Functions" - }, - { - "informational": true, - "link": "glossary.html", - "sections": [], - "title": "Glossary" + "title": "Exceptions and Errors" } ], "metadata": { - "fls_deployed_at": "2026-03-23T11:19:28Z", - "fls_deployed_commit": "0ff3456e02d31d5ddf9f01b4a0339fc1deadb7b9", - "fls_pages_deployment_id": "4148161226", + "fls_deployed_at": "2026-04-10T18:09:47Z", + "fls_deployed_commit": "f5c48f51906cc12b5745ced6658d96ea0850552a", + "fls_pages_deployment_id": "4331579455", "fls_source_url": "https://rust-lang.github.io/fls/paragraph-ids.json", "previous": { - "fls_deployed_at": "2026-03-12T09:28:01Z", - "fls_deployed_commit": "c26f1e1fa960f9b5687793b0ae191f4bdc4ac781", - "fls_pages_deployment_id": "4048711980", + "fls_deployed_at": "2026-03-23T11:19:28Z", + "fls_deployed_commit": "0ff3456e02d31d5ddf9f01b4a0339fc1deadb7b9", + "fls_pages_deployment_id": "4148161226", "fls_source_url": "https://rust-lang.github.io/fls/paragraph-ids.json" } } diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 000000000..e0b513409 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,45 @@ +from types import SimpleNamespace + +import pytest + +from tests.fixtures import reviewer_bot_recorders +from tests.fixtures.reviewer_bot_env import ( + build_test_lease_context, + clear_reviewer_bot_env, +) + + +@pytest.fixture(autouse=True) +def reset_reviewer_bot_process_state_fixture(): + with pytest.MonkeyPatch().context() as monkeypatch: + clear_reviewer_bot_env(monkeypatch) + yield + + +@pytest.fixture +def setenv_many(monkeypatch): + def setter(values: dict[str, str]): + for key, value in values.items(): + monkeypatch.setenv(key, value) + + return setter + + +@pytest.fixture +def lease_context(): + return build_test_lease_context() + + +@pytest.fixture +def tmp_deferred_path(tmp_path): + return tmp_path / "deferred-context.json" + + +@pytest.fixture +def captured_comments(): + return reviewer_bot_recorders.record_comment_dicts(SimpleNamespace()) + + +@pytest.fixture +def captured_status_label_ops(): + return reviewer_bot_recorders.record_status_label_ops(SimpleNamespace()) diff --git a/tests/contract/reviewer_bot/test_adapter_contract.py b/tests/contract/reviewer_bot/test_adapter_contract.py new file mode 100644 index 000000000..d03e1438c --- /dev/null +++ b/tests/contract/reviewer_bot/test_adapter_contract.py @@ -0,0 +1,592 @@ +import json +from pathlib import Path +from types import SimpleNamespace +from typing import get_type_hints + +import pytest + +pytestmark = pytest.mark.contract + +from scripts import reviewer_bot +from scripts.reviewer_bot_lib import event_inputs, lease_lock, review_state +from scripts.reviewer_bot_lib.runtime import ReviewerBotRuntime, StdErrLogger +from scripts.reviewer_bot_lib.runtime_protocols import ( + CommentApplicationRuntimeContext, + CommentRoutingRuntimeContext, + ReconcileAdaptersContext, + ReconcileRectifyGitHubContext, + ReconcileRectifyRuntimeContext, + ReconcileReviewStateAdapterContext, + ReconcileWorkflowRuntimeContext, +) +from tests.fixtures.fake_runtime import FakeReviewerBotRuntime +from tests.fixtures.reviewer_bot import make_state + + +def test_render_lock_commit_message_uses_direct_json_import(): + rendered = lease_lock.render_lock_commit_message(reviewer_bot._runtime_bot(), {"lock_state": "unlocked"}) + assert rendered.startswith("reviewer-bot-lock-v1\n") + + +def test_build_event_context_returns_structured_context(monkeypatch): + monkeypatch.setenv("EVENT_NAME", "workflow_run") + monkeypatch.setenv("EVENT_ACTION", "completed") + monkeypatch.setenv("REVIEWER_BOT_WORKFLOW_KIND", "reconcile") + monkeypatch.setenv("WORKFLOW_RUN_TRIGGERING_CONCLUSION", "success") + monkeypatch.setenv("ISSUE_LABELS", '["coding guideline"]') + event_path = Path("/tmp/test_build_event_context_returns_structured_context.json") + event_path.write_text(json.dumps({"workflow_run": {"name": "Reviewer Bot PR Review Submitted Observer"}}), encoding="utf-8") + monkeypatch.setenv("GITHUB_EVENT_PATH", str(event_path)) + + context = reviewer_bot.build_event_context() + + assert context.event_name == "workflow_run" + assert context.workflow_kind == "reconcile" + assert context.workflow_run_triggering_conclusion == "success" + assert context.workflow_artifact_contract == "artifact_required" + assert context.issue_labels == ("coding guideline",) + + +def test_execute_run_returns_execution_result(monkeypatch): + monkeypatch.setenv("EVENT_NAME", "pull_request_review") + monkeypatch.setenv("EVENT_ACTION", "submitted") + runtime = reviewer_bot._runtime_bot() + monkeypatch.setattr(runtime.state_store, "load_state", lambda *, fail_on_unavailable=False: {"active_reviews": {}}) + + result = reviewer_bot.execute_run(reviewer_bot.build_event_context(runtime), runtime) + + assert result.exit_code == 0 + + +def test_entrypoint_helpers_accept_explicit_runtime(monkeypatch): + runtime = FakeReviewerBotRuntime(monkeypatch) + runtime.set_config_value("EVENT_NAME", "issue_comment") + runtime.set_config_value("EVENT_ACTION", "created") + runtime.set_config_value("ISSUE_NUMBER", "42") + runtime.set_config_value("IS_PULL_REQUEST", "false") + runtime.set_config_value("ISSUE_STATE", "open") + runtime.set_config_value("ISSUE_AUTHOR", "dana") + runtime.set_config_value("ISSUE_LABELS", "[]") + runtime.set_config_value("COMMENT_ID", "100") + runtime.set_config_value("COMMENT_AUTHOR", "alice") + runtime.set_config_value("COMMENT_AUTHOR_ID", "200") + runtime.set_config_value("COMMENT_BODY", "hello") + runtime.set_config_value("COMMENT_CREATED_AT", "2026-03-17T10:00:00Z") + runtime.set_config_value("COMMENT_SOURCE_EVENT_KEY", "issue_comment:100") + runtime.set_config_value("COMMENT_USER_TYPE", "User") + runtime.set_config_value("COMMENT_SENDER_TYPE", "User") + runtime.set_config_value("COMMENT_PERFORMED_VIA_GITHUB_APP", "false") + runtime.handlers.stub("handle_comment_event", lambda state: False) + + context = reviewer_bot.build_event_context(runtime) + result = reviewer_bot.execute_run(context, runtime) + + assert context.event_name == "issue_comment" + assert result.exit_code == 0 + + +def test_review_state_owner_exports_mutation_helper(): + hints = get_type_hints(review_state.ensure_review_entry) + + assert hints["return"] == dict | None + + +def test_runtime_head_repair_contract_is_runtime_scoped(): + hints = get_type_hints(ReconcileReviewStateAdapterContext.maybe_record_head_observation_repair) + + assert hints["return"].__name__ == "HeadObservationRepairResult" + + +def _protocol_member_names(protocol_type) -> set[str]: + return set(protocol_type.__annotations__) | { + name + for name, value in protocol_type.__dict__.items() + if not name.startswith("_") and callable(value) + } + + +def test_k1b_context_freezes_exact_reconcile_workflow_runtime_seam(): + assert _protocol_member_names(ReconcileReviewStateAdapterContext) == { + "maybe_record_head_observation_repair", + } + assert _protocol_member_names(ReconcileAdaptersContext) == {"review_state"} + assert _protocol_member_names(ReconcileWorkflowRuntimeContext) == { + "REVIEW_FRESHNESS_RUNBOOK_PATH", + "logger", + "clock", + "adapters", + "assert_lock_held", + "load_deferred_payload", + "get_config_value", + "collect_touched_item", + "drain_touched_items", + "github_api_request", + "github_api", + } + + +def test_k1b_context_freezes_exact_reconcile_rectify_runtime_seam(): + assert _protocol_member_names(ReconcileRectifyGitHubContext) == { + "get_pull_request_reviews", + "get_user_permission_status", + } + assert _protocol_member_names(ReconcileRectifyRuntimeContext) == { + "github", + "adapters", + "get_config_value", + "parse_iso8601_timestamp", + "github_api_request", + "github_api", + "satisfy_mandatory_approver_requirement", + } + + +def test_k1d_rectify_context_refresh_keeps_live_pr_reads_in_runtime_seam_but_not_approval_owners(): + assert _protocol_member_names(ReconcileRectifyRuntimeContext) == { + "github", + "adapters", + "get_config_value", + "parse_iso8601_timestamp", + "github_api_request", + "github_api", + "satisfy_mandatory_approver_requirement", + } + + +def test_k1e_rectify_context_finalization_captures_retained_approval_support_without_broadening_workflow_seam(): + assert _protocol_member_names(ReconcileRectifyRuntimeContext) == { + "github", + "adapters", + "get_config_value", + "parse_iso8601_timestamp", + "github_api_request", + "github_api", + "satisfy_mandatory_approver_requirement", + } + + +def test_k1c_bootstrap_runtime_satisfies_frozen_workflow_reconcile_protocol(): + runtime = reviewer_bot._runtime_bot() + + assert isinstance(runtime, ReconcileWorkflowRuntimeContext) + + +def test_k1f_runtime_satisfies_finalized_rectify_runtime_protocol(): + runtime = reviewer_bot._runtime_bot() + + assert isinstance(runtime, ReconcileRectifyRuntimeContext) + + +def test_k2_runtime_satisfies_narrow_comment_runtime_protocols(): + runtime = reviewer_bot._runtime_bot() + + assert isinstance(runtime, CommentApplicationRuntimeContext) + assert isinstance(runtime, CommentRoutingRuntimeContext) + + +def test_runtime_review_state_adapter_mutates_active_reviews(): + state = make_state() + review = reviewer_bot._runtime_bot().adapters.review_state.ensure_review_entry(state, 42, create=True) + + assert review is state["active_reviews"]["42"] + + +def test_event_inputs_build_manual_dispatch_request_from_runtime_config(monkeypatch): + runtime = FakeReviewerBotRuntime(monkeypatch) + runtime.set_config_value("MANUAL_ACTION", "preview-reviewer-board") + runtime.set_config_value("ISSUE_NUMBER", "42") + runtime.set_config_value("PRIVILEGED_SOURCE_EVENT_KEY", "issue_comment:100") + + request = event_inputs.build_manual_dispatch_request(runtime) + + assert request.action == "preview-reviewer-board" + assert request.issue_number == 42 + assert request.privileged_source_event_key == "issue_comment:100" + + +def test_event_inputs_build_comment_request_and_pr_admission_from_runtime_config(monkeypatch): + runtime = FakeReviewerBotRuntime(monkeypatch) + runtime.set_config_value("EVENT_NAME", "issue_comment") + runtime.set_config_value("ISSUE_NUMBER", "42") + runtime.set_config_value("IS_PULL_REQUEST", "true") + runtime.set_config_value("ISSUE_STATE", "open") + runtime.set_config_value("ISSUE_AUTHOR", "dana") + runtime.set_config_value("ISSUE_LABELS", '["coding guideline"]') + runtime.set_config_value("COMMENT_ID", "100") + runtime.set_config_value("COMMENT_AUTHOR", "alice") + runtime.set_config_value("COMMENT_AUTHOR_ID", "200") + runtime.set_config_value("COMMENT_BODY", "hello") + runtime.set_config_value("COMMENT_CREATED_AT", "2026-03-17T10:00:00Z") + runtime.set_config_value("COMMENT_SOURCE_EVENT_KEY", "issue_comment:100") + runtime.set_config_value("COMMENT_USER_TYPE", "User") + runtime.set_config_value("COMMENT_SENDER_TYPE", "User") + runtime.set_config_value("COMMENT_INSTALLATION_ID", "") + runtime.set_config_value("COMMENT_PERFORMED_VIA_GITHUB_APP", "false") + runtime.set_config_value("GITHUB_REPOSITORY", "rustfoundation/safety-critical-rust-coding-guidelines") + runtime.set_config_value("PR_HEAD_FULL_NAME", "rustfoundation/safety-critical-rust-coding-guidelines") + runtime.set_config_value("PR_AUTHOR", "dana") + runtime.set_config_value("REVIEWER_BOT_ROUTE_OUTCOME", "trusted_direct") + runtime.set_config_value("REVIEWER_BOT_TRUST_CLASS", "pr_trusted_direct") + runtime.set_config_value("GITHUB_RUN_ID", "123") + runtime.set_config_value("GITHUB_RUN_ATTEMPT", "2") + + request = event_inputs.build_comment_event_request(runtime) + pr_admission = event_inputs.build_pr_comment_admission(runtime) + + assert request.issue_number == 42 + assert request.is_pull_request is True + assert request.comment_id == 100 + assert request.comment_author == "alice" + assert request.comment_author_id == 200 + assert request.issue_labels == ("coding guideline",) + assert request.comment_source_event_key == "issue_comment:100" + assert pr_admission is not None + assert pr_admission.github_repository == "rustfoundation/safety-critical-rust-coding-guidelines" + assert pr_admission.pr_head_full_name == "rustfoundation/safety-critical-rust-coding-guidelines" + assert pr_admission.pr_author == "dana" + assert pr_admission.issue_state == "open" + assert pr_admission.issue_labels == ("coding guideline",) + assert pr_admission.comment_author_id == 200 + assert pr_admission.github_run_id == 123 + assert pr_admission.github_run_attempt == 2 + + +def test_event_inputs_build_pr_admission_rejects_request_boundary_mismatch(monkeypatch): + runtime = FakeReviewerBotRuntime(monkeypatch) + runtime.set_config_value("EVENT_NAME", "issue_comment") + runtime.set_config_value("ISSUE_NUMBER", "42") + runtime.set_config_value("IS_PULL_REQUEST", "true") + runtime.set_config_value("ISSUE_STATE", "open") + runtime.set_config_value("ISSUE_AUTHOR", "dana") + runtime.set_config_value("ISSUE_LABELS", '["coding guideline"]') + runtime.set_config_value("COMMENT_ID", "100") + runtime.set_config_value("COMMENT_AUTHOR", "alice") + runtime.set_config_value("COMMENT_AUTHOR_ID", "200") + runtime.set_config_value("COMMENT_BODY", "hello") + runtime.set_config_value("COMMENT_CREATED_AT", "2026-03-17T10:00:00Z") + runtime.set_config_value("COMMENT_SOURCE_EVENT_KEY", "issue_comment:100") + runtime.set_config_value("COMMENT_USER_TYPE", "User") + runtime.set_config_value("COMMENT_SENDER_TYPE", "User") + runtime.set_config_value("COMMENT_PERFORMED_VIA_GITHUB_APP", "false") + runtime.set_config_value("GITHUB_REPOSITORY", "rustfoundation/safety-critical-rust-coding-guidelines") + runtime.set_config_value("PR_HEAD_FULL_NAME", "rustfoundation/safety-critical-rust-coding-guidelines") + runtime.set_config_value("PR_AUTHOR", "dana") + runtime.set_config_value("REVIEWER_BOT_ROUTE_OUTCOME", "trusted_direct") + runtime.set_config_value("REVIEWER_BOT_TRUST_CLASS", "pr_trusted_direct") + runtime.set_config_value("GITHUB_RUN_ID", "123") + runtime.set_config_value("GITHUB_RUN_ATTEMPT", "2") + + request = event_inputs.build_comment_event_request(runtime) + mismatched = request.__class__(**{**request.__dict__, "comment_author_id": 201}) + + with pytest.raises(event_inputs.InvalidEventInput, match="COMMENT_AUTHOR_ID must match"): + event_inputs.build_pr_comment_admission(runtime, mismatched) + + +def test_event_inputs_build_assignment_and_privileged_requests_from_runtime_config(monkeypatch): + runtime = FakeReviewerBotRuntime(monkeypatch) + runtime.set_config_value("ISSUE_AUTHOR", "dana") + runtime.set_config_value("IS_PULL_REQUEST", "true") + runtime.set_config_value("ISSUE_LABELS", '["fls-audit"]') + runtime.set_config_value("REPO_OWNER", "rustfoundation") + runtime.set_config_value("REPO_NAME", "safety-critical-rust-coding-guidelines") + assignment_request = event_inputs.build_assignment_request(runtime, issue_number=42) + privileged_request = event_inputs.build_privileged_command_request( + runtime, + issue_number=42, + actor="alice", + command_name="accept-no-fls-changes", + ) + + assert assignment_request.issue_number == 42 + assert assignment_request.issue_author == "dana" + assert assignment_request.is_pull_request is True + assert assignment_request.issue_labels == ("fls-audit",) + assert assignment_request.repo_owner == "rustfoundation" + assert assignment_request.repo_name == "safety-critical-rust-coding-guidelines" + assert privileged_request.issue_number == 42 + assert privileged_request.actor == "alice" + assert privileged_request.command_name == "accept-no-fls-changes" + assert privileged_request.is_pull_request is True + assert privileged_request.issue_labels == ("fls-audit",) + + +def test_github_api_assignment_helpers_use_runtime_config_for_pr_vs_issue(monkeypatch): + runtime = FakeReviewerBotRuntime(monkeypatch) + recorded = [] + + def fake_request(method, endpoint, data=None, suppress_error_log=True, **kwargs): + recorded.append((method, endpoint, data)) + return runtime.GitHubApiResult(201, {}, {}, "ok", True, None, 0, None) + + runtime.github_api_request = fake_request + + runtime.set_config_value("IS_PULL_REQUEST", "true") + github_pr_attempt = reviewer_bot._runtime_bot(runtime).github.request_pr_reviewer_assignment(42, "alice") + + runtime.set_config_value("IS_PULL_REQUEST", "false") + github_issue_attempt = reviewer_bot._runtime_bot(runtime).github.assign_issue_assignee(42, "alice") + + assert github_pr_attempt.success is True + assert github_issue_attempt.success is True + assert recorded == [ + ("POST", "pulls/42/requested_reviewers", {"reviewers": ["alice"]}), + ("POST", "issues/42/assignees", {"assignees": ["alice"]}), + ] + + +def test_event_inputs_parse_labels_from_runtime_config(monkeypatch): + runtime = FakeReviewerBotRuntime(monkeypatch) + runtime.set_config_value("ISSUE_LABELS", '["coding guideline", "fls-audit"]') + + assert event_inputs.parse_issue_labels(runtime) == ["coding guideline", "fls-audit"] + + +def test_event_inputs_build_issue_lifecycle_request_from_runtime_config(monkeypatch): + runtime = FakeReviewerBotRuntime(monkeypatch) + runtime.set_config_value("ISSUE_NUMBER", "42") + runtime.set_config_value("IS_PULL_REQUEST", "true") + runtime.set_config_value("ISSUE_LABELS", '["coding guideline"]') + runtime.set_config_value("ISSUE_AUTHOR", "dana") + runtime.set_config_value("SENDER_LOGIN", "alice") + runtime.set_config_value("ISSUE_UPDATED_AT", "2026-03-17T10:00:00Z") + runtime.set_config_value("ISSUE_TITLE", "New title") + runtime.set_config_value("ISSUE_BODY", "new body") + runtime.set_config_value("ISSUE_CHANGES_TITLE_FROM", "Old title") + runtime.set_config_value("ISSUE_CHANGES_BODY_FROM", "old body") + runtime.set_config_value("PR_HEAD_SHA", "head-2") + runtime.set_config_value("EVENT_CREATED_AT", "2026-03-17T10:05:00Z") + + request = event_inputs.build_issue_lifecycle_request(runtime) + + assert request.issue_number == 42 + assert request.is_pull_request is True + assert request.issue_labels == ("coding guideline",) + assert request.issue_author == "dana" + assert request.sender_login == "alice" + assert request.updated_at == "2026-03-17T10:00:00Z" + assert request.issue_title == "New title" + assert request.issue_body == "new body" + assert request.previous_title == "Old title" + assert request.previous_body == "old body" + assert request.pr_head_sha == "head-2" + assert request.event_created_at == "2026-03-17T10:05:00Z" + + +def test_event_inputs_build_label_and_sync_requests_from_runtime_config(monkeypatch): + runtime = FakeReviewerBotRuntime(monkeypatch) + runtime.set_config_value("ISSUE_NUMBER", "42") + runtime.set_config_value("IS_PULL_REQUEST", "true") + runtime.set_config_value("LABEL_NAME", "sign-off: create pr") + runtime.set_config_value("PR_HEAD_SHA", "head-2") + runtime.set_config_value("EVENT_CREATED_AT", "2026-03-17T10:05:00Z") + + label_request = event_inputs.build_label_event_request(runtime) + sync_request = event_inputs.build_pull_request_sync_request(runtime) + + assert label_request.issue_number == 42 + assert label_request.is_pull_request is True + assert label_request.label_name == "sign-off: create pr" + assert sync_request.issue_number == 42 + assert sync_request.head_sha == "head-2" + assert sync_request.event_created_at == "2026-03-17T10:05:00Z" + + +def test_runtime_typed_config_accessors_read_runtime_config(monkeypatch): + runtime = reviewer_bot._runtime_bot() + monkeypatch.setenv("STATE_ISSUE_NUMBER", "77") + monkeypatch.setenv("REVIEWER_BOT_LOCK_API_RETRY_LIMIT", "9") + monkeypatch.setenv("REVIEWER_BOT_LOCK_RETRY_SECONDS", "3.5") + monkeypatch.setenv("REVIEWER_BOT_LOCK_MAX_WAIT_SECONDS", "180") + monkeypatch.setenv("REVIEWER_BOT_LOCK_TTL_SECONDS", "600") + monkeypatch.setenv("REVIEWER_BOT_LOCK_RENEWAL_WINDOW_SECONDS", "90") + monkeypatch.setenv("REVIEWER_BOT_STATE_READ_RETRY_LIMIT", "8") + monkeypatch.setenv("REVIEWER_BOT_STATE_READ_RETRY_SECONDS", "1.5") + monkeypatch.setenv("REVIEWER_BOT_LOCK_REF_NAME", "heads/test-lock") + monkeypatch.setenv("REVIEWER_BOT_LOCK_BOOTSTRAP_BRANCH", "develop") + + assert runtime.state_issue_number() == 77 + assert runtime.lock_api_retry_limit() == 9 + assert runtime.lock_retry_base_seconds() == 3.5 + assert runtime.lock_max_wait_seconds() == 180 + assert runtime.lock_lease_ttl_seconds() == 600 + assert runtime.lock_renewal_window_seconds() == 90 + assert runtime.state_read_retry_limit() == 8 + assert runtime.state_read_retry_base_seconds() == 1.5 + assert runtime.lock_ref_name() == "heads/test-lock" + assert runtime.lock_ref_bootstrap_branch() == "develop" + + +def test_runtime_accepts_injected_infra_services(): + clock = object() + sleeper = object() + jitter = object() + uuid_source = object() + logger = object() + rest_transport = object() + graphql_transport = object() + + runtime = ReviewerBotRuntime( + requests=SimpleNamespace(), + sys=SimpleNamespace(stderr=SimpleNamespace(write=lambda _text: None)), + random=SimpleNamespace(uniform=lambda lower, upper: lower), + time=SimpleNamespace(sleep=lambda _seconds: None), + rest_transport=rest_transport, + graphql_transport=graphql_transport, + clock=clock, + sleeper=sleeper, + jitter=jitter, + uuid_source=uuid_source, + logger=logger, + state_store=SimpleNamespace(load_state=lambda **kwargs: {}, save_state=lambda state: True), + github=SimpleNamespace(github_api=lambda *args, **kwargs: {}, github_api_request=lambda *args, **kwargs: {}), + locks=SimpleNamespace(), + handlers=SimpleNamespace(), + adapters=SimpleNamespace(), + ) + + assert runtime.clock is clock + assert runtime.sleeper is sleeper + assert runtime.jitter is jitter + assert runtime.uuid_source is uuid_source + assert runtime.logger is logger + assert runtime.rest_transport is rest_transport + assert runtime.graphql_transport is graphql_transport + + +def test_runtime_exposes_explicit_infra_and_domain_service_groups(): + runtime = reviewer_bot._runtime_bot() + + assert runtime.infra.config is runtime.config + assert runtime.infra.outputs is runtime.outputs + assert runtime.infra.deferred_payloads is runtime.deferred_payloads + assert runtime.infra.rest_transport is runtime.rest_transport + assert runtime.infra.graphql_transport is runtime.graphql_transport + assert runtime.infra.artifact_download_transport is runtime.artifact_download_transport + assert runtime.infra.clock is runtime.clock + assert runtime.infra.sleeper is runtime.sleeper + assert runtime.infra.jitter is runtime.jitter + assert runtime.infra.uuid_source is runtime.uuid_source + assert runtime.infra.logger is runtime.logger + assert runtime.infra.touch_tracker is runtime.touch_tracker + assert runtime.domain.state_store is runtime.state_store + assert runtime.domain.github is runtime.github + assert runtime.domain.locks is runtime.locks + assert runtime.domain.handlers is runtime.handlers + + +def test_bootstrap_runtime_wires_explicit_config_output_and_deferred_services(): + runtime = reviewer_bot._runtime_bot() + + assert runtime.config is runtime.infra.config + assert runtime.outputs is runtime.infra.outputs + assert runtime.deferred_payloads is runtime.infra.deferred_payloads + + +def test_bootstrap_runtime_wires_explicit_state_github_and_lock_services(): + runtime = reviewer_bot._runtime_bot() + + assert hasattr(runtime.locks, "acquire") + assert hasattr(runtime.locks, "release") + assert hasattr(runtime.locks, "refresh") + assert hasattr(runtime.state_store, "load_state") + assert hasattr(runtime.state_store, "save_state") + assert hasattr(runtime.github, "github_api") + assert hasattr(runtime.github, "github_api_request") + + +def test_bootstrap_runtime_wires_explicit_handler_services(): + runtime = reviewer_bot._runtime_bot() + + assert hasattr(runtime.handlers, "handle_issue_or_pr_opened") + assert hasattr(runtime.handlers, "handle_comment_event") + assert hasattr(runtime.handlers, "handle_workflow_run_event") is False + + +def test_bootstrap_runtime_wires_explicit_adapter_services(): + runtime = reviewer_bot._runtime_bot() + + assert hasattr(runtime.adapters, "github") + assert hasattr(runtime.adapters, "review_state") + assert hasattr(runtime.adapters, "commands") + assert hasattr(runtime.adapters, "queue") + assert hasattr(runtime.adapters, "workflow") + assert hasattr(runtime.adapters, "automation") + assert hasattr(runtime.adapters, "state_lock") + assert runtime.adapters.github is runtime.github + assert hasattr(runtime.adapters.github, "get_github_token") + assert hasattr(runtime.adapters.review_state, "ensure_review_entry") + assert hasattr(runtime.adapters.commands, "handle_pass_command") + assert hasattr(runtime.adapters.queue, "get_next_reviewer") + assert hasattr(runtime.adapters.state_lock, "render_state_issue_body") + + +def test_status_label_sync_contract_stays_on_workflow_adapter_surface(): + runtime = reviewer_bot._runtime_bot() + + assert hasattr(runtime.adapters.workflow, "sync_status_labels_for_items") + assert hasattr(runtime, "list_open_items_with_status_labels") is False + + +def _load_runtime_surface_inventory() -> dict: + return json.loads( + Path("tests/fixtures/equivalence/runtime_surface/triple_inventory.json").read_text( + encoding="utf-8" + ) + ) + + +def test_f2a_runtime_surface_inventory_fixture_records_retained_triples(): + inventory = _load_runtime_surface_inventory() + + assert inventory["harness_id"] == "F2a runtime/bootstrap/fake-runtime triple inventory" + assert inventory["artifact_classification"] == "active migration proof fixture" + assert inventory["proof_artifacts"] + assert all(entry["classification"] == "rewritten final proof" for entry in inventory["proof_artifacts"]) + capabilities = {entry["capability"]: entry for entry in inventory["capability_triples"]} + + assert capabilities["comment-event dispatch"]["classification"] == "retained final surface" + assert "workflow-run dispatch" not in capabilities + assert "refresh reviewer review from live preferred review" not in capabilities + assert "repair missing reviewer review state" not in capabilities + assert capabilities["privileged pull request creation"]["classification"] == "retained final surface" + assert "github timestamp parsing" not in capabilities + + +def test_f2a_runtime_surface_inventory_matches_bootstrap_adapter_examples(): + inventory = _load_runtime_surface_inventory() + capabilities = {entry["capability"]: entry for entry in inventory["capability_triples"]} + + assert capabilities["comment-event dispatch"]["bootstrap_adapter"].endswith("handle_comment_event") + assert "workflow-run dispatch" not in capabilities + assert capabilities["sync status labels"]["bootstrap_adapter"].endswith( + "sync_status_labels_for_items" + ) + assert capabilities["rebuild approval state"]["runtime_forwarder"].endswith( + "rebuild_pr_approval_state" + ) + assert capabilities["rebuild approval state"]["bootstrap_adapter"].endswith( + "rebuild_pr_approval_state" + ) + assert capabilities["mandatory approver satisfaction"]["bootstrap_adapter"].endswith( + "satisfy_mandatory_approver_requirement" + ) + + +def test_f2c_no_runtime_surface_triples_are_deletion_ready_yet(): + inventory = _load_runtime_surface_inventory() + + deletion_ready = [ + entry for entry in inventory["capability_triples"] if entry["classification"] == "zero-caller deletion candidate" + ] + + assert deletion_ready == [] + + +def test_default_stderr_logger_renders_message_and_sorted_fields(): + writes = [] + logger = StdErrLogger(SimpleNamespace(stderr=SimpleNamespace(write=lambda text: writes.append(text)))) + + logger.event("warning", "retrying request", retry_attempt=2, issue_number=42) + + assert writes == ["[warning] retrying request issue_number=42 retry_attempt=2\n"] diff --git a/tests/contract/reviewer_bot/test_blast_radius_review.py b/tests/contract/reviewer_bot/test_blast_radius_review.py new file mode 100644 index 000000000..d2ac8ddb5 --- /dev/null +++ b/tests/contract/reviewer_bot/test_blast_radius_review.py @@ -0,0 +1,321 @@ +import json +from pathlib import Path + + +def _load_review() -> dict: + return json.loads( + Path("tests/fixtures/equivalence/blast_radius/review.json").read_text(encoding="utf-8") + ) + + +def _cards_by_id(review: dict) -> dict[str, dict]: + return {entry["id"]: entry for entry in review["representative_changes"]} + + +def test_f3_blast_radius_review_records_representative_locality_cards(): + review = _load_review() + cards = _cards_by_id(review) + + assert review["harness_id"] == "F3 blast radius review" + assert review["locality_budgets"] == { + "ordinary policy change": { + "production_authority_categories": 1, + "legacy_same_seam_authority_files": 0, + "proof_families": 1, + }, + "support or execution change": { + "production_authority_categories": 2, + "proof_families": 1, + }, + "runtime or protocol change": { + "production_authority_categories": 3, + "proof_families": 2, + }, + "orchestration or transaction change": { + "production_authority_categories": 2, + "proof_families": 2, + }, + } + assert set(cards) == {f"RC{number}" for number in range(1, 11)} + + summary = { + card_id: { + key: cards[card_id][key] + for key in ( + "change", + "change_class", + "production_authority_categories", + "proof_families", + "hotspot_files", + "semantic_inventory_entries", + "expected_files", + ) + } + for card_id in sorted(cards) + } + + assert summary == { + "RC1": { + "change": "reviewer-response policy rule", + "change_class": "ordinary policy change", + "production_authority_categories": ["core-policy"], + "proof_families": ["unit-equivalence"], + "hotspot_files": [], + "semantic_inventory_entries": [ + "tests/fixtures/equivalence/reviewer_response/scenario_matrix.json" + ], + "expected_files": [ + "scripts/reviewer_bot_core/reviewer_response_policy.py", + "tests/unit/reviewer_bot/test_reviewer_response_equivalence.py", + ], + }, + "RC10": { + "change": "workflow-run transaction result field change", + "change_class": "orchestration or transaction change", + "production_authority_categories": ["execution-orchestration"], + "proof_families": ["integration-transaction"], + "hotspot_files": [ + "scripts/reviewer_bot_lib/reconcile.py", + "scripts/reviewer_bot_lib/app.py", + ], + "semantic_inventory_entries": [], + "expected_files": [ + "scripts/reviewer_bot_lib/reconcile.py", + "scripts/reviewer_bot_lib/app.py", + "tests/integration/reviewer_bot/test_app_execution.py", + "tests/integration/reviewer_bot/test_app_workflow_run_bookkeeping.py", + "tests/integration/reviewer_bot/test_app_workflow_run_paths.py", + ], + }, + "RC2": { + "change": "live-repair rule", + "change_class": "ordinary policy change", + "production_authority_categories": ["core-policy"], + "proof_families": ["unit-equivalence"], + "hotspot_files": [], + "semantic_inventory_entries": [ + "tests/fixtures/equivalence/review_state_live_repair/scenarios.json" + ], + "expected_files": [ + "scripts/reviewer_bot_core/review_state_live_repair.py", + "tests/unit/reviewer_bot/test_review_state_equivalence.py", + ], + }, + "RC3": { + "change": "approval rule", + "change_class": "ordinary policy change", + "production_authority_categories": ["core-policy"], + "proof_families": ["unit-equivalence"], + "hotspot_files": [], + "semantic_inventory_entries": [ + "tests/fixtures/equivalence/approval_policy/in_scope_functions.json" + ], + "expected_files": [ + "scripts/reviewer_bot_core/approval_policy.py", + "tests/unit/reviewer_bot/test_approval_policy_equivalence.py", + ], + }, + "RC4": { + "change": "mandatory-approver policy rule", + "change_class": "ordinary policy change", + "production_authority_categories": ["core-policy"], + "proof_families": ["unit-equivalence"], + "hotspot_files": [], + "semantic_inventory_entries": [ + "tests/fixtures/equivalence/mandatory_approver_policy/decision_matrix.json" + ], + "expected_files": [ + "scripts/reviewer_bot_core/mandatory_approver_policy.py", + "tests/unit/reviewer_bot/test_mandatory_approver_policy_equivalence.py", + ], + }, + "RC5": { + "change": "mandatory-approver execution flow", + "change_class": "support or execution change", + "production_authority_categories": ["lib-support"], + "proof_families": ["unit-live-fetch"], + "hotspot_files": ["scripts/reviewer_bot_lib/reviews.py"], + "semantic_inventory_entries": [], + "expected_files": [ + "scripts/reviewer_bot_lib/reviews.py", + "tests/unit/reviewer_bot/test_reviews_live_fetch.py", + ], + }, + "RC6": { + "change": "deferred replay fail-closed message", + "change_class": "orchestration or transaction change", + "production_authority_categories": ["core-policy"], + "proof_families": ["unit-equivalence", "integration-transaction"], + "hotspot_files": [], + "semantic_inventory_entries": [], + "expected_files": [ + "scripts/reviewer_bot_core/reconcile_replay_policy.py", + "tests/unit/reviewer_bot/test_reconcile_replay_equivalence.py", + "tests/integration/reviewer_bot/test_reconcile_workflow_run.py", + ], + }, + "RC7": { + "change": "ordinary command rule", + "change_class": "ordinary policy change", + "production_authority_categories": ["core-policy"], + "proof_families": ["unit-equivalence"], + "hotspot_files": [], + "semantic_inventory_entries": [], + "expected_files": [ + "scripts/reviewer_bot_core/comment_command_policy.py", + "tests/unit/reviewer_bot/test_comment_command_equivalence.py", + "tests/unit/reviewer_bot/test_commands.py", + ], + }, + "RC8": { + "change": "reconcile workflow protocol change", + "change_class": "runtime or protocol change", + "production_authority_categories": ["execution-orchestration", "runtime-protocol"], + "proof_families": ["unit-equivalence", "contract-inventory"], + "hotspot_files": [ + "scripts/reviewer_bot_lib/context.py", + "scripts/reviewer_bot_lib/reconcile.py", + ], + "semantic_inventory_entries": [ + "tests/fixtures/equivalence/runtime_surface/deletion_manifest.json" + ], + "expected_files": [ + "scripts/reviewer_bot_lib/context.py", + "scripts/reviewer_bot_lib/reconcile.py", + "tests/unit/reviewer_bot/test_reconcile_unit.py", + "tests/contract/reviewer_bot/test_runtime_protocols.py", + ], + }, + "RC9": { + "change": "runtime compatibility surface change", + "change_class": "runtime or protocol change", + "production_authority_categories": ["runtime-protocol", "fixture-double"], + "proof_families": ["contract-inventory"], + "hotspot_files": [ + "scripts/reviewer_bot_lib/runtime.py", + "scripts/reviewer_bot_lib/bootstrap_runtime.py", + "tests/fixtures/fake_runtime.py", + "tests/fixtures/focused_fake_services.py", + ], + "semantic_inventory_entries": [ + "tests/fixtures/equivalence/runtime_surface/triple_inventory.json", + "tests/fixtures/equivalence/runtime_surface/deletion_manifest.json", + ], + "expected_files": [ + "scripts/reviewer_bot_lib/runtime.py", + "scripts/reviewer_bot_lib/bootstrap_runtime.py", + "tests/fixtures/fake_runtime.py", + "tests/fixtures/focused_fake_services.py", + "tests/contract/reviewer_bot/test_adapter_contract.py", + "tests/contract/reviewer_bot/test_fake_runtime_contract.py", + "tests/contract/reviewer_bot/test_runtime_protocols.py", + ], + }, + } + + +def test_f3_representative_changes_stay_within_locality_budgets(): + review = _load_review() + budgets = review["locality_budgets"] + + for card in review["representative_changes"]: + budget = budgets[card["change_class"]] + assert len(card["production_authority_categories"]) <= budget["production_authority_categories"], card["id"] + assert len(card["proof_families"]) <= budget["proof_families"], card["id"] + assert set(card["hotspot_files"]).issubset(set(card["expected_files"])), card["id"] + if card["change_class"] == "ordinary policy change": + assert "scripts/reviewer_bot_lib/reviews.py" not in card["expected_files"], card["id"] + assert budgets[card["change_class"]]["legacy_same_seam_authority_files"] == 0 + + +def test_f3_blast_radius_review_matches_current_owner_locations(): + mandatory_approver_policy_text = Path("scripts/reviewer_bot_core/mandatory_approver_policy.py").read_text(encoding="utf-8") + reviews_text = Path("scripts/reviewer_bot_lib/reviews.py").read_text(encoding="utf-8") + reviewer_response_policy_text = Path("scripts/reviewer_bot_core/reviewer_response_policy.py").read_text(encoding="utf-8") + live_repair_text = Path("scripts/reviewer_bot_core/review_state_live_repair.py").read_text(encoding="utf-8") + helper_text = Path("scripts/reviewer_bot_core/reviewer_review_helpers.py").read_text(encoding="utf-8") + approval_policy_text = Path("scripts/reviewer_bot_core/approval_policy.py").read_text(encoding="utf-8") + replay_text = Path("scripts/reviewer_bot_core/reconcile_replay_policy.py").read_text(encoding="utf-8") + command_text = Path("scripts/reviewer_bot_core/comment_command_policy.py").read_text(encoding="utf-8") + + assert "return reviewer_response_policy.compute_reviewer_response_state(" in reviews_text + assert "approval_policy.compute_pr_approval_state_result(" in reviewer_response_policy_text + assert "legacy_reviews.resolve_pr_approval_state(" not in reviewer_response_policy_text + assert "reviewer_review_helpers.get_preferred_current_reviewer_review_for_cycle(" in reviewer_response_policy_text + assert "reviewer_review_helpers.build_reviewer_review_record_from_live_review(" in reviewer_response_policy_text + assert "reviewer_review_helpers.compare_records(" in reviewer_response_policy_text + assert "legacy_reviews.get_preferred_current_reviewer_review_for_cycle(" not in reviewer_response_policy_text + assert "legacy_reviews.build_reviewer_review_record_from_live_review(" not in reviewer_response_policy_text + assert "legacy_reviews._compare_records(" not in reviewer_response_policy_text + assert "live_review_support.read_pull_request_result(" in reviewer_response_policy_text + assert "live_review_support.read_pull_request_reviews_result(" in reviewer_response_policy_text + assert "reviewer_review_helpers.get_preferred_current_reviewer_review_for_cycle(" in live_repair_text + assert "reviewer_review_helpers.build_reviewer_review_record_from_live_review(" in live_repair_text + assert "legacy_reviews.get_preferred_current_reviewer_review_for_cycle(" not in live_repair_text + assert "legacy_reviews.build_reviewer_review_record_from_live_review(" not in live_repair_text + assert "live_review_support.read_pull_request_result(" in live_repair_text + assert "review_state_machine.upsert_channel_accepted_record(" in live_repair_text + assert "mandatory_approver_policy.decide_mandatory_approver_escalation(" in reviews_text + assert "mandatory_approver_policy.decide_mandatory_approver_satisfaction(" in reviews_text + assert "def trigger_mandatory_approver_escalation(" in reviews_text + assert "def satisfy_mandatory_approver_requirement(" in reviews_text + assert "def refresh_reviewer_review_from_live_preferred_review(" not in reviews_text + assert "def repair_missing_reviewer_review_state(" not in reviews_text + assert "def decide_mandatory_approver_escalation(" in mandatory_approver_policy_text + assert "def decide_mandatory_approver_satisfaction(" in mandatory_approver_policy_text + assert "live_review_support.projection_failure_result(" in approval_policy_text + assert "live_review_support.read_pull_request_result(" in approval_policy_text + assert "live_review_support.read_pull_request_reviews_result(" in approval_policy_text + assert "live_review_support.permission_status(" in approval_policy_text + assert "live_review_support.parse_github_timestamp" in approval_policy_text + assert "def get_preferred_current_reviewer_review_for_cycle(" in helper_text + assert "def build_reviewer_review_record_from_live_review(" in helper_text + assert "def compare_records(" in helper_text + assert "def decide_comment_replay(" in replay_text + assert "def decide_comment_command(" in command_text + + +def test_o5_final_proof_artifact_classification_is_explicit(): + review = _load_review() + + assert review["surviving_final_proof_artifacts"] == [ + { + "path": "tests/contract/reviewer_bot/test_blast_radius_review.py", + "reason": "active blast-radius risk for future change", + }, + { + "path": "tests/fixtures/equivalence/blast_radius/review.json", + "reason": "active blast-radius risk for future change", + }, + { + "path": "tests/contract/reviewer_bot/test_runtime_protocols.py", + "reason": "active runtime/bootstrap/fake-runtime compatibility safety", + }, + { + "path": "tests/contract/reviewer_bot/test_adapter_contract.py", + "reason": "active runtime/bootstrap/fake-runtime compatibility safety", + }, + { + "path": "tests/contract/reviewer_bot/test_fake_runtime_contract.py", + "reason": "active runtime/bootstrap/fake-runtime compatibility safety", + }, + ] + assert review["active_migration_proof_artifacts"] == [ + { + "path": "tests/contract/reviewer_bot/test_support_layer_ownership.py", + "reason": "active migration proof that still enforces support-layer ownership inventory", + }, + { + "path": "tests/fixtures/equivalence/support_layer/symbol_inventory.json", + "reason": "active migration fixture still enforced by support-layer ownership contract proof", + }, + { + "path": "tests/fixtures/equivalence/runtime_surface/triple_inventory.json", + "reason": "active migration fixture until runtime-surface inventory and deletion proof retire together", + }, + { + "path": "tests/fixtures/equivalence/runtime_surface/deletion_manifest.json", + "reason": "active migration fixture until runtime-surface inventory and deletion proof retire together", + }, + ] + assert review["retired_migration_only_artifacts"] == [] diff --git a/tests/contract/reviewer_bot/test_comment_application_contract.py b/tests/contract/reviewer_bot/test_comment_application_contract.py new file mode 100644 index 000000000..73d33cf25 --- /dev/null +++ b/tests/contract/reviewer_bot/test_comment_application_contract.py @@ -0,0 +1,242 @@ +from pathlib import Path + +import pytest + +from scripts.reviewer_bot_core import comment_command_policy +from scripts.reviewer_bot_lib import comment_application, comment_routing, review_state +from scripts.reviewer_bot_lib.config import FLS_AUDIT_LABEL +from tests.fixtures.comment_routing_harness import CommentRoutingHarness +from tests.fixtures.reviewer_bot import make_state + +pytestmark = pytest.mark.contract + + +def test_comment_application_digest_is_stable_for_replay_identity(): + assert comment_application.digest_comment_body("hello\r\nworld\n") == comment_application.digest_comment_body("hello\nworld") + + +def test_comment_application_records_contributor_freshness_from_typed_request(monkeypatch): + harness = CommentRoutingHarness(monkeypatch) + state = make_state() + review = review_state.ensure_review_entry(state, 42, create=True) + assert review is not None + request = harness.request( + issue_number=42, + is_pull_request=False, + issue_author="dana", + comment_author="dana", + comment_body="plain text", + ) + + changed = comment_application.record_conversation_freshness(harness.runtime, state, request) + + assert changed is True + assert review["contributor_comment"]["accepted"]["semantic_key"] == "issue_comment:100" + + +def test_comment_application_stores_pending_privileged_command_from_typed_request(monkeypatch): + harness = CommentRoutingHarness(monkeypatch) + state = make_state() + review = review_state.ensure_review_entry(state, 42, create=True) + assert review is not None + request = harness.request( + issue_number=42, + is_pull_request=False, + issue_author="dana", + issue_labels=(FLS_AUDIT_LABEL,), + comment_author="dana", + comment_body="@guidelines-bot /accept-no-fls-changes", + ) + harness.runtime.get_user_permission_status = lambda username, required_permission="triage": "granted" + harness.runtime.post_comment = lambda issue_number, body: True + + changed = comment_application.process_comment_event( + harness.runtime, + state, + request, + classify_comment_payload=comment_routing.classify_comment_payload, + classify_issue_comment_actor=comment_routing.classify_issue_comment_actor, + ) + + assert changed is True + assert review["sidecars"]["pending_privileged_commands"]["issue_comment:100"]["command_name"] == "accept-no-fls-changes" + + +def test_comment_application_freezes_pending_privileged_command_metadata_shape_and_ack_message(monkeypatch): + harness = CommentRoutingHarness(monkeypatch) + state = make_state() + review = review_state.ensure_review_entry(state, 42, create=True) + assert review is not None + posted = [] + request = harness.request( + issue_number=42, + is_pull_request=False, + issue_author="dana", + issue_labels=(FLS_AUDIT_LABEL,), + comment_author="alice", + comment_body="@guidelines-bot /accept-no-fls-changes", + ) + harness.runtime.set_config_value("STATE_ISSUE_NUMBER", "314") + harness.runtime.get_user_permission_status = lambda username, required_permission="triage": "granted" + harness.runtime.post_comment = lambda issue_number, body: posted.append((issue_number, body)) or True + + changed = comment_application.process_comment_event( + harness.runtime, + state, + request, + classify_comment_payload=comment_routing.classify_comment_payload, + classify_issue_comment_actor=comment_routing.classify_issue_comment_actor, + ) + + assert changed is True + pending = review["sidecars"]["pending_privileged_commands"]["issue_comment:100"] + assert list(pending) == [ + "source_event_key", + "command_name", + "issue_number", + "actor", + "authorization_required_permission", + "authorization_authorized", + "target_kind", + "target_number", + "target_labels_snapshot", + "status", + "created_at", + "completed_at", + "result_code", + "result_message", + "opened_pr_url", + ] + assert posted == [ + ( + 42, + "✅ Recorded pending privileged command `accept-no-fls-changes` from trusted live validation. Use the isolated privileged workflow to execute it from issue `#314` state.", + ) + ] + + +def test_c3b2_comment_application_deletion_manifest_leaves_only_privileged_branching(): + module_text = Path("scripts/reviewer_bot_lib/comment_application.py").read_text(encoding="utf-8") + + assert "DeferPrivilegedHandoffDecision" in module_text + assert "ORDINARY_COMMAND_HANDLERS" in module_text + assert "decision.command_id" in module_text + for command_name in [ + '"pass"', + '"away"', + '"label"', + '"sync-members"', + '"queue"', + '"commands"', + '"claim"', + '"release"', + '"rectify"', + '"r?-user"', + '"assign-from-queue"', + '"_multiple_commands"', + '"_malformed_known"', + '"_malformed_unknown"', + ]: + assert f"command == {command_name}" not in module_text + + +def test_n1_comment_application_obeys_policy_selected_handler_without_inline_command_branching(monkeypatch): + harness = CommentRoutingHarness(monkeypatch) + state = make_state() + review = review_state.ensure_review_entry(state, 42, create=True) + assert review is not None + request = harness.request( + issue_number=42, + is_pull_request=False, + issue_author="dana", + comment_author="alice", + comment_body="@guidelines-bot /claim", + ) + calls = [] + + monkeypatch.setattr( + comment_command_policy, + "decide_comment_command", + lambda *args, **kwargs: comment_command_policy.ExecuteOrdinaryCommandDecision( + command_id=comment_command_policy.OrdinaryCommandId.QUEUE, + issue_number=42, + actor="alice", + raw_args=(), + needs_assignment_request=False, + ), + ) + monkeypatch.setattr( + comment_application.commands_module, + "handle_queue_command", + lambda bot, current_state: calls.append((bot, current_state)) or ("", True), + ) + harness.runtime.add_reaction = lambda *args, **kwargs: True + + changed = comment_application.process_comment_event( + harness.runtime, + state, + request, + classify_comment_payload=lambda bot, body: {"comment_class": "command_only", "command_count": 1, "command": "claim", "args": []}, + classify_issue_comment_actor=comment_routing.classify_issue_comment_actor, + ) + + assert changed is False + assert calls == [(harness.runtime, state)] + + +def test_comment_application_no_longer_owns_privileged_handoff_validation_or_metadata_shaping(): + module_text = Path("scripts/reviewer_bot_lib/comment_application.py").read_text(encoding="utf-8") + + assert "privileged_command_policy.validate_accept_no_fls_changes_handoff(" in module_text + assert "privileged_command_policy.build_pending_privileged_command(" in module_text + assert "put_pending_accept_no_fls_changes" in module_text + + +def test_comment_application_no_longer_owns_direct_comment_freshness_decision_branching(): + module_text = Path("scripts/reviewer_bot_lib/comment_application.py").read_text(encoding="utf-8") + + assert "comment_freshness_policy.decide_comment_freshness(review_data, request)" in module_text + assert "if request.issue_author and request.issue_author.lower() == comment_author.lower():" not in module_text + assert "if isinstance(current_reviewer, str) and current_reviewer.lower() == comment_author.lower():" not in module_text + + +def test_n1_comment_application_obeys_routing_result_without_text_shape_contract(monkeypatch): + harness = CommentRoutingHarness(monkeypatch) + state = make_state() + review = review_state.ensure_review_entry(state, 42, create=True) + assert review is not None + request = harness.request( + issue_number=42, + is_pull_request=False, + issue_author="dana", + comment_author="dana", + comment_body="plain text", + ) + calls = {"freshness": 0, "command": 0} + + monkeypatch.setattr( + comment_application, + "_route_comment_application", + lambda classified, *, comment_id: "freshness_only", + ) + monkeypatch.setattr( + comment_application, + "record_conversation_freshness", + lambda bot, current_state, current_request: calls.__setitem__("freshness", calls["freshness"] + 1) or True, + ) + monkeypatch.setattr( + comment_application, + "apply_comment_command", + lambda *args, **kwargs: calls.__setitem__("command", calls["command"] + 1) or False, + ) + + changed = comment_application.process_comment_event( + harness.runtime, + state, + request, + classify_comment_payload=lambda bot, body: {"comment_class": "plain_text", "command_count": 0, "command": None, "args": []}, + classify_issue_comment_actor=comment_routing.classify_issue_comment_actor, + ) + + assert changed is True + assert calls == {"freshness": 1, "command": 0} diff --git a/tests/contract/reviewer_bot/test_fake_github_transport_contract.py b/tests/contract/reviewer_bot/test_fake_github_transport_contract.py new file mode 100644 index 000000000..6704d0c9c --- /dev/null +++ b/tests/contract/reviewer_bot/test_fake_github_transport_contract.py @@ -0,0 +1,149 @@ +import pytest + +from tests.fixtures.reviewer_bot_fakes import RouteGitHubApi, github_result + +pytestmark = pytest.mark.contract + + +def test_github_result_builds_success_shape(): + result = github_result(200, {"ok": True}, headers={"ETag": "abc"}) + + assert result.status_code == 200 + assert result.payload == {"ok": True} + assert result.headers == {"etag": "abc"} + assert result.ok is True + assert result.failure_kind is None + + +def test_github_result_builds_not_found_shape(): + result = github_result(404, {"message": "missing"}) + + assert result.ok is False + assert result.failure_kind == "not_found" + assert result.text == "missing" + + +def test_github_result_builds_server_error_shape(): + result = github_result(502, {"message": "bad gateway"}) + + assert result.ok is False + assert result.failure_kind == "server_error" + assert result.text == "bad gateway" + + +def test_route_github_api_request_supports_invalid_payload_routes(): + routes = RouteGitHubApi().add_request("GET", "pulls/42", status_code=200, payload=["not", "a", "dict"]) + + result = routes.github_api_request("GET", "pulls/42") + + assert result.ok is True + assert result.payload == ["not", "a", "dict"] + + +def test_route_github_api_raises_system_exit_on_request_and_keeps_api_mode(): + routes = ( + RouteGitHubApi() + .add_api("GET", "pulls/42/reviews?per_page=100&page=1", [{"id": 10}]) + .raise_system_exit_on_request() + ) + + with pytest.raises(SystemExit): + routes.github_api_request("GET", "pulls/42/reviews?per_page=100&page=1") + + assert routes.github_api("GET", "pulls/42/reviews?per_page=100&page=1") == [{"id": 10}] + + +def test_route_github_api_supports_paginated_review_endpoints(): + routes = ( + RouteGitHubApi() + .add_request( + "GET", + "pulls/42/reviews?per_page=100&page=1", + status_code=200, + payload=[{"id": index} for index in range(100)], + ) + .add_request( + "GET", + "pulls/42/reviews?per_page=100&page=2", + status_code=200, + payload=[{"id": 100}], + ) + ) + + first = routes.github_api_request("GET", "pulls/42/reviews?per_page=100&page=1") + second = routes.github_api_request("GET", "pulls/42/reviews?per_page=100&page=2") + + assert len(first.payload) == 100 + assert second.payload == [{"id": 100}] + assert routes.requested_endpoints() == [ + "pulls/42/reviews?per_page=100&page=1", + "pulls/42/reviews?per_page=100&page=2", + ] + + +def test_route_github_api_add_pull_request_snapshot_registers_api_and_request_modes(): + routes = RouteGitHubApi().add_pull_request_snapshot(42, {"head": {"sha": "head-1"}}) + + assert routes.github_api("GET", "pulls/42") == {"head": {"sha": "head-1"}} + result = routes.github_api_request("GET", "pulls/42") + assert result.ok is True + assert result.payload == {"head": {"sha": "head-1"}} + + +def test_route_github_api_add_pull_request_reviews_registers_page_route(): + routes = RouteGitHubApi().add_pull_request_reviews(42, [{"id": 10}], page=2) + + result = routes.github_api_request("GET", "pulls/42/reviews?per_page=100&page=2") + + assert result.ok is True + assert result.payload == [{"id": 10}] + + +def test_route_github_api_request_sequence_replays_retryable_failures_then_success(): + routes = RouteGitHubApi().add_request_sequence( + "GET", + "issues/42", + [ + github_result(429, {"message": "slow down"}), + github_result(502, {"message": "bad gateway"}), + github_result(200, {"ok": True}), + ], + ) + + first = routes.github_api_request("GET", "issues/42") + second = routes.github_api_request("GET", "issues/42") + third = routes.github_api_request("GET", "issues/42") + fourth = routes.github_api_request("GET", "issues/42") + + assert first.failure_kind == "rate_limited" + assert second.failure_kind == "server_error" + assert third.ok is True + assert fourth.ok is True + + +def test_route_github_api_request_sequence_can_raise_then_return_success(): + routes = RouteGitHubApi().add_request_sequence( + "GET", + "issues/42", + [RuntimeError("timeout"), github_result(200, {"ok": True})], + ) + + with pytest.raises(RuntimeError, match="timeout"): + routes.github_api_request("GET", "issues/42") + + result = routes.github_api_request("GET", "issues/42") + + assert result.ok is True + assert result.payload == {"ok": True} + + +def test_route_github_api_api_sequence_replays_values_and_keeps_last_value(): + routes = RouteGitHubApi().add_api_sequence( + "GET", + "pulls/42", + [{"head": {"sha": "head-1"}}, {"head": {"sha": "head-2"}}], + ) + + assert routes.github_api("GET", "pulls/42") == {"head": {"sha": "head-1"}} + assert routes.github_api("GET", "pulls/42") == {"head": {"sha": "head-2"}} + assert routes.github_api("GET", "pulls/42") == {"head": {"sha": "head-2"}} diff --git a/tests/contract/reviewer_bot/test_fake_runtime_contract.py b/tests/contract/reviewer_bot/test_fake_runtime_contract.py new file mode 100644 index 000000000..00b0ed4bf --- /dev/null +++ b/tests/contract/reviewer_bot/test_fake_runtime_contract.py @@ -0,0 +1,350 @@ +import json +from pathlib import Path + +import pytest + +from scripts.reviewer_bot_lib.runtime_protocols import ( + CommentApplicationRuntimeContext, + CommentRoutingRuntimeContext, + ReconcileRectifyRuntimeContext, + ReconcileWorkflowRuntimeContext, +) +from tests.fixtures.fake_runtime import FakeReviewerBotRuntime +from tests.fixtures.focused_fake_services import ( + ArtifactDownloadTransportStub, + ConfigBag, + DeferredPayloadStore, + GitHubStub, + GraphQLTransportStub, + HandlerStub, + LockStub, + OutputCapture, + RestTransportStub, + StateStoreStub, + TouchTrackerStub, + WorkflowBehaviorStub, + build_default_handler_map, +) +from tests.fixtures.reviewer_bot_fakes import RouteGitHubApi + +pytestmark = pytest.mark.contract + + +def test_fake_runtime_config_writes_round_trip_locally(monkeypatch): + runtime = FakeReviewerBotRuntime(monkeypatch) + + runtime.set_config_value("EVENT_NAME", "issue_comment") + + assert runtime.get_config_value("EVENT_NAME") == "issue_comment" + + +def test_fake_runtime_config_writes_do_not_leak_to_process_env(monkeypatch): + runtime = FakeReviewerBotRuntime(monkeypatch) + monkeypatch.delenv("EVENT_NAME", raising=False) + + runtime.set_config_value("EVENT_NAME", "issue_comment") + + import os + + assert "EVENT_NAME" not in os.environ + assert hasattr(FakeReviewerBotRuntime, "__getattr__") is False + assert "_module" not in vars(runtime) + + +def test_fake_runtime_exposes_explicit_service_fields_and_no_omnibus_service_container(monkeypatch): + runtime = FakeReviewerBotRuntime(monkeypatch) + + assert runtime.config is not None + assert runtime.outputs is not None + assert runtime.deferred_payloads is not None + assert runtime.logger is not None + assert runtime.state_store is not None + assert runtime.github is not None + assert runtime.locks is not None + assert runtime.handlers is not None + assert runtime.touch_tracker is not None + assert runtime.infra is not None + assert runtime.domain is not None + assert runtime.compat is not None + assert hasattr(runtime, "services") is False + assert hasattr(runtime, "components") is False + + +def test_fake_runtime_exposes_no_class_level_module_authority_hints(monkeypatch): + runtime = FakeReviewerBotRuntime(monkeypatch) + module_hints = sorted(name for name in vars(FakeReviewerBotRuntime) if name.endswith("_module")) + + assert module_hints == [] + assert hasattr(runtime, "review_state_module") is False + assert hasattr(runtime, "reviews_module") is False + + +def test_fake_runtime_output_sink_records_writes(monkeypatch): + runtime = FakeReviewerBotRuntime(monkeypatch) + + runtime.write_output("state_changed", "true") + + assert runtime.outputs.writes == [("state_changed", "true")] + + +def test_fake_runtime_recording_logger_captures_structured_events(monkeypatch): + runtime = FakeReviewerBotRuntime(monkeypatch) + + runtime.logger.event("warning", "retrying", issue_number=42, retry_attempt=2) + + assert runtime.logger.records == [ + { + "level": "warning", + "message": "retrying", + "fields": {"issue_number": 42, "retry_attempt": 2}, + } + ] + + +def test_fake_runtime_touched_items_preserve_uniqueness_and_drain(monkeypatch): + runtime = FakeReviewerBotRuntime(monkeypatch) + + runtime.collect_touched_item(42) + runtime.collect_touched_item(42) + runtime.collect_touched_item(99) + + assert runtime.drain_touched_items() == [42, 99] + assert runtime.drain_touched_items() == [] + + +def test_fake_runtime_stub_state_sequence_replays_until_last_snapshot(monkeypatch): + runtime = FakeReviewerBotRuntime(monkeypatch) + runtime.stub_state_sequence({"active_reviews": {"42": {}}}, {"active_reviews": {}}) + + first = runtime.load_state() + second = runtime.load_state() + third = runtime.load_state() + + assert first == {"active_reviews": {"42": {}}} + assert second == {"active_reviews": {}} + assert third == {"active_reviews": {}} + + +def test_fake_runtime_stub_state_unavailable_requires_fail_closed_load(monkeypatch): + runtime = FakeReviewerBotRuntime(monkeypatch) + runtime.stub_state_unavailable("state unavailable") + + with pytest.raises(RuntimeError, match="state unavailable"): + runtime.load_state(fail_on_unavailable=True) + + +def test_fake_runtime_record_saves_captures_structured_snapshots(monkeypatch): + runtime = FakeReviewerBotRuntime(monkeypatch) + snapshots = [] + runtime.record_saves(snapshots) + + state = {"active_reviews": {"42": {"current_reviewer": "alice"}}} + assert runtime.save_state(state) is True + state["active_reviews"]["42"]["current_reviewer"] = "bob" + + assert snapshots == [{"active_reviews": {"42": {"current_reviewer": "alice"}}}] + + +def test_fake_runtime_optional_lock_hooks_are_replaceable(monkeypatch): + runtime = FakeReviewerBotRuntime(monkeypatch) + calls = [] + runtime.stub_lock(acquire=lambda: calls.append("acquire") or None, release=lambda: calls.append("release") or True) + + assert runtime.acquire_state_issue_lease_lock() is None + assert runtime.release_state_issue_lease_lock() is True + assert calls == ["acquire", "release"] + + +def test_fake_runtime_uses_explicit_public_service_fields(monkeypatch): + runtime = FakeReviewerBotRuntime(monkeypatch) + + assert runtime.config is not None + assert runtime.outputs is not None + assert runtime.deferred_payloads is not None + assert runtime.state_store is not None + assert runtime.github is not None + assert runtime.locks is not None + assert runtime.touch_tracker is not None + + +def test_fake_runtime_review_state_compatibility_surface_is_limited(monkeypatch): + runtime = FakeReviewerBotRuntime(monkeypatch) + + allowed = {"ensure_review_entry", "set_current_reviewer", "update_reviewer_activity", "mark_review_complete"} + removed = {"record_transition_notice_sent", "accept_channel_event", "record_reviewer_activity", "get_current_cycle_boundary"} + + for name in allowed: + assert hasattr(runtime, name) + for name in removed: + assert hasattr(runtime, name) is False + + +def test_fake_runtime_rejects_unknown_handler_names(monkeypatch): + runtime = FakeReviewerBotRuntime(monkeypatch) + + with pytest.raises(AssertionError, match="Unsupported runtime handler override"): + runtime.handlers.stub("handle_everything", lambda state: False) + + +def test_fake_runtime_github_transport_delegates_to_shared_route_fake(monkeypatch): + github = RouteGitHubApi().add_request("GET", "pulls/42", status_code=200, payload={"head": {"sha": "head-1"}}) + runtime = FakeReviewerBotRuntime(monkeypatch, github=github) + + result = runtime.github_api_request("GET", "pulls/42") + + assert result.ok is True + assert result.payload == {"head": {"sha": "head-1"}} + + +def test_fake_runtime_github_api_mode_delegates_to_shared_route_fake(monkeypatch): + github = RouteGitHubApi().add_api("GET", "pulls/42", {"head": {"sha": "head-1"}}) + runtime = FakeReviewerBotRuntime(monkeypatch, github=github) + + assert runtime.github_api("GET", "pulls/42") == {"head": {"sha": "head-1"}} + + +def test_focused_fake_service_types_are_exposed_for_direct_fixture_composition(monkeypatch): + runtime = FakeReviewerBotRuntime(monkeypatch) + + assert isinstance(runtime.config, ConfigBag) + assert isinstance(runtime.outputs, OutputCapture) + assert isinstance(runtime.deferred_payloads, DeferredPayloadStore) + assert isinstance(runtime.state_store, StateStoreStub) + assert isinstance(runtime.github, GitHubStub) + assert isinstance(runtime.locks, LockStub) + assert isinstance(runtime.rest_transport, RestTransportStub) + assert isinstance(runtime.graphql_transport, GraphQLTransportStub) + assert isinstance(runtime.artifact_download_transport, ArtifactDownloadTransportStub) + assert isinstance(runtime.handlers, HandlerStub) + assert isinstance(runtime.touch_tracker, TouchTrackerStub) + assert isinstance(runtime.workflow, WorkflowBehaviorStub) + + +def test_fake_runtime_groups_focused_services_into_infra_and_domain_shells(monkeypatch): + runtime = FakeReviewerBotRuntime(monkeypatch) + + assert runtime.infra.config is runtime.config + assert runtime.infra.outputs is runtime.outputs + assert runtime.infra.deferred_payloads is runtime.deferred_payloads + assert runtime.infra.logger is runtime.logger + assert runtime.infra.rest_transport is runtime.rest_transport + assert runtime.infra.graphql_transport is runtime.graphql_transport + assert runtime.infra.artifact_download_transport is runtime.artifact_download_transport + assert runtime.infra.touch_tracker is runtime.touch_tracker + assert runtime.domain.state_store is runtime.state_store + assert runtime.domain.github is runtime.github + assert runtime.domain.locks is runtime.locks + assert runtime.domain.handlers is runtime.handlers + assert runtime.domain.workflow is runtime.workflow + + +def test_fake_runtime_exposes_compatibility_groups_for_thin_delegation(monkeypatch): + runtime = FakeReviewerBotRuntime(monkeypatch) + + assert hasattr(runtime.compat, "github") + assert hasattr(runtime.compat, "review") + assert hasattr(runtime.compat, "state_lock") + assert hasattr(runtime.compat, "automation") + + +def test_k1b_fake_runtime_contract_still_exposes_members_needed_by_frozen_reconcile_seams(monkeypatch): + runtime = FakeReviewerBotRuntime(monkeypatch) + + assert hasattr(runtime, "assert_lock_held") + assert hasattr(runtime, "load_deferred_payload") + assert hasattr(runtime, "collect_touched_item") + assert hasattr(runtime, "drain_touched_items") + assert hasattr(runtime, "github_api_request") + assert hasattr(runtime, "github_api") + assert hasattr(runtime.adapters.review_state, "maybe_record_head_observation_repair") + assert hasattr(runtime.github, "get_pull_request_reviews") + assert hasattr(runtime.github, "get_user_permission_status") + assert hasattr(runtime, "parse_iso8601_timestamp") + assert hasattr(runtime, "satisfy_mandatory_approver_requirement") + assert hasattr(runtime, "reconcile_workflow_runtime") is False + assert hasattr(runtime, "reconcile_rectify_runtime") is False + assert isinstance(ReconcileWorkflowRuntimeContext, type) + assert isinstance(ReconcileRectifyRuntimeContext, type) + + +def test_k1c_fake_runtime_satisfies_frozen_workflow_reconcile_protocol(monkeypatch): + runtime = FakeReviewerBotRuntime(monkeypatch) + + assert isinstance(runtime, ReconcileWorkflowRuntimeContext) + + +def test_k1d_fake_runtime_contract_keeps_rectify_read_helpers_explicit_without_new_mega_surface(monkeypatch): + runtime = FakeReviewerBotRuntime(monkeypatch) + + assert hasattr(runtime, "github_api_request") + assert hasattr(runtime, "github_api") + assert hasattr(runtime, "parse_iso8601_timestamp") + assert hasattr(runtime.github, "get_pull_request_reviews") + assert hasattr(runtime.github, "get_user_permission_status") + assert hasattr(runtime.adapters.review_state, "maybe_record_head_observation_repair") + assert hasattr(runtime, "reconcile_rectify_runtime") is False + + +def test_k1e_fake_runtime_contract_exposes_retained_approval_support_for_rectify_only(monkeypatch): + runtime = FakeReviewerBotRuntime(monkeypatch) + + assert hasattr(runtime, "satisfy_mandatory_approver_requirement") + assert hasattr(runtime, "reconcile_workflow_runtime") is False + assert hasattr(runtime, "reconcile_rectify_runtime") is False + + +def test_k1f_fake_runtime_satisfies_finalized_rectify_runtime_protocol(monkeypatch): + runtime = FakeReviewerBotRuntime(monkeypatch) + + assert isinstance(runtime, ReconcileRectifyRuntimeContext) + + +def test_k2_fake_runtime_satisfies_narrow_comment_runtime_protocols(monkeypatch): + runtime = FakeReviewerBotRuntime(monkeypatch) + + assert isinstance(runtime, CommentApplicationRuntimeContext) + assert isinstance(runtime, CommentRoutingRuntimeContext) + + +def _load_runtime_surface_inventory() -> dict: + return json.loads( + Path("tests/fixtures/equivalence/runtime_surface/triple_inventory.json").read_text( + encoding="utf-8" + ) + ) + + +def test_f2a_runtime_surface_inventory_matches_fake_runtime_branch_examples(): + inventory = _load_runtime_surface_inventory() + capabilities = {entry["capability"]: entry for entry in inventory["capability_triples"]} + + assert capabilities["comment-event dispatch"]["fake_runtime_branch"].endswith("handle_comment_event") + assert capabilities["privileged accept-no-fls-changes execution"]["fake_runtime_branch"].endswith( + "handle_accept_no_fls_changes_command" + ) + assert capabilities["mandatory approver satisfaction"]["fake_runtime_branch"].endswith( + "satisfy_mandatory_approver_requirement" + ) + assert "refresh reviewer review from live preferred review" not in capabilities + assert "repair missing reviewer review state" not in capabilities + + +def test_f2b_no_migration_required_runtime_surface_triples_are_recorded(): + inventory = _load_runtime_surface_inventory() + + migration_required = [ + entry for entry in inventory["capability_triples"] if entry["classification"] == "migration-required compatibility" + ] + + assert migration_required == [] + + +def test_fake_runtime_default_handlers_are_built_from_focused_fake_service_helper(monkeypatch): + runtime = FakeReviewerBotRuntime(monkeypatch) + + expected = build_default_handler_map(runtime) + + assert set(expected) == HandlerStub.ALLOWED + assert "handle_workflow_run_event" not in expected + assert "handle_pull_request_review_event" not in expected + assert hasattr(runtime, "handle_workflow_run_event") is False diff --git a/tests/contract/reviewer_bot/test_observer_payload_drift.py b/tests/contract/reviewer_bot/test_observer_payload_drift.py new file mode 100644 index 000000000..4539f25a5 --- /dev/null +++ b/tests/contract/reviewer_bot/test_observer_payload_drift.py @@ -0,0 +1,112 @@ +import json +from pathlib import Path + +import pytest + +pytestmark = pytest.mark.contract + +from scripts.reviewer_bot_lib import reconcile_payloads + + +def _load_contract_matrix() -> dict: + return json.loads( + Path("tests/fixtures/workflow_contracts/observer_payload_contract_matrix.json").read_text( + encoding="utf-8" + ) + ) + + +def _load_fixture(relative_path: str) -> dict: + return json.loads(Path(relative_path).read_text(encoding="utf-8")) + + +@pytest.mark.parametrize( + ("fixture_path", "expected_event_name", "expected_event_action"), + [ + ("tests/fixtures/observer_payloads/workflow_pr_comment_deferred.json", "issue_comment", "created"), + ( + "tests/fixtures/observer_payloads/workflow_pr_review_submitted_deferred.json", + "pull_request_review", + "submitted", + ), + ( + "tests/fixtures/observer_payloads/workflow_pr_review_dismissed_deferred.json", + "pull_request_review", + "dismissed", + ), + ( + "tests/fixtures/observer_payloads/workflow_pr_review_comment_deferred.json", + "pull_request_review_comment", + "created", + ), + ], +) +def test_workflow_emitted_payload_fixtures_match_parseable_identity_contract( + fixture_path, expected_event_name, expected_event_action +): + fixture = _load_fixture(fixture_path) + payload = fixture["payload"] + metadata = fixture["fixture_metadata"] + parsed = reconcile_payloads.parse_deferred_context_payload(payload) + + assert metadata["contract_class"] == "workflow_emitted_payload" + assert metadata["contract_source"] == "workflow YAML" + assert payload["source_event_name"] == expected_event_name + assert payload["source_event_action"] == expected_event_action + assert parsed.identity.source_event_name == expected_event_name + assert parsed.identity.source_event_action == expected_event_action + assert parsed.identity.source_run_id == payload["source_run_id"] + assert parsed.identity.source_run_attempt == payload["source_run_attempt"] + assert parsed.raw_payload == payload + + +@pytest.mark.parametrize( + ("fixture_path",), + [ + ("tests/fixtures/observer_payloads/helper_pr_comment_trusted_direct_noop.json",), + ("tests/fixtures/observer_payloads/helper_pr_comment_automation_noop.json",), + ], +) +def test_python_helper_output_fixtures_remain_parseable_migration_examples(fixture_path): + fixture = _load_fixture(fixture_path) + payload = fixture["payload"] + metadata = fixture["fixture_metadata"] + parsed = reconcile_payloads.parse_deferred_context_payload(payload) + + assert metadata["contract_class"] == "python_helper_output" + assert metadata["contract_source"] == "production Python helper" + assert payload["kind"] == "observer_noop" + assert parsed.reason + assert parsed.raw_payload == payload + + +def test_observer_contract_matrix_uses_final_top_level_sections_and_row_schema(): + matrix = _load_contract_matrix() + + assert set(matrix) == {"behavior_questions", "payload_contracts"} + assert all( + set(item) == {"question_id", "owner", "source_event_name", "source_event_action", "payload_kind"} + for item in matrix["behavior_questions"] + ) + assert all( + set(item) + == {"payload_kind", "owner", "schema_version", "carried_edge_fields", "debug_only_fields"} + for item in matrix["payload_contracts"] + ) + assert {item["payload_kind"] for item in matrix["payload_contracts"]} == { + "deferred_comment", + "deferred_review_submitted", + "deferred_review_dismissed", + "deferred_review_comment", + } + assert "observer_noop" not in {item["payload_kind"] for item in matrix["payload_contracts"]} + + +def test_observer_contract_matrix_rows_track_only_retained_payload_contracts(): + matrix = _load_contract_matrix() + + assert matrix["behavior_questions"] + assert matrix["payload_contracts"] + assert all(item["schema_version"] == 3 for item in matrix["payload_contracts"]) + assert all(item["owner"].startswith(".github/workflows/") for item in matrix["behavior_questions"]) + assert all(item["owner"].startswith(".github/workflows/") for item in matrix["payload_contracts"]) diff --git a/tests/contract/reviewer_bot/test_privileged_commands_workflow.py b/tests/contract/reviewer_bot/test_privileged_commands_workflow.py new file mode 100644 index 000000000..b72d2eea0 --- /dev/null +++ b/tests/contract/reviewer_bot/test_privileged_commands_workflow.py @@ -0,0 +1,27 @@ +from pathlib import Path + +import pytest + +pytestmark = pytest.mark.contract + +def test_privileged_commands_workflow_executes_source_entrypoint(): + workflow_text = Path(".github/workflows/reviewer-bot-privileged-commands.yml").read_text( + encoding="utf-8" + ) + assert "Fetch trusted bot source tarball" in workflow_text + assert 'python "$BOT_SRC_ROOT/scripts/reviewer_bot.py"' in workflow_text + + +def test_privileged_commands_workflow_stays_isolated_from_observer_and_reconcile_contracts(): + workflow_text = Path(".github/workflows/reviewer-bot-privileged-commands.yml").read_text( + encoding="utf-8" + ) + + assert "name: Reviewer Bot Privileged Commands" in workflow_text + assert "workflow_dispatch:" in workflow_text + assert "source_event_key:" in workflow_text + assert "reviewer-bot-pr-comment-observer.yml" not in workflow_text + assert "reviewer-bot-pr-review-submitted-observer.yml" not in workflow_text + assert "reviewer-bot-pr-review-dismissed-observer.yml" not in workflow_text + assert "reviewer-bot-pr-review-comment-observer.yml" not in workflow_text + assert "reviewer-bot-reconcile.yml" not in workflow_text diff --git a/tests/contract/reviewer_bot/test_review_state_contract.py b/tests/contract/reviewer_bot/test_review_state_contract.py new file mode 100644 index 000000000..4446ca781 --- /dev/null +++ b/tests/contract/reviewer_bot/test_review_state_contract.py @@ -0,0 +1,370 @@ +from pathlib import Path + +import pytest + +from scripts.reviewer_bot_core import state_adapters +from scripts.reviewer_bot_lib import review_state, reviews +from tests.fixtures.reviewer_bot import make_state + +pytestmark = pytest.mark.contract + + +ROOT = Path(__file__).resolve().parents[3] + +DELETED_REVIEW_MUTATION_SYMBOLS = [ + "_ensure_channel_map", + "_ensure_dict", + "_ensure_review_entry", + "_reset_cycle_state", + "clear_transition_timers", + "_record_reviewer_activity", + "_record_transition_notice_sent", + "_set_current_reviewer", + "_semantic_key_seen", + "_accept_channel_event", + "_update_reviewer_activity", + "_mark_review_complete", + "_get_current_cycle_boundary", + "_legacy_accept_reviewer_review_from_live_review", + "_legacy_refresh_reviewer_review_from_live_preferred_review", + "_legacy_repair_missing_reviewer_review_state", +] + + +def _read(relative_path: str) -> str: + return (ROOT / relative_path).read_text(encoding="utf-8") + + +def _read_state_contract_table() -> str: + return _read("tests/fixtures/state_contracts/review_state_contract_table.md") + + +def _read_review_state_inventory() -> str: + return _read("tests/fixtures/equivalence/review_state/api_inventory.md") + + +def test_ensure_review_entry_initializes_tracked_review_shape(): + state = make_state() + + review = review_state.ensure_review_entry(state, 42, create=True) + + assert review is not None + assert review["current_reviewer"] is None + assert review["reviewer_comment"]["accepted"] is None + assert review["sidecars"]["repair_markers"] == { + "review_repair": None, + "head_observation_repair": None, + "status_label_projection": None, + } + assert review["sidecars"]["pending_privileged_commands"] == {} + assert review["sidecars"]["deferred_gaps"] == {} + assert review["sidecars"]["observer_discovery_watermarks"] == {} + assert review["sidecars"]["reconciled_source_events"] == {} + assert review["current_cycle_write_approval"] == {} + + +def test_state_contract_table_fixture_lists_frozen_sections_and_field_classifications(): + table = _read_state_contract_table() + + for heading in [ + "Top-Level Persisted Keys", + "Per-Review-Entry Keys", + "Per-Channel Keys", + "Lazy-Upgrade Cases", + ]: + assert heading in table + + for line in [ + "- active_reviews: required", + "- status_projection_epoch: lazily materialized", + "- sidecars: lazily materialized", + "- repair_markers: nested under sidecars with fixed owner keys", + "- pending_privileged_commands: nested under sidecars", + "- deferred_gaps: nested under sidecars", + "- current_cycle_write_approval: lazily materialized", + "- observer_discovery_watermarks: nested under sidecars", + "- reconciled_source_events: nested under sidecars as a map; tolerated legacy list shape", + "- accepted: lazily materialized", + "- seen_keys: tolerated legacy shape", + "- non-list skipped: tolerated legacy shape", + ]: + assert line in table + + +def test_ensure_review_entry_lazily_upgrades_sparse_legacy_review_entries(): + state = make_state() + state["active_reviews"]["42"] = ["alice", "bob"] + + review = review_state.ensure_review_entry(state, 42, create=False) + + assert review is state["active_reviews"]["42"] + assert review["skipped"] == ["alice", "bob"] + assert review["reviewer_comment"] == {"accepted": None, "seen_keys": []} + assert review["sidecars"]["repair_markers"] == { + "review_repair": None, + "head_observation_repair": None, + "status_label_projection": None, + } + assert review["sidecars"]["pending_privileged_commands"] == {} + assert review["sidecars"]["reconciled_source_events"] == {} + + +def test_ensure_review_entry_repairs_missing_nested_maps_and_legacy_list_fields(): + state = make_state() + state["active_reviews"]["42"] = { + "skipped": "legacy", + "reviewer_comment": {"accepted": None, "seen_keys": "bad"}, + "reconciled_source_events": "bad", + } + + review = review_state.ensure_review_entry(state, 42, create=False) + + assert review is not None + assert review["skipped"] == [] + assert review["reviewer_comment"] == {"accepted": None, "seen_keys": []} + assert review["contributor_revision"] == {"accepted": None, "seen_keys": []} + assert review["sidecars"]["deferred_gaps"] == {} + assert review["sidecars"]["observer_discovery_watermarks"] == {} + assert review["sidecars"]["pending_privileged_commands"] == {} + assert review["sidecars"]["repair_markers"] == { + "review_repair": None, + "head_observation_repair": None, + "status_label_projection": None, + } + assert review["current_cycle_completion"] == {} + assert review["current_cycle_write_approval"] == {} + assert review["sidecars"]["reconciled_source_events"] == {} + + +def test_core_state_adapter_matches_current_sparse_review_entry_upgrade_contract(): + state = make_state() + state["active_reviews"]["42"] = ["alice", "bob"] + + adapted = state_adapters.review_entry_from_persisted(state["active_reviews"]["42"]) + upgraded = review_state.ensure_review_entry(state, 42, create=False) + + assert adapted is not None + assert state_adapters.review_entry_to_persisted(adapted) == { + key: upgraded[key] + for key in state_adapters.review_entry_to_persisted(adapted) + } + + +def test_review_state_mutation_inventory_freezes_overlap_classification_and_live_read_scope(): + inventory = _read_review_state_inventory() + + for heading in [ + "Local-State-Only Mutation APIs", + "Live-Read-Assisted Mutation APIs", + "Read-Only Helpers", + ]: + assert heading in inventory + + for line in [ + "- ensure_review_entry: local-state-only mutation", + "- accept_channel_event: local-state-only mutation", + "- record_reviewer_activity: local-state-only mutation", + "- record_transition_notice_sent: local-state-only mutation", + "- set_current_reviewer: local-state-only mutation", + "- update_reviewer_activity: local-state-only mutation", + "- mark_review_complete: local-state-only mutation", + "- get_current_cycle_boundary: read-only helper", + "- clear_transition_timers: local-state-only mutation", + "- semantic_key_seen: read-only helper", + "- accept_reviewer_review_from_live_review: live-read-assisted mutation; C1c in-scope", + "- refresh_reviewer_review_from_live_preferred_review: live-read-assisted mutation; C1c in-scope", + "- repair_missing_reviewer_review_state: live-read-assisted mutation; C1c in-scope", + "- list_open_tracked_review_items: read-only helper", + ]: + assert line in inventory + + +def test_accept_channel_event_deduplicates_semantic_keys(): + state = make_state() + review = review_state.ensure_review_entry(state, 42, create=True) + assert review is not None + + first = review_state.accept_channel_event( + review, + "reviewer_comment", + semantic_key="issue_comment:100", + timestamp="2026-03-17T10:00:00Z", + actor="alice", + ) + second = review_state.accept_channel_event( + review, + "reviewer_comment", + semantic_key="issue_comment:100", + timestamp="2026-03-17T10:00:00Z", + actor="alice", + ) + + assert first is True + assert second is False + + +def test_mark_review_complete_updates_completion_fields(): + state = make_state() + review_state.ensure_review_entry(state, 42, create=True) + + changed = review_state.mark_review_complete(state, 42, "alice", "unit-test") + + assert changed is True + review = state["active_reviews"]["42"] + assert review["review_completed_by"] == "alice" + assert review["review_completion_source"] == "unit-test" + assert review["current_cycle_completion"]["completed"] is True + + +def test_list_open_tracked_review_items_returns_only_assigned_entries(): + state = make_state() + review_state.ensure_review_entry(state, 42, create=True) + review_state.ensure_review_entry(state, 99, create=True) + state["active_reviews"]["42"]["current_reviewer"] = "alice" + + assert review_state.list_open_tracked_review_items(state) == [42] + + +def test_review_state_module_exposes_named_mutation_surface(): + for name in [ + "ensure_review_entry", + "accept_channel_event", + "record_reviewer_activity", + "record_transition_notice_sent", + "set_current_reviewer", + "update_reviewer_activity", + "mark_review_complete", + "get_current_cycle_boundary", + ]: + assert hasattr(review_state, name) + + +def test_review_state_local_mutation_surface_delegates_to_core_owner(): + review_state_text = _read("scripts/reviewer_bot_lib/review_state.py") + + assert "from scripts.reviewer_bot_core import (" in review_state_text + assert "live_review_support," in review_state_text + assert "review_state_live_repair," in review_state_text + assert "review_state_machine," in review_state_text + assert "return review_state_machine.ensure_review_entry(state, issue_number, create=create)" in review_state_text + assert "return review_state_machine.accept_channel_event(" in review_state_text + assert "return review_state_machine.mark_review_complete(state, issue_number, reviewer, source, completed_at=_now_iso())" in review_state_text + + +def test_production_modules_do_not_import_mutable_review_state_api_from_reviews_module(): + review_state_text = _read("scripts/reviewer_bot_lib/review_state.py") + runtime_text = _read("scripts/reviewer_bot_lib/runtime.py") + bootstrap_text = _read("scripts/reviewer_bot_lib/bootstrap_runtime.py") + commands_text = _read("scripts/reviewer_bot_lib/commands.py") + reconcile_text = _read("scripts/reviewer_bot_lib/reconcile.py") + reviews_text = _read("scripts/reviewer_bot_lib/reviews.py") + + for name in [ + "ensure_review_entry", + "accept_channel_event", + "record_reviewer_activity", + "record_transition_notice_sent", + "set_current_reviewer", + "update_reviewer_activity", + "mark_review_complete", + "get_current_cycle_boundary", + ]: + assert f"def {name}(" in review_state_text + assert f"from .reviews import {name}" not in runtime_text + assert f"reviews.{name}(" not in runtime_text + assert f"reviews.{name}(" not in bootstrap_text + assert f"bot.{name}(" not in commands_text + assert f"bot.{name}(" not in reconcile_text + assert f"bot.{name}(" not in reviews_text + + +def test_production_modules_use_review_state_as_live_repair_bridge_home(): + review_state_text = _read("scripts/reviewer_bot_lib/review_state.py") + maintenance_text = _read("scripts/reviewer_bot_lib/maintenance.py") + reconcile_text = _read("scripts/reviewer_bot_lib/reconcile.py") + sweeper_text = _read("scripts/reviewer_bot_lib/sweeper.py") + reviews_text = _read("scripts/reviewer_bot_lib/reviews.py") + + for name in [ + "accept_reviewer_review_from_live_review", + "refresh_reviewer_review_from_live_preferred_review", + "repair_missing_reviewer_review_state", + ]: + assert f"def {name}(" in review_state_text + assert f"def {name}(" not in reviews_text + + assert "repair_missing_reviewer_review_state = maintenance_schedule.repair_missing_reviewer_review_state" in maintenance_text + assert "refresh_reviewer_review_from_live_preferred_review," in reconcile_text + assert "accept_reviewer_review_from_live_review," in sweeper_text + assert "refresh_reviewer_review_from_live_preferred_review," in sweeper_text + assert "from .reviews import refresh_reviewer_review_from_live_preferred_review" not in reconcile_text + assert "from .reviews import accept_reviewer_review_from_live_review" not in sweeper_text + assert "from .reviews import refresh_reviewer_review_from_live_preferred_review" not in sweeper_text + + +def test_runtime_and_bootstrap_forwarders_are_explicit_adapter_compatibility_surface_only(): + runtime_text = _read("scripts/reviewer_bot_lib/runtime.py") + bootstrap_text = _read("scripts/reviewer_bot_lib/bootstrap_runtime.py") + + assert "Adapter-only mutable review-state compatibility surface." in runtime_text + assert "Adapter-only mutable review-state compatibility surface." in bootstrap_text + assert "record_transition_notice_sent" not in runtime_text + assert "accept_channel_event" not in runtime_text + assert "get_current_cycle_boundary" not in runtime_text + assert "record_transition_notice_sent" not in bootstrap_text + assert "accept_channel_event" not in bootstrap_text + assert "get_current_cycle_boundary" not in bootstrap_text + + +def test_tests_do_not_rely_on_runtime_mutable_review_state_forwarders_outside_contract_surface(): + for path in ROOT.glob("tests/**/*.py"): + if path.name in {"test_fake_runtime_contract.py", "test_adapter_contract.py", "test_review_state_contract.py"}: + continue + text = path.read_text(encoding="utf-8") + assert "runtime.ensure_review_entry" not in text + assert "runtime.set_current_reviewer" not in text + assert "runtime.update_reviewer_activity" not in text + assert "runtime.mark_review_complete" not in text + + +def test_reviews_module_no_longer_exposes_public_mutation_helpers(): + for name in [ + "ensure_review_entry", + "accept_channel_event", + "record_reviewer_activity", + "record_transition_notice_sent", + "set_current_reviewer", + "update_reviewer_activity", + "mark_review_complete", + "get_current_cycle_boundary", + ]: + assert hasattr(reviews, name) is False + + +def test_c1d_deletion_manifest_matches_deleted_duplicate_review_mutation_logic(): + reviews_text = _read("scripts/reviewer_bot_lib/reviews.py") + + for symbol in DELETED_REVIEW_MUTATION_SYMBOLS: + assert f"def {symbol}(" not in reviews_text + + +def test_c1d_repo_wide_importer_and_caller_inventory_for_deleted_review_mutation_logic_is_zero(): + importer_inventory = [] + caller_inventory = [] + + for path in ROOT.glob("**/*.py"): + relative_path = path.relative_to(ROOT).as_posix() + if relative_path == "tests/contract/reviewer_bot/test_review_state_contract.py": + continue + text = path.read_text(encoding="utf-8") + for symbol in DELETED_REVIEW_MUTATION_SYMBOLS: + if ( + f"from .reviews import {symbol}" in text + or f"from scripts.reviewer_bot_lib.reviews import {symbol}" in text + ): + importer_inventory.append((relative_path, symbol)) + if f"reviews.{symbol}(" in text: + caller_inventory.append((relative_path, symbol)) + + assert importer_inventory == [] + assert caller_inventory == [] diff --git a/tests/contract/reviewer_bot/test_reviewer_board_workflow_contracts.py b/tests/contract/reviewer_bot/test_reviewer_board_workflow_contracts.py new file mode 100644 index 000000000..f06f795bf --- /dev/null +++ b/tests/contract/reviewer_bot/test_reviewer_board_workflow_contracts.py @@ -0,0 +1,30 @@ +from pathlib import Path + +import pytest + +pytestmark = pytest.mark.contract + +import yaml + + +def test_sweeper_repair_workflow_exposes_reviewer_board_preview_dispatch(): + data = yaml.safe_load(Path(".github/workflows/reviewer-bot-sweeper-repair.yml").read_text(encoding="utf-8")) + on_block = data.get("on", data.get(True)) + workflow_dispatch = on_block["workflow_dispatch"] + action_input = workflow_dispatch["inputs"]["action"] + assert "preview-reviewer-board" in action_input["options"] + issue_number_input = workflow_dispatch["inputs"]["issue_number"] + assert issue_number_input["required"] is False + assert issue_number_input["type"] == "string" + +def test_sweeper_repair_workflow_scopes_reviewer_board_env_to_preview_only(): + workflow_text = Path(".github/workflows/reviewer-bot-sweeper-repair.yml").read_text(encoding="utf-8") + assert "ISSUE_NUMBER: ${{ github.event.inputs.issue_number }}" in workflow_text + assert ( + "REVIEWER_BOARD_ENABLED: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.action == 'preview-reviewer-board' && 'true' || 'false' }}" + in workflow_text + ) + assert ( + "REVIEWER_BOARD_TOKEN: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.action == 'preview-reviewer-board' && secrets.REVIEWER_BOARD_TOKEN || '' }}" + in workflow_text + ) diff --git a/tests/contract/reviewer_bot/test_runtime_protocols.py b/tests/contract/reviewer_bot/test_runtime_protocols.py new file mode 100644 index 000000000..bd2d41f4b --- /dev/null +++ b/tests/contract/reviewer_bot/test_runtime_protocols.py @@ -0,0 +1,83 @@ +import json +from pathlib import Path + +import pytest + +pytestmark = pytest.mark.contract + +from scripts import reviewer_bot +from scripts.reviewer_bot_lib.runtime import ReviewerBotRuntime +from scripts.reviewer_bot_lib.runtime_protocols import ( + AppEventContextRuntime, + AppExecutionRuntime, + EventInputsContext, + ProjectBoardMetadataContext, + ProjectBoardProjectionContext, + ReconcileRectifyRuntimeContext, + ReconcileWorkflowRuntimeContext, +) +from tests.fixtures.fake_runtime import FakeReviewerBotRuntime + + +def _assert_core_runtime_surface(runtime) -> None: + assert hasattr(runtime, "get_config_value") + assert hasattr(runtime, "infra") + assert hasattr(runtime, "domain") + assert runtime.infra.config is runtime.config + assert runtime.infra.logger is runtime.logger + assert runtime.infra.rest_transport is runtime.rest_transport + assert runtime.infra.graphql_transport is runtime.graphql_transport + assert runtime.infra.artifact_download_transport is runtime.artifact_download_transport + assert runtime.domain.state_store is runtime.state_store + assert runtime.domain.github is runtime.github + assert runtime.domain.locks is runtime.locks + assert runtime.domain.handlers is runtime.handlers + + +def test_runtime_bot_returns_concrete_runtime_object(): + runtime = reviewer_bot._runtime_bot() + + assert isinstance(runtime, ReviewerBotRuntime) + assert runtime.EVENT_INTENT_MUTATING == reviewer_bot._runtime_bot().EVENT_INTENT_MUTATING + + +def test_runtime_object_satisfies_runtime_context_protocols(): + runtime = reviewer_bot._runtime_bot() + + assert isinstance(runtime, AppEventContextRuntime) + assert isinstance(runtime, AppExecutionRuntime) + assert isinstance(runtime, EventInputsContext) + assert isinstance(runtime, ProjectBoardMetadataContext) + assert isinstance(runtime, ProjectBoardProjectionContext) + assert isinstance(runtime, ReconcileWorkflowRuntimeContext) + assert isinstance(runtime, ReconcileRectifyRuntimeContext) + _assert_core_runtime_surface(runtime) + + +def test_fake_runtime_satisfies_app_execution_runtime_protocol(monkeypatch): + runtime = FakeReviewerBotRuntime(monkeypatch) + + assert isinstance(runtime, AppEventContextRuntime) + assert isinstance(runtime, AppExecutionRuntime) + assert isinstance(runtime, EventInputsContext) + assert isinstance(runtime, ReconcileWorkflowRuntimeContext) + assert isinstance(runtime, ReconcileRectifyRuntimeContext) + _assert_core_runtime_surface(runtime) + +def test_o3_runtime_deletion_manifest_forbids_dead_workflow_run_handler_compatibility_surface(): + manifest = json.loads( + Path("tests/fixtures/equivalence/runtime_surface/deletion_manifest.json").read_text(encoding="utf-8") + ) + + assert manifest["harness_id"] == "O3 runtime compatibility deletion manifest" + assert manifest["artifact_classification"] == "active migration proof fixture" + recorded_paths = {entry["path"] for entry in manifest["paths"]} + assert recorded_paths >= { + "scripts/reviewer_bot_lib/context.py", + "scripts/reviewer_bot_lib/bootstrap_runtime.py", + "tests/fixtures/fake_runtime.py", + "tests/fixtures/focused_fake_services.py", + } + assert manifest["proof_artifacts"] + assert all(entry["classification"] == "rewritten final proof" for entry in manifest["proof_artifacts"]) + assert all("handle_workflow_run_event" in path for path in manifest["deleted_paths"]) diff --git a/tests/contract/reviewer_bot/test_support_layer_ownership.py b/tests/contract/reviewer_bot/test_support_layer_ownership.py new file mode 100644 index 000000000..24b2b67e0 --- /dev/null +++ b/tests/contract/reviewer_bot/test_support_layer_ownership.py @@ -0,0 +1,168 @@ +import json +from pathlib import Path + +import pytest + +from tests.fixtures import http_responses, reviewer_bot_fakes, reviewer_bot_recorders +from tests.fixtures.focused_fake_services import ( + ArtifactDownloadTransportStub, + ConfigBag, + DeferredPayloadStore, + GitHubStub, + GraphQLTransportStub, + HandlerStub, + LockStub, + OutputCapture, + RestTransportStub, + StateStoreStub, + TouchTrackerStub, +) +from tests.fixtures.reviewer_bot_fakes import RouteGitHubApi, github_result + +pytestmark = pytest.mark.contract + + +def _load_support_layer_inventory() -> dict: + return json.loads( + Path("tests/fixtures/equivalence/support_layer/symbol_inventory.json").read_text( + encoding="utf-8" + ) + ) + + +def test_transport_fake_authority_is_owned_by_reviewer_bot_fakes_module(): + assert reviewer_bot_fakes.RouteGitHubApi is RouteGitHubApi + assert reviewer_bot_fakes.github_result is github_result + + +def test_low_level_http_response_helper_has_dedicated_home(): + assert http_responses.FakeGitHubResponse is not None + assert http_responses.__all__ == ["FakeGitHubResponse"] + + +def test_reviewer_bot_recorders_module_remains_available_for_shared_recorders(): + assert reviewer_bot_recorders is not None + + +def test_n2_focused_fake_services_provide_behavioral_authority_for_small_fixture_services(monkeypatch): + config = ConfigBag(monkeypatch) + config.set("HELLO", "world") + output = OutputCapture() + output.write("changed", "true") + deferred = DeferredPayloadStore() + deferred.set_payload({"event": "workflow_run"}) + state_store = StateStoreStub() + lock = LockStub() + github = GitHubStub() + rest = RestTransportStub(github) + graphql = GraphQLTransportStub() + artifact = ArtifactDownloadTransportStub() + handlers = HandlerStub({"handle_comment_event": lambda state: False}) + touch = TouchTrackerStub() + touch.collect(42) + + assert config.get("HELLO") == "world" + assert output.writes == [("changed", "true")] + assert deferred.load() == {"event": "workflow_run"} + assert state_store.load_state() == {"active_reviews": {}} + assert lock.calls == [] + assert github._github is None + assert rest.calls == [] + assert graphql.calls == [] + assert artifact.calls == [] + assert callable(handlers.handle_comment_event) + assert touch.drain() == [42] + + +def test_support_layer_contract_focuses_on_active_authority_boundaries_only(): + active_authorities = { + RouteGitHubApi.__module__, + github_result.__module__, + http_responses.FakeGitHubResponse.__module__, + ConfigBag.__module__, + reviewer_bot_recorders.__name__, + } + + assert active_authorities == { + "tests.fixtures.reviewer_bot_fakes", + "tests.fixtures.http_responses", + "tests.fixtures.focused_fake_services", + "tests.fixtures.reviewer_bot_recorders", + } + + +def test_f1a_support_layer_inventory_fixture_records_candidate_classifications_and_importers(): + inventory = _load_support_layer_inventory() + + assert inventory["harness_id"] == "F1a support-layer symbol inventory" + assert inventory["artifact_classification"] == "active migration proof fixture" + assert inventory["proof_artifacts"] == [ + { + "path": "tests/contract/reviewer_bot/test_support_layer_ownership.py", + "classification": "active migration proof", + } + ] + symbols = {entry["symbol"]: entry for entry in inventory["symbols"]} + + assert symbols["scripts.reviewer_bot_lib.reviews.parse_github_timestamp"]["classification"] == "retained compatibility forwarder" + assert symbols["scripts.reviewer_bot_core.live_review_support.projection_failure_result"]["classification"] == "retained support owner" + assert symbols["scripts.reviewer_bot_core.live_review_support.read_pull_request_result"]["classification"] == "retained support owner" + assert symbols["scripts.reviewer_bot_core.live_review_support.read_pull_request_reviews_result"]["classification"] == "retained support owner" + assert symbols["scripts.reviewer_bot_core.live_review_support.permission_status"]["classification"] == "retained support owner" + assert symbols["scripts.reviewer_bot_core.live_review_support.parse_github_timestamp"]["classification"] == "retained support owner" + assert symbols["scripts.reviewer_bot_core.live_review_support.normalize_reviews_with_parsed_timestamps"]["classification"] == "retained support owner" + assert symbols["scripts.reviewer_bot_core.live_review_support.filter_current_head_reviews_for_cycle"]["classification"] == "retained support owner" + assert symbols["scripts.reviewer_bot_core.live_review_support.collect_permission_statuses"]["classification"] == "retained support owner" + assert symbols["scripts.reviewer_bot_lib.deferred_gap_bookkeeping._clear_source_event_key"]["classification"] == "retained support owner" + assert symbols["scripts.reviewer_bot_lib.deferred_gap_bookkeeping._mark_reconciled_source_event"]["classification"] == "retained support owner" + assert symbols["scripts.reviewer_bot_lib.deferred_gap_bookkeeping._update_deferred_gap"]["classification"] == "retained support owner" + assert symbols["scripts.reviewer_bot_lib.deferred_gap_bookkeeping._was_reconciled_source_event"]["classification"] == "retained support owner" + assert symbols["scripts.reviewer_bot_lib.reviews.list_open_items_with_status_labels"]["classification"] == "retained support behavior" + assert symbols["scripts.reviewer_bot_lib.reviews.rebuild_pr_approval_state"]["classification"] == "retained final surface" + assert symbols["scripts.reviewer_bot_lib.sweeper.sweep_deferred_gaps"]["classification"] == "retained final surface" + + +def test_f1a_support_layer_inventory_records_transitional_importer_examples_without_exact_list_policing(): + inventory = _load_support_layer_inventory() + symbols = {entry["symbol"]: entry for entry in inventory["symbols"]} + + for symbol_name in [ + "scripts.reviewer_bot_core.live_review_support.projection_failure_result", + "scripts.reviewer_bot_core.live_review_support.read_pull_request_result", + "scripts.reviewer_bot_core.live_review_support.read_pull_request_reviews_result", + "scripts.reviewer_bot_core.live_review_support.permission_status", + "scripts.reviewer_bot_core.live_review_support.parse_github_timestamp", + "scripts.reviewer_bot_core.live_review_support.normalize_reviews_with_parsed_timestamps", + "scripts.reviewer_bot_core.live_review_support.filter_current_head_reviews_for_cycle", + "scripts.reviewer_bot_core.live_review_support.collect_permission_statuses", + "scripts.reviewer_bot_lib.deferred_gap_bookkeeping._clear_source_event_key", + "scripts.reviewer_bot_lib.deferred_gap_bookkeeping._mark_reconciled_source_event", + "scripts.reviewer_bot_lib.deferred_gap_bookkeeping._update_deferred_gap", + "scripts.reviewer_bot_lib.deferred_gap_bookkeeping._was_reconciled_source_event", + "scripts.reviewer_bot_lib.reviews.list_open_items_with_status_labels", + "scripts.reviewer_bot_lib.reviews.rebuild_pr_approval_state", + "scripts.reviewer_bot_lib.reviews.rebuild_pr_approval_state_result", + "scripts.reviewer_bot_lib.sweeper.sweep_deferred_gaps", + ]: + production_importers = symbols[symbol_name]["production_importers"] + assert production_importers + assert all(importer.endswith(".py") for importer in production_importers) + assert symbols["scripts.reviewer_bot_lib.reviews.parse_github_timestamp"]["production_importers"] + + +def test_f1b_no_migration_required_production_importers_remain_for_deprecated_support_layer_paths(): + inventory = _load_support_layer_inventory() + + deletion_ready = [entry for entry in inventory["symbols"] if entry["classification"] == "zero-importer deletion candidate"] + + assert deletion_ready == [] + + +def test_f1c_deleted_legacy_support_layer_paths_are_explicitly_forbidden(): + reviews_text = Path("scripts/reviewer_bot_lib/reviews.py").read_text(encoding="utf-8") + + assert "def compute_pr_approval_state_result(" not in reviews_text + assert "def find_triage_approval_after(" not in reviews_text + assert "def build_reviewer_review_record_from_live_review(" not in reviews_text + assert "def get_preferred_current_reviewer_review_for_cycle(" not in reviews_text + assert "def _compare_records(" not in reviews_text diff --git a/tests/contract/reviewer_bot/test_workflow_artifact_contracts.py b/tests/contract/reviewer_bot/test_workflow_artifact_contracts.py new file mode 100644 index 000000000..bf244cac3 --- /dev/null +++ b/tests/contract/reviewer_bot/test_workflow_artifact_contracts.py @@ -0,0 +1,115 @@ +import json +from pathlib import Path +from types import SimpleNamespace + +import pytest + +pytestmark = pytest.mark.contract + +from scripts.reviewer_bot_lib import reconcile_payloads + + +def _load_fixture_payload(relative_path: str) -> dict: + data = json.loads(Path(relative_path).read_text(encoding="utf-8")) + return data["payload"] + + +@pytest.mark.parametrize( + ("workflow_path",), + [ + (".github/workflows/reviewer-bot-pr-comment-router.yml",), + (".github/workflows/reviewer-bot-pr-review-submitted-observer.yml",), + (".github/workflows/reviewer-bot-pr-review-dismissed-observer.yml",), + (".github/workflows/reviewer-bot-pr-review-comment-observer.yml",), + ], +) +def test_observer_workflow_files_upload_exactly_one_json_payload(workflow_path): + workflow = __import__("yaml").safe_load(Path(workflow_path).read_text(encoding="utf-8")) + job_name = "route-pr-comment" if workflow_path.endswith("reviewer-bot-pr-comment-router.yml") else "observer" + build_step = workflow["jobs"][job_name]["steps"][0] + upload_step = workflow["jobs"][job_name]["steps"][1] + + assert build_step["env"]["PAYLOAD_PATH"].endswith(".json") + assert upload_step["with"]["path"] == build_step["env"]["PAYLOAD_PATH"] + assert isinstance(upload_step["with"]["name"], str) and upload_step["with"]["name"] + + +@pytest.mark.parametrize( + ("fixture_path", "expected_event_name", "expected_event_action"), + [ + ("tests/fixtures/observer_payloads/workflow_pr_comment_deferred.json", "issue_comment", "created"), + ( + "tests/fixtures/observer_payloads/workflow_pr_review_submitted_deferred.json", + "pull_request_review", + "submitted", + ), + ( + "tests/fixtures/observer_payloads/workflow_pr_review_dismissed_deferred.json", + "pull_request_review", + "dismissed", + ), + ( + "tests/fixtures/observer_payloads/workflow_pr_review_comment_deferred.json", + "pull_request_review_comment", + "created", + ), + ], +) +def test_deferred_payload_fixtures_parse_identity_without_packaging_contracts( + fixture_path, expected_event_name, expected_event_action +): + payload = _load_fixture_payload(fixture_path) + parsed = reconcile_payloads.parse_deferred_context_payload(payload) + + assert parsed.identity.source_event_name == expected_event_name + assert parsed.identity.source_event_action == expected_event_action + assert parsed.identity.source_run_id == payload["source_run_id"] + assert parsed.identity.source_run_attempt == payload["source_run_attempt"] + assert parsed.raw_payload == payload + + +def test_deferred_comment_payload_parses_without_artifact_name_field(): + payload = _load_fixture_payload("tests/fixtures/observer_payloads/workflow_pr_comment_deferred.json") + payload.pop("source_artifact_name", None) + + parsed = reconcile_payloads.parse_deferred_context_payload(payload) + + assert parsed.identity.source_event_name == "issue_comment" + + +def test_validate_workflow_run_artifact_identity_rejects_run_attempt_mismatch(monkeypatch): + monkeypatch.setenv("WORKFLOW_RUN_TRIGGERING_ID", "1") + monkeypatch.setenv("WORKFLOW_RUN_TRIGGERING_ATTEMPT", "2") + monkeypatch.setenv("WORKFLOW_RUN_TRIGGERING_CONCLUSION", "success") + payload = { + "source_event_name": "issue_comment", + "source_event_action": "created", + "source_workflow_name": "Reviewer Bot PR Comment Observer", + "source_workflow_file": ".github/workflows/reviewer-bot-pr-comment-observer.yml", + "source_run_id": 1, + "source_run_attempt": 1, + } + + bot = SimpleNamespace(get_config_value=lambda name, default="": __import__("os").environ.get(name, default)) + + with pytest.raises(RuntimeError, match="run_attempt mismatch"): + reconcile_payloads.validate_workflow_run_artifact_identity(bot, payload) + + +def test_validate_workflow_run_artifact_identity_requires_successful_conclusion(monkeypatch): + monkeypatch.setenv("WORKFLOW_RUN_TRIGGERING_ID", "1") + monkeypatch.setenv("WORKFLOW_RUN_TRIGGERING_ATTEMPT", "1") + monkeypatch.setenv("WORKFLOW_RUN_TRIGGERING_CONCLUSION", "failure") + payload = { + "source_event_name": "issue_comment", + "source_event_action": "created", + "source_workflow_name": "Reviewer Bot PR Comment Observer", + "source_workflow_file": ".github/workflows/reviewer-bot-pr-comment-observer.yml", + "source_run_id": 1, + "source_run_attempt": 1, + } + + bot = SimpleNamespace(get_config_value=lambda name, default="": __import__("os").environ.get(name, default)) + + with pytest.raises(RuntimeError, match="did not conclude successfully"): + reconcile_payloads.validate_workflow_run_artifact_identity(bot, payload) diff --git a/tests/contract/reviewer_bot/test_workflow_files.py b/tests/contract/reviewer_bot/test_workflow_files.py new file mode 100644 index 000000000..c44bb14ce --- /dev/null +++ b/tests/contract/reviewer_bot/test_workflow_files.py @@ -0,0 +1,175 @@ +import json +from pathlib import Path + +import pytest + +pytestmark = pytest.mark.contract + +import yaml + + +def _load_observer_contract_matrix() -> dict: + return json.loads( + Path("tests/fixtures/workflow_contracts/observer_payload_contract_matrix.json").read_text( + encoding="utf-8" + ) + ) + + +def _load_fixture_payload(relative_path: str) -> dict: + data = json.loads(Path(relative_path).read_text(encoding="utf-8")) + return data["payload"] + + +def test_issue_comment_direct_workflow_exports_issue_state(): + workflow_text = Path(".github/workflows/reviewer-bot-issue-comment-direct.yml").read_text( + encoding="utf-8" + ) + assert "ISSUE_STATE: ${{ github.event.issue.state }}" in workflow_text + +def test_pr_comment_router_workflow_builds_payload_inline_without_bot_src_root(): + workflow = Path(".github/workflows/reviewer-bot-pr-comment-router.yml").read_text(encoding="utf-8") + assert "build_pr_comment_observer_payload" not in workflow + assert "Fetch trusted bot source tarball" in workflow + +def test_pr_comment_router_workflow_contains_route_and_trusted_jobs_in_order(): + data = yaml.safe_load(Path(".github/workflows/reviewer-bot-pr-comment-router.yml").read_text(encoding="utf-8")) + assert set(data["jobs"]) == {"route-pr-comment", "trusted-direct"} + route_steps = data["jobs"]["route-pr-comment"]["steps"] + trusted_steps = data["jobs"]["trusted-direct"]["steps"] + assert route_steps[0]["name"] == "Route PR comment" + assert route_steps[1]["name"] == "Upload deferred comment artifact" + assert trusted_steps[0]["name"] == "Install uv" + assert trusted_steps[1]["name"] == "Fetch trusted bot source tarball" + assert trusted_steps[2]["name"] == "Run reviewer bot" + +def test_pr_comment_router_upload_is_emitted_only_for_deferred_reconcile(): + data = yaml.safe_load(Path(".github/workflows/reviewer-bot-pr-comment-router.yml").read_text(encoding="utf-8")) + upload_step = data["jobs"]["route-pr-comment"]["steps"][1] + assert upload_step["if"] == "${{ steps.route.outputs.route_outcome == 'deferred_reconcile' }}" + +def test_review_comment_observer_workflow_exists_and_is_read_only(): + data = yaml.safe_load( + Path(".github/workflows/reviewer-bot-pr-review-comment-observer.yml").read_text(encoding="utf-8") + ) + on_block = data.get("on", data.get(True)) + assert on_block["pull_request_review_comment"]["types"] == ["created"] + job = data["jobs"]["observer"] + assert job["permissions"]["contents"] == "read" + steps = job["steps"] + assert steps[0]["name"] == "Build deferred review comment artifact" + assert steps[1]["name"] == "Upload deferred review comment artifact" + workflow_text = Path(".github/workflows/reviewer-bot-pr-review-comment-observer.yml").read_text( + encoding="utf-8" + ) + assert "checkout" not in workflow_text + assert "pull_request_review_comment" in workflow_text + +def test_mutating_reviewer_bot_workflows_do_not_share_global_github_concurrency(): + workflow_paths = [ + ".github/workflows/reviewer-bot-issues.yml", + ".github/workflows/reviewer-bot-issue-comment-direct.yml", + ".github/workflows/reviewer-bot-sweeper-repair.yml", + ".github/workflows/reviewer-bot-pr-metadata.yml", + ".github/workflows/reviewer-bot-pr-comment-router.yml", + ".github/workflows/reviewer-bot-reconcile.yml", + ".github/workflows/reviewer-bot-privileged-commands.yml", + ] + for workflow_path in workflow_paths: + data = yaml.safe_load(Path(workflow_path).read_text(encoding="utf-8")) + for job in data.get("jobs", {}).values(): + assert "concurrency" not in job + +def test_workflow_policy_split_and_lock_only_boundaries(): + workflows_dir = Path(".github/workflows") + required = { + "reviewer-bot-issues.yml", + "reviewer-bot-issue-comment-direct.yml", + "reviewer-bot-sweeper-repair.yml", + "reviewer-bot-pr-metadata.yml", + "reviewer-bot-pr-comment-router.yml", + "reviewer-bot-pr-review-submitted-observer.yml", + "reviewer-bot-pr-review-dismissed-observer.yml", + "reviewer-bot-pr-review-comment-observer.yml", + "reviewer-bot-reconcile.yml", + "reviewer-bot-privileged-commands.yml", + } + assert required.issubset({path.name for path in workflows_dir.glob("reviewer-bot-*.yml")}) + for path in required: + data = yaml.safe_load((workflows_dir / path).read_text(encoding="utf-8")) + jobs = data.get("jobs", {}) + for job in jobs.values(): + permissions = job.get("permissions", {}) + steps = job.get("steps", []) + uses_values = [step.get("uses", "") for step in steps if isinstance(step, dict)] + text = (workflows_dir / path).read_text(encoding="utf-8") + if "observer" in path: + assert permissions.get("contents") == "read" + assert all("checkout" not in value for value in uses_values) + if permissions.get("contents") == "write" and path not in { + "reviewer-bot-privileged-commands.yml", + "reviewer-bot-pr-comment-router.yml", + }: + assert all("checkout" not in value for value in uses_values) + assert "Temporary lock debt" in text + for value in uses_values: + if value: + assert "@" in value and len(value.split("@", 1)[1]) == 40 + +def test_workflow_summaries_and_runbook_references_exist(): + runbook = Path("docs/reviewer-bot-review-freshness-operator-runbook.md") + assert runbook.exists() + reconcile = Path(".github/workflows/reviewer-bot-reconcile.yml").read_text(encoding="utf-8") + assert "docs/reviewer-bot-review-freshness-operator-runbook.md" in reconcile + + +@pytest.mark.parametrize( + ("fixture_path", "workflow_file", "payload_kind", "expected_event_name", "expected_event_action"), + [ + ( + "tests/fixtures/observer_payloads/workflow_pr_comment_deferred.json", + ".github/workflows/reviewer-bot-pr-comment-router.yml", + "deferred_comment", + "issue_comment", + "created", + ), + ( + "tests/fixtures/observer_payloads/workflow_pr_review_submitted_deferred.json", + ".github/workflows/reviewer-bot-pr-review-submitted-observer.yml", + "deferred_review_submitted", + "pull_request_review", + "submitted", + ), + ( + "tests/fixtures/observer_payloads/workflow_pr_review_dismissed_deferred.json", + ".github/workflows/reviewer-bot-pr-review-dismissed-observer.yml", + "deferred_review_dismissed", + "pull_request_review", + "dismissed", + ), + ( + "tests/fixtures/observer_payloads/workflow_pr_review_comment_deferred.json", + ".github/workflows/reviewer-bot-pr-review-comment-observer.yml", + "deferred_review_comment", + "pull_request_review_comment", + "created", + ), + ], +) +def test_observer_workflow_fixtures_match_trigger_event_shape( + fixture_path, workflow_file, payload_kind, expected_event_name, expected_event_action +): + matrix = _load_observer_contract_matrix() + fixture_entry = next( + item for item in matrix["payload_contracts"] if item["payload_kind"] == payload_kind + ) + payload = _load_fixture_payload(fixture_path) + workflow_text = Path(workflow_file).read_text(encoding="utf-8") + workflow_data = yaml.safe_load(workflow_text) + on_block = workflow_data.get("on", workflow_data.get(True)) + + assert fixture_entry["owner"] == workflow_file + assert fixture_entry["payload_kind"] == payload_kind + assert payload["source_event_name"] == expected_event_name + assert payload["source_event_action"] == expected_event_action + assert expected_event_name in on_block diff --git a/tests/fixtures/__init__.py b/tests/fixtures/__init__.py new file mode 100644 index 000000000..c0970cc35 --- /dev/null +++ b/tests/fixtures/__init__.py @@ -0,0 +1,39 @@ +from .reviewer_bot_builders import ( + accept_contributor_comment as accept_contributor_comment, +) +from .reviewer_bot_builders import ( + accept_contributor_revision as accept_contributor_revision, +) +from .reviewer_bot_builders import ( + accept_reviewer_comment as accept_reviewer_comment, +) +from .reviewer_bot_builders import ( + accept_reviewer_review as accept_reviewer_review, +) +from .reviewer_bot_builders import ( + accepted_record as accepted_record, +) +from .reviewer_bot_builders import ( + issue_snapshot as issue_snapshot, +) +from .reviewer_bot_builders import ( + make_tracked_review_state as make_tracked_review_state, +) +from .reviewer_bot_builders import ( + pull_request_payload as pull_request_payload, +) +from .reviewer_bot_builders import ( + review_payload as review_payload, +) + +__all__ = [ + "accept_contributor_comment", + "accept_contributor_revision", + "accept_reviewer_comment", + "accept_reviewer_review", + "accepted_record", + "issue_snapshot", + "make_tracked_review_state", + "pull_request_payload", + "review_payload", +] diff --git a/tests/fixtures/app_harness.py b/tests/fixtures/app_harness.py new file mode 100644 index 000000000..ec898d644 --- /dev/null +++ b/tests/fixtures/app_harness.py @@ -0,0 +1,79 @@ +from __future__ import annotations + +from dataclasses import dataclass + +from scripts import reviewer_bot +from scripts.reviewer_bot_lib import app +from scripts.reviewer_bot_lib.context import EventContext, ExecutionResult + +from .fake_runtime import FakeReviewerBotRuntime +from .reviewer_bot_env import set_env_values, set_workflow_run_event_payload + + +@dataclass +class MainRun: + exit_code: int | None + context: EventContext | None = None + + +class AppHarness: + def __init__(self, monkeypatch): + self._monkeypatch = monkeypatch + self.runtime = FakeReviewerBotRuntime(monkeypatch) + self.config = self.runtime.config + self.outputs = self.runtime.outputs + self.state_store = self.runtime.state_store + self.locks = self.runtime.locks + self.handlers = self.runtime.handlers + self.touch_tracker = self.runtime.touch_tracker + + def set_event(self, **values) -> None: + set_env_values(self.config, **values) + + def set_workflow_run_name(self, workflow_name: str) -> None: + set_workflow_run_event_payload(self.config, workflow_name) + + def set_state_sequence(self, *states: dict) -> None: + self.runtime.stub_state_sequence(*states) + + def stub_load_state(self, func) -> None: + self.state_store.stub_load(func) + + def stub_save_state(self, func) -> None: + self.state_store.stub_save(func) + + def stub_lock(self, *, acquire=None, release=None, refresh=None) -> None: + self.locks.stub(acquire=acquire, release=release, refresh=refresh) + + def stub_handler(self, name: str, func) -> None: + self.handlers.stub(name, func) + + def stub_pass_until(self, func) -> None: + self.runtime.stub_pass_until(func) + + def stub_sync_members(self, func) -> None: + self.runtime.stub_sync_members(func) + + def stub_sync_status_labels(self, func) -> None: + self.runtime.stub_sync_status_labels(func) + + def stub_execute_run(self, result: ExecutionResult) -> MainRun: + captured = MainRun(exit_code=None) + + def fake_execute_run(bot, context): + captured.context = context + return result + + self._monkeypatch.setattr(app, "execute_run", fake_execute_run) + return captured + + def run_execute(self): + return reviewer_bot.execute_run(reviewer_bot.build_event_context(self.runtime), self.runtime) + + def run_main(self) -> MainRun: + try: + reviewer_bot.main(self.runtime) + except SystemExit as exc: + code = exc.code if isinstance(exc.code, int) else 1 + return MainRun(exit_code=code) + return MainRun(exit_code=None) diff --git a/tests/fixtures/commands_harness.py b/tests/fixtures/commands_harness.py new file mode 100644 index 000000000..3b65b7435 --- /dev/null +++ b/tests/fixtures/commands_harness.py @@ -0,0 +1,341 @@ +from __future__ import annotations + +import subprocess + +from scripts.reviewer_bot_core.comment_routing_policy import PrCommentRouterOutcome +from scripts.reviewer_bot_lib import automation as automation_module +from scripts.reviewer_bot_lib import commands as commands_module +from scripts.reviewer_bot_lib import comment_routing as comment_routing_module +from scripts.reviewer_bot_lib import event_inputs +from scripts.reviewer_bot_lib import maintenance as maintenance_module +from scripts.reviewer_bot_lib import reconcile as reconcile_module +from scripts.reviewer_bot_lib.config import AssignmentAttempt +from scripts.reviewer_bot_lib.context import ( + CommentEventRequest, + PrCommentAdmission, +) + +from .fake_runtime import FakeReviewerBotRuntime +from .reviewer_bot_builders import ( + build_assignment_request, + build_comment_event_request, + build_pr_comment_admission, + build_privileged_command_request, +) +from .reviewer_bot_env import set_env_values +from .reviewer_bot_recorders import record_comment_side_effects, record_comments + + +class AutomationRunner: + def __init__(self): + self._results: dict[tuple[str, ...], subprocess.CompletedProcess] = {} + self.calls: list[tuple[list[str], object, bool]] = [] + + def when(self, command: list[str], *, returncode: int = 0, stdout: str = "", stderr: str = "") -> None: + self._results[tuple(command)] = subprocess.CompletedProcess(command, returncode, stdout=stdout, stderr=stderr) + + def run(self, command, cwd, check=False): + command_list = list(command) + self.calls.append((command_list, cwd, check)) + key = tuple(command_list) + if key not in self._results: + raise AssertionError(f"Unexpected command: {command_list}") + return self._results[key] + + +class CommandHarness: + def __init__(self, monkeypatch): + self._monkeypatch = monkeypatch + self.runtime = FakeReviewerBotRuntime(monkeypatch) + self.runtime.ACTIVE_LEASE_CONTEXT = object() + self.config = self.runtime.config + self.github = self.runtime.github + self.handlers = self.runtime.handlers + + def wrapper_set_comment_command( + self, + *, + issue_number: int, + actor: str, + body: str, + issue_author: str, + is_pull_request: bool = False, + author_association: str = "", + workflow_file: str = "", + repository: str = "", + ref: str = "", + comment_id: int = 100, + created_at: str = "2026-03-17T10:00:00Z", + ) -> None: + values = { + "ISSUE_NUMBER": issue_number, + "IS_PULL_REQUEST": str(is_pull_request).lower(), + "ISSUE_AUTHOR": issue_author, + "COMMENT_USER_TYPE": "User", + "COMMENT_AUTHOR": actor, + "COMMENT_ID": comment_id, + "COMMENT_CREATED_AT": created_at, + "COMMENT_BODY": body, + } + if author_association: + values["COMMENT_AUTHOR_ASSOCIATION"] = author_association + if workflow_file: + values["CURRENT_WORKFLOW_FILE"] = workflow_file + if repository: + values["GITHUB_REPOSITORY"] = repository + if ref: + values["GITHUB_REF"] = ref + set_env_values(self.config, **values) + + def wrapper_set_assignment_context(self, *, issue_author: str, is_pull_request: bool) -> None: + set_env_values(self.config, ISSUE_AUTHOR=issue_author, IS_PULL_REQUEST=str(is_pull_request).lower()) + + def wrapper_set_privileged_context( + self, + *, + labels: list[str], + is_pull_request: bool = False, + target_repo_root=None, + ) -> None: + import json + + set_env_values(self.config, IS_PULL_REQUEST=str(is_pull_request).lower(), ISSUE_LABELS=json.dumps(labels)) + if target_repo_root is not None: + self.config.set("REVIEWER_BOT_TARGET_REPO_ROOT", target_repo_root) + + def wrapper_set_manual_dispatch(self, *, source_event_key: str) -> None: + set_env_values(self.config, MANUAL_ACTION="execute-pending-privileged-command", PRIVILEGED_SOURCE_EVENT_KEY=source_event_key) + + set_comment_command = wrapper_set_comment_command + set_assignment_context = wrapper_set_assignment_context + set_privileged_context = wrapper_set_privileged_context + set_manual_dispatch = wrapper_set_manual_dispatch + + def capture_posted_comments(self): + return record_comments(self.runtime) + + def capture_comment_side_effects(self): + return record_comment_side_effects(self.runtime) + + def stub_assignees(self, assignees): + self.runtime.get_issue_assignees = lambda issue_number: assignees + + def stub_assignment(self, *, success: bool = True, status_code: int = 201): + def attempt(issue_number, username): + return AssignmentAttempt(success=success, status_code=status_code) + + self.runtime.request_pr_reviewer_assignment = attempt + self.runtime.assign_issue_assignee = attempt + + def stub_permission(self, status: str) -> None: + self.runtime.get_user_permission_status = lambda username, required_permission="triage": status + + def stub_handler(self, name: str, func) -> None: + self.handlers.stub(name, func) + + def automation_runner(self) -> AutomationRunner: + runner = AutomationRunner() + self._monkeypatch.setattr(automation_module, "run_command", runner.run) + self.runtime.run_command = runner.run + return runner + + def assignment_request(self, *, issue_number: int): + return event_inputs.build_assignment_request(self.runtime, issue_number=issue_number) + + def typed_assignment_request( + self, + *, + issue_number: int, + issue_author: str = "", + is_pull_request: bool = False, + issue_labels: tuple[str, ...] = (), + repo_owner: str = "", + repo_name: str = "", + ): + return build_assignment_request( + issue_number=issue_number, + issue_author=issue_author, + is_pull_request=is_pull_request, + issue_labels=issue_labels, + repo_owner=repo_owner, + repo_name=repo_name, + ) + + def privileged_request(self, *, issue_number: int, actor: str = "", command_name: str = ""): + return event_inputs.build_privileged_command_request( + self.runtime, + issue_number=issue_number, + actor=actor, + command_name=command_name, + ) + + def typed_privileged_request( + self, + *, + issue_number: int, + actor: str = "", + command_name: str = "", + is_pull_request: bool = False, + issue_labels: tuple[str, ...] = (), + ): + return build_privileged_command_request( + issue_number=issue_number, + actor=actor, + command_name=command_name, + is_pull_request=is_pull_request, + issue_labels=issue_labels, + ) + + def typed_comment_request( + self, + *, + issue_number: int, + actor: str, + body: str, + issue_author: str, + is_pull_request: bool = False, + issue_state: str = "open", + issue_labels: tuple[str, ...] = (), + comment_id: int = 100, + created_at: str = "2026-03-17T10:00:00Z", + ) -> CommentEventRequest: + return build_comment_event_request( + issue_number=issue_number, + is_pull_request=is_pull_request, + issue_state=issue_state, + issue_author=issue_author, + issue_labels=issue_labels, + comment_id=comment_id, + comment_author=actor, + comment_body=body, + comment_created_at=created_at, + comment_user_type="User", + ) + + def typed_pr_admission( + self, + *, + repository: str = "", + pr_head_full_name: str = "", + pr_author: str = "", + issue_state: str = "open", + issue_labels: tuple[str, ...] = (), + comment_author_id: int = 200, + run_id: int = 0, + run_attempt: int = 0, + author_association: str = "", + workflow_file: str = "", + ref: str = "", + ) -> PrCommentAdmission: + del author_association, workflow_file, ref + if repository and not pr_head_full_name: + pr_head_full_name = repository + if repository and not pr_author: + pr_author = "trusted-author" + return build_pr_comment_admission( + route_outcome=PrCommentRouterOutcome.TRUSTED_DIRECT, + declared_trust_class="pr_trusted_direct", + github_repository=repository, + pr_head_full_name=pr_head_full_name, + pr_author=pr_author, + issue_state=issue_state, + issue_labels=issue_labels, + comment_author_id=comment_author_id, + github_run_id=run_id, + github_run_attempt=run_attempt, + ) + + def typed_trust_context(self, **kwargs): + return self.typed_pr_admission(**kwargs) + + def handle_assign(self, state: dict, issue_number: int, username: str, *, request=None): + return commands_module.handle_assign_command( + self.runtime, + state, + issue_number, + username, + request=request or self.assignment_request(issue_number=issue_number), + ) + + def handle_claim(self, state: dict, issue_number: int, comment_author: str, *, request=None): + return commands_module.handle_claim_command( + self.runtime, + state, + issue_number, + comment_author, + request=request or self.assignment_request(issue_number=issue_number), + ) + + def handle_pass(self, state: dict, issue_number: int, comment_author: str, reason: str | None, *, request=None): + return commands_module.handle_pass_command( + self.runtime, + state, + issue_number, + comment_author, + reason, + request=request or self.assignment_request(issue_number=issue_number), + ) + + def handle_pass_until(self, state: dict, issue_number: int, comment_author: str, return_date: str, reason: str | None, *, request=None): + return commands_module.handle_pass_until_command( + self.runtime, + state, + issue_number, + comment_author, + return_date, + reason, + request=request or self.assignment_request(issue_number=issue_number), + ) + + def handle_release(self, state: dict, issue_number: int, comment_author: str, args=None, *, request=None): + return commands_module.handle_release_command( + self.runtime, + state, + issue_number, + comment_author, + args, + request=request or self.assignment_request(issue_number=issue_number), + ) + + def handle_assign_from_queue(self, state: dict, issue_number: int, *, request=None): + return commands_module.handle_assign_from_queue_command( + self.runtime, + state, + issue_number, + request=request or self.assignment_request(issue_number=issue_number), + ) + + def handle_rectify(self, state: dict, issue_number: int, comment_author: str): + return reconcile_module.handle_rectify_command( + self.runtime, + state, + issue_number, + comment_author, + ) + + def handle_accept_no_fls_changes(self, issue_number: int, comment_author: str, *, request=None): + result = automation_module.handle_accept_no_fls_changes_command( + self.runtime, + issue_number, + comment_author, + request=request or self.privileged_request(issue_number=issue_number, actor=comment_author, command_name="accept-no-fls-changes"), + ) + return result.result_message or "", result.status == "executed" + + def handle_comment_event( + self, + state: dict, + *, + request: CommentEventRequest | None = None, + pr_admission: PrCommentAdmission | None = None, + trust_context: PrCommentAdmission | None = None, + ): + return comment_routing_module.handle_comment_event( + self.runtime, + state, + request or event_inputs.build_comment_event_request(self.runtime), + pr_admission or trust_context or event_inputs.build_pr_comment_admission(self.runtime), + ) + + def handle_manual_dispatch(self, state: dict): + return maintenance_module.handle_manual_dispatch(self.runtime, state) diff --git a/tests/fixtures/comment_routing_harness.py b/tests/fixtures/comment_routing_harness.py new file mode 100644 index 000000000..93035ee26 --- /dev/null +++ b/tests/fixtures/comment_routing_harness.py @@ -0,0 +1,173 @@ +from __future__ import annotations + +from scripts.reviewer_bot_core.comment_routing_policy import PrCommentRouterOutcome +from scripts.reviewer_bot_lib import comment_routing, event_inputs +from scripts.reviewer_bot_lib.context import ( + CommentEventRequest, + PrCommentAdmission, +) + +from .fake_runtime import FakeReviewerBotRuntime +from .reviewer_bot_builders import ( + build_comment_event_request, + build_pr_comment_admission, +) +from .reviewer_bot_env import set_env_values +from .reviewer_bot_fakes import RouteGitHubApi +from .reviewer_bot_recorders import record_comment_side_effects + + +class CommentRoutingHarness: + def __init__(self, monkeypatch): + self._monkeypatch = monkeypatch + self.github = RouteGitHubApi() + self.runtime = FakeReviewerBotRuntime(monkeypatch, github=self.github) + self.config = self.runtime.config + self.handlers = self.runtime.handlers + + def env_build_request(self, *, issue_number: int | None = None): + return event_inputs.build_comment_event_request(self.runtime, issue_number=issue_number) + + def env_build_pr_admission(self): + return event_inputs.build_pr_comment_admission(self.runtime) + + wrapper_request = env_build_request + wrapper_pr_admission = env_build_pr_admission + + def handle_comment_event(self, state: dict, request: CommentEventRequest | None = None, pr_admission: PrCommentAdmission | None = None): + return comment_routing.handle_comment_event( + self.runtime, + state, + request or self.env_build_request(), + pr_admission, + ) + + def request( + self, + *, + issue_number: int, + is_pull_request: bool, + issue_state: str = "open", + issue_author: str = "", + issue_labels: tuple[str, ...] = (), + comment_id: int = 100, + comment_author: str, + comment_author_id: int = 200, + comment_body: str, + comment_created_at: str = "2026-03-17T10:00:00Z", + comment_source_event_key: str = "", + comment_user_type: str = "User", + ) -> CommentEventRequest: + return build_comment_event_request( + issue_number=issue_number, + is_pull_request=is_pull_request, + issue_state=issue_state, + issue_author=issue_author, + issue_labels=issue_labels, + comment_id=comment_id, + comment_author=comment_author, + comment_author_id=comment_author_id, + comment_body=comment_body, + comment_created_at=comment_created_at, + comment_source_event_key=comment_source_event_key, + comment_user_type=comment_user_type, + comment_sender_type="User", + comment_installation_id=None, + comment_performed_via_github_app=False, + ) + + def pr_admission( + self, + *, + route_outcome: PrCommentRouterOutcome = PrCommentRouterOutcome.TRUSTED_DIRECT, + declared_trust_class: str = "pr_trusted_direct", + github_repository: str = "", + pr_head_full_name: str = "", + pr_author: str = "", + issue_state: str = "open", + issue_labels: tuple[str, ...] = (), + comment_author_id: int = 200, + github_run_id: int = 0, + github_run_attempt: int = 0, + comment_author_association: str = "", + current_workflow_file: str = "", + github_ref: str = "", + ) -> PrCommentAdmission: + del comment_author_association, current_workflow_file, github_ref + if github_repository and not pr_head_full_name: + pr_head_full_name = github_repository + if github_repository and not pr_author: + pr_author = "trusted-author" + return build_pr_comment_admission( + route_outcome=route_outcome, + declared_trust_class=declared_trust_class, + github_repository=github_repository, + pr_head_full_name=pr_head_full_name, + pr_author=pr_author, + issue_state=issue_state, + issue_labels=issue_labels, + comment_author_id=comment_author_id, + github_run_id=github_run_id, + github_run_attempt=github_run_attempt, + ) + + def trust_context(self, **kwargs): + return self.pr_admission(**kwargs) + + def add_pull_request_metadata(self, *, issue_number: int, head_repo_full_name: str, pr_author: str) -> None: + payload = {"head": {"repo": {"full_name": head_repo_full_name}}, "user": {"login": pr_author}} + self.github.add_api("GET", f"pulls/{issue_number}", payload) + self.github.add_request("GET", f"pulls/{issue_number}", status_code=200, payload=payload) + + def capture_comment_side_effects(self): + return record_comment_side_effects(self.runtime) + + def wrapper_apply_inputs( + self, + *, + issue_number: int, + is_pull_request: bool, + issue_state: str = "open", + issue_author: str = "", + issue_labels: str = "[]", + comment_id: int = 100, + comment_author: str, + comment_author_id: int = 200, + comment_body: str, + comment_created_at: str = "2026-03-17T10:00:00Z", + comment_user_type: str = "User", + comment_author_association: str = "", + current_workflow_file: str = "", + github_repository: str = "", + github_ref: str = "", + pr_head_full_name: str = "", + pr_author: str = "", + ) -> None: + del comment_author_association, current_workflow_file, github_ref + values = { + "EVENT_NAME": "issue_comment", + "ISSUE_NUMBER": issue_number, + "IS_PULL_REQUEST": str(is_pull_request).lower(), + "ISSUE_STATE": issue_state, + "ISSUE_LABELS": issue_labels, + "ISSUE_AUTHOR": issue_author, + "COMMENT_ID": comment_id, + "COMMENT_AUTHOR": comment_author, + "COMMENT_AUTHOR_ID": comment_author_id, + "COMMENT_BODY": comment_body, + "COMMENT_CREATED_AT": comment_created_at, + "COMMENT_USER_TYPE": comment_user_type, + "COMMENT_SENDER_TYPE": "User", + "COMMENT_PERFORMED_VIA_GITHUB_APP": "false", + "REVIEWER_BOT_ROUTE_OUTCOME": "trusted_direct", + "REVIEWER_BOT_TRUST_CLASS": "pr_trusted_direct", + } + if github_repository: + values["GITHUB_REPOSITORY"] = github_repository + if pr_head_full_name: + values["PR_HEAD_FULL_NAME"] = pr_head_full_name + if pr_author: + values["PR_AUTHOR"] = pr_author + set_env_values(self.config, **values) + + apply_wrapper_inputs = wrapper_apply_inputs diff --git a/tests/fixtures/equivalence/app/post_edit_transaction_phase_map.json b/tests/fixtures/equivalence/app/post_edit_transaction_phase_map.json new file mode 100644 index 000000000..7ebe86368 --- /dev/null +++ b/tests/fixtures/equivalence/app/post_edit_transaction_phase_map.json @@ -0,0 +1,18 @@ +{ + "harness_id": "D4b app post-edit transaction phase map", + "branch_to_phase": { + "lock_required acquire": "lock acquisition", + "state_store.load_state initial": "initial load", + "lock_required pass_until": "pass-until restoration", + "lock_required sync_members": "member sync", + "issues/pull_request_target/issue_comment/workflow_dispatch/schedule/workflow_run dispatch": "event handling", + "drain touched items": "touched-item drain", + "schedule empty active reviews guard": "schedule-only empty-active-reviews guard", + "_revalidate_epoch authoritative save": "authoritative-save epoch revalidation", + "state_store.save_state authoritative": "authoritative save", + "state_store.load_state reload before status labels": "reload before status sync", + "_revalidate_epoch status-label projection": "status-sync epoch revalidation", + "_mark_projection_repair_needed and save_state": "projection failure repair marker persistence", + "locks.release": "lock release" + } +} diff --git a/tests/fixtures/equivalence/app/transaction_phase_map.json b/tests/fixtures/equivalence/app/transaction_phase_map.json new file mode 100644 index 000000000..045b27b63 --- /dev/null +++ b/tests/fixtures/equivalence/app/transaction_phase_map.json @@ -0,0 +1,33 @@ +{ + "harness_id": "D4a app transaction phase map", + "phases": [ + "lock acquisition", + "initial load", + "pass-until restoration", + "member sync", + "event handling", + "touched-item drain", + "schedule-only empty-active-reviews guard", + "authoritative-save epoch revalidation", + "authoritative save", + "reload before status sync", + "status-sync epoch revalidation", + "projection failure repair marker persistence", + "lock release" + ], + "branch_to_phase": { + "lock_required acquire": "lock acquisition", + "state_store.load_state initial": "initial load", + "lock_required pass_until": "pass-until restoration", + "lock_required sync_members": "member sync", + "issues/pull_request_target/issue_comment/workflow_dispatch/schedule/workflow_run dispatch": "event handling", + "drain touched items": "touched-item drain", + "schedule empty active reviews guard": "schedule-only empty-active-reviews guard", + "_revalidate_epoch authoritative save": "authoritative-save epoch revalidation", + "state_store.save_state authoritative": "authoritative save", + "state_store.load_state reload before status labels": "reload before status sync", + "_revalidate_epoch status-label projection": "status-sync epoch revalidation", + "_mark_projection_repair_needed and save_state": "projection failure repair marker persistence", + "locks.release": "lock release" + } +} diff --git a/tests/fixtures/equivalence/approval_policy/function_classification_table.md b/tests/fixtures/equivalence/approval_policy/function_classification_table.md new file mode 100644 index 000000000..ba82b9544 --- /dev/null +++ b/tests/fixtures/equivalence/approval_policy/function_classification_table.md @@ -0,0 +1,25 @@ +Stays in `reviews.py` +- `resolve_pr_approval_state` +- `rebuild_pr_approval_state` +- `rebuild_pr_approval_state_result` +- `pr_has_current_write_approval` +- `apply_pr_approval_state` +- `trigger_mandatory_approver_escalation` +- `satisfy_mandatory_approver_requirement` +- `handle_pr_approved_review` + +Moves to `approval_policy.py` +- `compute_pr_approval_state_result` +- `find_triage_approval_after` +- `compute_pr_approval_state_from_reviews` + +Moves to `live_review_support.py` +- `filter_current_head_reviews_for_cycle` +- `normalize_reviews_with_parsed_timestamps` +- `collect_permission_statuses` + +Remains in `reviews_projection.py` +- `desired_labels_from_response_state` + +Out of scope +- `compute_reviewer_response_state`: out of scope for `C2a/C2b` diff --git a/tests/fixtures/equivalence/approval_policy/in_scope_functions.json b/tests/fixtures/equivalence/approval_policy/in_scope_functions.json new file mode 100644 index 000000000..83787cb0b --- /dev/null +++ b/tests/fixtures/equivalence/approval_policy/in_scope_functions.json @@ -0,0 +1,10 @@ +{ + "harness_id": "C2b approval/completion derivation equivalence", + "in_scope_move_functions": [ + "compute_pr_approval_state_result", + "find_triage_approval_after" + ], + "out_of_scope_functions": [ + "compute_reviewer_response_state" + ] +} diff --git a/tests/fixtures/equivalence/blast_radius/review.json b/tests/fixtures/equivalence/blast_radius/review.json new file mode 100644 index 000000000..56d3b9faf --- /dev/null +++ b/tests/fixtures/equivalence/blast_radius/review.json @@ -0,0 +1,279 @@ +{ + "harness_id": "F3 blast radius review", + "locality_budgets": { + "ordinary policy change": { + "production_authority_categories": 1, + "legacy_same_seam_authority_files": 0, + "proof_families": 1 + }, + "support or execution change": { + "production_authority_categories": 2, + "proof_families": 1 + }, + "runtime or protocol change": { + "production_authority_categories": 3, + "proof_families": 2 + }, + "orchestration or transaction change": { + "production_authority_categories": 2, + "proof_families": 2 + } + }, + "representative_changes": [ + { + "id": "RC1", + "change": "reviewer-response policy rule", + "change_class": "ordinary policy change", + "production_authority_categories": [ + "core-policy" + ], + "proof_families": [ + "unit-equivalence" + ], + "hotspot_files": [], + "semantic_inventory_entries": [ + "tests/fixtures/equivalence/reviewer_response/scenario_matrix.json" + ], + "expected_files": [ + "scripts/reviewer_bot_core/reviewer_response_policy.py", + "tests/unit/reviewer_bot/test_reviewer_response_equivalence.py" + ] + }, + { + "id": "RC4", + "change": "mandatory-approver policy rule", + "change_class": "ordinary policy change", + "production_authority_categories": [ + "core-policy" + ], + "proof_families": [ + "unit-equivalence" + ], + "hotspot_files": [], + "semantic_inventory_entries": [ + "tests/fixtures/equivalence/mandatory_approver_policy/decision_matrix.json" + ], + "expected_files": [ + "scripts/reviewer_bot_core/mandatory_approver_policy.py", + "tests/unit/reviewer_bot/test_mandatory_approver_policy_equivalence.py" + ] + }, + { + "id": "RC5", + "change": "mandatory-approver execution flow", + "change_class": "support or execution change", + "production_authority_categories": [ + "lib-support" + ], + "proof_families": [ + "unit-live-fetch" + ], + "hotspot_files": [ + "scripts/reviewer_bot_lib/reviews.py" + ], + "semantic_inventory_entries": [], + "expected_files": [ + "scripts/reviewer_bot_lib/reviews.py", + "tests/unit/reviewer_bot/test_reviews_live_fetch.py" + ] + }, + { + "id": "RC6", + "change": "deferred replay fail-closed message", + "change_class": "orchestration or transaction change", + "production_authority_categories": [ + "core-policy" + ], + "proof_families": [ + "unit-equivalence", + "integration-transaction" + ], + "hotspot_files": [], + "semantic_inventory_entries": [], + "expected_files": [ + "scripts/reviewer_bot_core/reconcile_replay_policy.py", + "tests/unit/reviewer_bot/test_reconcile_replay_equivalence.py", + "tests/integration/reviewer_bot/test_reconcile_workflow_run.py" + ] + }, + { + "id": "RC7", + "change": "ordinary command rule", + "change_class": "ordinary policy change", + "production_authority_categories": [ + "core-policy" + ], + "proof_families": [ + "unit-equivalence" + ], + "hotspot_files": [], + "semantic_inventory_entries": [], + "expected_files": [ + "scripts/reviewer_bot_core/comment_command_policy.py", + "tests/unit/reviewer_bot/test_comment_command_equivalence.py", + "tests/unit/reviewer_bot/test_commands.py" + ] + }, + { + "id": "RC8", + "change": "reconcile workflow protocol change", + "change_class": "runtime or protocol change", + "production_authority_categories": [ + "execution-orchestration", + "runtime-protocol" + ], + "proof_families": [ + "unit-equivalence", + "contract-inventory" + ], + "hotspot_files": [ + "scripts/reviewer_bot_lib/context.py", + "scripts/reviewer_bot_lib/reconcile.py" + ], + "semantic_inventory_entries": [ + "tests/fixtures/equivalence/runtime_surface/deletion_manifest.json" + ], + "expected_files": [ + "scripts/reviewer_bot_lib/context.py", + "scripts/reviewer_bot_lib/reconcile.py", + "tests/unit/reviewer_bot/test_reconcile_unit.py", + "tests/contract/reviewer_bot/test_runtime_protocols.py" + ] + }, + { + "id": "RC9", + "change": "runtime compatibility surface change", + "change_class": "runtime or protocol change", + "production_authority_categories": [ + "runtime-protocol", + "fixture-double" + ], + "proof_families": [ + "contract-inventory" + ], + "hotspot_files": [ + "scripts/reviewer_bot_lib/runtime.py", + "scripts/reviewer_bot_lib/bootstrap_runtime.py", + "tests/fixtures/fake_runtime.py", + "tests/fixtures/focused_fake_services.py" + ], + "semantic_inventory_entries": [ + "tests/fixtures/equivalence/runtime_surface/triple_inventory.json", + "tests/fixtures/equivalence/runtime_surface/deletion_manifest.json" + ], + "expected_files": [ + "scripts/reviewer_bot_lib/runtime.py", + "scripts/reviewer_bot_lib/bootstrap_runtime.py", + "tests/fixtures/fake_runtime.py", + "tests/fixtures/focused_fake_services.py", + "tests/contract/reviewer_bot/test_adapter_contract.py", + "tests/contract/reviewer_bot/test_fake_runtime_contract.py", + "tests/contract/reviewer_bot/test_runtime_protocols.py" + ] + }, + { + "id": "RC10", + "change": "workflow-run transaction result field change", + "change_class": "orchestration or transaction change", + "production_authority_categories": [ + "execution-orchestration" + ], + "proof_families": [ + "integration-transaction" + ], + "hotspot_files": [ + "scripts/reviewer_bot_lib/reconcile.py", + "scripts/reviewer_bot_lib/app.py" + ], + "semantic_inventory_entries": [], + "expected_files": [ + "scripts/reviewer_bot_lib/reconcile.py", + "scripts/reviewer_bot_lib/app.py", + "tests/integration/reviewer_bot/test_app_execution.py", + "tests/integration/reviewer_bot/test_app_workflow_run_bookkeeping.py", + "tests/integration/reviewer_bot/test_app_workflow_run_paths.py" + ] + }, + { + "id": "RC2", + "change": "live-repair rule", + "change_class": "ordinary policy change", + "production_authority_categories": [ + "core-policy" + ], + "proof_families": [ + "unit-equivalence" + ], + "hotspot_files": [], + "semantic_inventory_entries": [ + "tests/fixtures/equivalence/review_state_live_repair/scenarios.json" + ], + "expected_files": [ + "scripts/reviewer_bot_core/review_state_live_repair.py", + "tests/unit/reviewer_bot/test_review_state_equivalence.py" + ] + }, + { + "id": "RC3", + "change": "approval rule", + "change_class": "ordinary policy change", + "production_authority_categories": [ + "core-policy" + ], + "proof_families": [ + "unit-equivalence" + ], + "hotspot_files": [], + "semantic_inventory_entries": [ + "tests/fixtures/equivalence/approval_policy/in_scope_functions.json" + ], + "expected_files": [ + "scripts/reviewer_bot_core/approval_policy.py", + "tests/unit/reviewer_bot/test_approval_policy_equivalence.py" + ] + } + ], + "surviving_final_proof_artifacts": [ + { + "path": "tests/contract/reviewer_bot/test_blast_radius_review.py", + "reason": "active blast-radius risk for future change" + }, + { + "path": "tests/fixtures/equivalence/blast_radius/review.json", + "reason": "active blast-radius risk for future change" + }, + { + "path": "tests/contract/reviewer_bot/test_runtime_protocols.py", + "reason": "active runtime/bootstrap/fake-runtime compatibility safety" + }, + { + "path": "tests/contract/reviewer_bot/test_adapter_contract.py", + "reason": "active runtime/bootstrap/fake-runtime compatibility safety" + }, + { + "path": "tests/contract/reviewer_bot/test_fake_runtime_contract.py", + "reason": "active runtime/bootstrap/fake-runtime compatibility safety" + } + ], + "active_migration_proof_artifacts": [ + { + "path": "tests/contract/reviewer_bot/test_support_layer_ownership.py", + "reason": "active migration proof that still enforces support-layer ownership inventory" + }, + { + "path": "tests/fixtures/equivalence/support_layer/symbol_inventory.json", + "reason": "active migration fixture still enforced by support-layer ownership contract proof" + }, + { + "path": "tests/fixtures/equivalence/runtime_surface/triple_inventory.json", + "reason": "active migration fixture until runtime-surface inventory and deletion proof retire together" + }, + { + "path": "tests/fixtures/equivalence/runtime_surface/deletion_manifest.json", + "reason": "active migration fixture until runtime-surface inventory and deletion proof retire together" + } + ], + "retired_migration_only_artifacts": [ + + ] +} diff --git a/tests/fixtures/equivalence/comment_command/decision_scope.json b/tests/fixtures/equivalence/comment_command/decision_scope.json new file mode 100644 index 000000000..aaea1ef1b --- /dev/null +++ b/tests/fixtures/equivalence/comment_command/decision_scope.json @@ -0,0 +1,31 @@ +{ + "harness_id": "C3b1 command decision equivalence", + "in_scope_command_set": [ + "pass", + "away", + "label", + "sync-members", + "queue", + "commands", + "claim", + "release", + "rectify", + "r?-user", + "assign-from-queue", + "r?", + "_multiple_commands", + "_malformed_known", + "_malformed_unknown", + "unknown_command" + ], + "deferred_command_set": [ + "accept-no-fls-changes" + ], + "equivalence_scenarios": [ + "queue_success", + "label_triplet_handler", + "away_missing_date", + "multiple_commands_warning", + "unknown_command_error" + ] +} diff --git a/tests/fixtures/equivalence/comment_freshness/scenario_matrix.json b/tests/fixtures/equivalence/comment_freshness/scenario_matrix.json new file mode 100644 index 000000000..1d6f30ac0 --- /dev/null +++ b/tests/fixtures/equivalence/comment_freshness/scenario_matrix.json @@ -0,0 +1,10 @@ +{ + "harness_id": "D1a comment freshness equivalence", + "scenarios": [ + "contributor_plain_text_freshness", + "reviewer_plain_text_freshness", + "reviewer_activity_only_when_semantic_key_exists", + "non_contributor_non_reviewer_noop", + "command_plus_text_freshness" + ] +} diff --git a/tests/fixtures/equivalence/comment_routing/route_outcomes.json b/tests/fixtures/equivalence/comment_routing/route_outcomes.json new file mode 100644 index 000000000..081d0e78e --- /dev/null +++ b/tests/fixtures/equivalence/comment_routing/route_outcomes.json @@ -0,0 +1,14 @@ +{ + "harness_id": "C3a trust-routing and comment classification equivalence", + "route_scenarios": [ + "same_repo_trusted_pr_comment", + "cross_repo_deferred_pr_comment", + "automation_or_self_noop_pr_comment", + "issue_comment_direct" + ], + "payload_scenarios": [ + "command_plus_text", + "trusted_direct_observer_noop", + "deferred_observer_payload" + ] +} diff --git a/tests/fixtures/equivalence/deferred_gap_diagnosis/vocabulary_matrix.json b/tests/fixtures/equivalence/deferred_gap_diagnosis/vocabulary_matrix.json new file mode 100644 index 000000000..0a30a0d7c --- /dev/null +++ b/tests/fixtures/equivalence/deferred_gap_diagnosis/vocabulary_matrix.json @@ -0,0 +1,30 @@ +{ + "harness_id": "C5a deferred gap diagnosis vocabulary matrix", + "reason_values": [ + "artifact_missing", + "artifact_invalid", + "observer_state_unknown", + "reconcile_failed_closed" + ], + "diagnostic_reason_values": [ + "invalid_source_event_created_at", + "workflow_run_scan_unavailable", + "unsupported_source_event_kind", + "artifact_scan_unavailable", + "no_exact_source_event_key_match", + "ambiguous_exact_artifact_matches", + "run_scan_unknown", + "exact_artifact_missing", + "artifact_ambiguity", + "artifact_invalid" + ], + "visible_review_repair_categories": [ + "review_submission_repair", + "review_dismissal_repair", + "no_repair_recommended" + ], + "ownership_decision": { + "diagnosis": "recommends", + "sweeper": "mutates" + } +} diff --git a/tests/fixtures/equivalence/mandatory_approver_policy/decision_matrix.json b/tests/fixtures/equivalence/mandatory_approver_policy/decision_matrix.json new file mode 100644 index 000000000..68a067a64 --- /dev/null +++ b/tests/fixtures/equivalence/mandatory_approver_policy/decision_matrix.json @@ -0,0 +1,53 @@ +{ + "harness_id": "H2a mandatory-approver decision equivalence", + "owner": [ + "scripts.reviewer_bot_lib.reviews.trigger_mandatory_approver_escalation", + "scripts.reviewer_bot_lib.reviews.satisfy_mandatory_approver_requirement" + ], + "out_of_scope": [ + "reviewer-response derivation", + "label writes outside mandatory approver decisions" + ], + "rows": [ + { + "id": "escalation_required_label_apply_ping_required", + "decision_rows": [ + "escalation-required decision", + "label-apply decision", + "ping-required decision" + ], + "result": true + }, + { + "id": "escalation_already_required_label_and_ping_already_recorded", + "decision_rows": [ + "escalation-required decision", + "label-apply decision", + "ping-required decision" + ], + "result": false + }, + { + "id": "satisfaction_allowed_with_label_remove", + "decision_rows": [ + "satisfaction-allowed decision", + "label-remove decision" + ], + "result": true + }, + { + "id": "satisfaction_denied_when_not_required", + "decision_rows": [ + "satisfaction-allowed decision" + ], + "result": false + }, + { + "id": "satisfaction_denied_when_already_satisfied", + "decision_rows": [ + "satisfaction-allowed decision" + ], + "result": false + } + ] +} diff --git a/tests/fixtures/equivalence/privileged_command_planning/contract_matrix.json b/tests/fixtures/equivalence/privileged_command_planning/contract_matrix.json new file mode 100644 index 000000000..7b6203d5d --- /dev/null +++ b/tests/fixtures/equivalence/privileged_command_planning/contract_matrix.json @@ -0,0 +1,115 @@ +{ + "harness_id": "C6a privileged command planning contract matrix", + "ordered_execution_steps": [ + "working_tree_clean_check", + "audit_no_impact_check", + "spec_lock_update", + "changed_files_validation", + "branch_name_derivation", + "branch_existence_check", + "branch_creation", + "git_add_spec_lock", + "git_commit", + "git_push", + "pull_request_create" + ], + "failure_categories": [ + "pull_request_target_not_allowed", + "missing_fls_audit_label", + "authorization_unavailable", + "authorization_failed", + "working_tree_not_clean", + "audit_reported_guideline_impact", + "audit_failed", + "update_failed", + "unexpected_changed_files", + "branch_or_push_failed", + "pull_request_creation_failed" + ], + "commit_message": "chore: update spec.lock; no affected guidelines", + "pull_request_title": "chore: update spec.lock (no guideline impact)", + "pull_request_body_lines": [ + "Updates `src/spec.lock` after confirming the audit reported no affected guidelines.", + "Closes #" + ], + "branch_name_format": "chore/spec-lock--issue-", + "revalidation_checkpoints": [ + "issue_not_pull_request", + "fls_audit_label_present", + "triage_permission_granted", + "working_tree_clean_before_update", + "audit_no_impact_before_update", + "changed_files_exact_after_update" + ], + "handoff_fail_closed_reasons": [ + "pull_request_target_not_allowed", + "missing_fls_audit_label", + "authorization_unavailable", + "authorization_failed" + ], + "revalidation_fail_closed_reasons": [ + "unsupported_command", + "pull_request_target_not_allowed", + "missing_fls_audit_label", + "authorization_unavailable", + "authorization_failed" + ], + "execution_result_codes": [ + "working_tree_not_clean", + "audit_reported_guideline_impact", + "audit_failed", + "update_failed", + "already_up_to_date", + "unexpected_changed_files", + "branch_or_push_failed", + "pull_request_creation_failed", + "opened_pull_request" + ], + "pending_command_metadata_keys": [ + "source_event_key", + "command_name", + "issue_number", + "actor", + "authorization_required_permission", + "authorization_authorized", + "target_kind", + "target_number", + "target_labels_snapshot", + "status", + "created_at", + "completed_at", + "result_code", + "result_message", + "opened_pr_url" + ], + "execute_plan_keys": [ + "record", + "execution_context" + ], + "execute_plan_record_keys": [ + "source_event_key", + "command_name", + "issue_number", + "actor", + "authorization_required_permission", + "authorization_authorized", + "target_kind", + "target_number", + "target_labels_snapshot", + "status", + "created_at", + "completed_at", + "result_code", + "result_message", + "opened_pr_url" + ], + "execute_plan_execution_context_keys": [ + "target_repo_root", + "base_branch", + "branch_probe_name", + "branch_name", + "expected_changed_files", + "existing_open_pr_url" + ], + "trusted_acknowledgment_comment": "✅ Recorded pending privileged command `accept-no-fls-changes` from trusted live validation. Use the isolated privileged workflow to execute it from issue `#314` state." +} diff --git a/tests/fixtures/equivalence/reconcile_replay/scenario_matrix.json b/tests/fixtures/equivalence/reconcile_replay/scenario_matrix.json new file mode 100644 index 000000000..25aa1511d --- /dev/null +++ b/tests/fixtures/equivalence/reconcile_replay/scenario_matrix.json @@ -0,0 +1,86 @@ +{ + "harness_id": "C4a reconcile replay scenario matrix", + "scenarios": [ + { + "scenario_id": "noop_artifact", + "fixture_kind": "observer_noop", + "expected_state_delta": "create_tracked_review_only", + "expected_touched_items": [42], + "expected_deferred_gap_mutation": "none", + "expected_reconciled_source_events_mutation": "none", + "expected_diagnostic_output": "none" + }, + { + "scenario_id": "deferred_comment_replay_matching_live_comment", + "fixture_kind": "deferred_comment", + "expected_state_delta": "apply_comment_replay", + "expected_touched_items": [42], + "expected_deferred_gap_mutation": "clear_or_none", + "expected_reconciled_source_events_mutation": "append_source_event_key", + "expected_diagnostic_output": "none" + }, + { + "scenario_id": "deferred_comment_replay_missing_live_comment", + "fixture_kind": "deferred_comment", + "expected_state_delta": "no_business_mutation", + "expected_touched_items": [42], + "expected_deferred_gap_mutation": "artifact_missing_or_not_found", + "expected_reconciled_source_events_mutation": "none", + "expected_diagnostic_output": "missing_live_comment" + }, + { + "scenario_id": "deferred_comment_replay_digest_or_classification_drift", + "fixture_kind": "deferred_comment", + "expected_state_delta": "fail_closed_only", + "expected_touched_items": [42], + "expected_deferred_gap_mutation": "reconcile_failed_closed", + "expected_reconciled_source_events_mutation": "none", + "expected_diagnostic_output": "classification_or_digest_drift" + }, + { + "scenario_id": "deferred_review_submitted_replay", + "fixture_kind": "deferred_review_submitted", + "expected_state_delta": "apply_review_replay", + "expected_touched_items": [42], + "expected_deferred_gap_mutation": "clear_or_none", + "expected_reconciled_source_events_mutation": "append_source_event_key", + "expected_diagnostic_output": "none" + }, + { + "scenario_id": "deferred_review_dismissed_replay", + "fixture_kind": "deferred_review_dismissed", + "expected_state_delta": "apply_review_dismissal_replay", + "expected_touched_items": [42], + "expected_deferred_gap_mutation": "clear_or_none", + "expected_reconciled_source_events_mutation": "append_source_event_key", + "expected_diagnostic_output": "none" + }, + { + "scenario_id": "deferred_review_comment_replay", + "fixture_kind": "deferred_review_comment", + "expected_state_delta": "apply_review_comment_replay", + "expected_touched_items": [42], + "expected_deferred_gap_mutation": "clear_or_none", + "expected_reconciled_source_events_mutation": "append_source_event_key", + "expected_diagnostic_output": "none" + }, + { + "scenario_id": "freshness_only_update", + "fixture_kind": "deferred_comment", + "expected_state_delta": "freshness_only", + "expected_touched_items": [42], + "expected_deferred_gap_mutation": "none", + "expected_reconciled_source_events_mutation": "none", + "expected_diagnostic_output": "none" + }, + { + "scenario_id": "fail_closed_gap_update", + "fixture_kind": "deferred_review_submitted", + "expected_state_delta": "fail_closed_only", + "expected_touched_items": [42], + "expected_deferred_gap_mutation": "reconcile_failed_closed", + "expected_reconciled_source_events_mutation": "none", + "expected_diagnostic_output": "fail_closed_gap_update" + } + ] +} diff --git a/tests/fixtures/equivalence/review_state/api_inventory.md b/tests/fixtures/equivalence/review_state/api_inventory.md new file mode 100644 index 000000000..6d48a7d4e --- /dev/null +++ b/tests/fixtures/equivalence/review_state/api_inventory.md @@ -0,0 +1,19 @@ +Local-State-Only Mutation APIs +- ensure_review_entry: local-state-only mutation +- accept_channel_event: local-state-only mutation +- record_reviewer_activity: local-state-only mutation +- record_transition_notice_sent: local-state-only mutation +- set_current_reviewer: local-state-only mutation +- update_reviewer_activity: local-state-only mutation +- mark_review_complete: local-state-only mutation +- clear_transition_timers: local-state-only mutation + +Live-Read-Assisted Mutation APIs +- accept_reviewer_review_from_live_review: live-read-assisted mutation; C1c in-scope +- refresh_reviewer_review_from_live_preferred_review: live-read-assisted mutation; C1c in-scope +- repair_missing_reviewer_review_state: live-read-assisted mutation; C1c in-scope + +Read-Only Helpers +- get_current_cycle_boundary: read-only helper +- list_open_tracked_review_items: read-only helper +- semantic_key_seen: read-only helper diff --git a/tests/fixtures/equivalence/review_state/live_read_assisted_scenarios.json b/tests/fixtures/equivalence/review_state/live_read_assisted_scenarios.json new file mode 100644 index 000000000..2b0918805 --- /dev/null +++ b/tests/fixtures/equivalence/review_state/live_read_assisted_scenarios.json @@ -0,0 +1,5 @@ +{ + "harness_id": "C1b/C1c mutable review-state mutation equivalence", + "scope": "live-read-assisted mutation", + "scenarios": [] +} diff --git a/tests/fixtures/equivalence/review_state/local_state_only_scenarios.json b/tests/fixtures/equivalence/review_state/local_state_only_scenarios.json new file mode 100644 index 000000000..e79a8f49e --- /dev/null +++ b/tests/fixtures/equivalence/review_state/local_state_only_scenarios.json @@ -0,0 +1,5 @@ +{ + "harness_id": "C1b/C1c mutable review-state mutation equivalence", + "scope": "local-state-only mutation", + "scenarios": [] +} diff --git a/tests/fixtures/equivalence/review_state_live_repair/api_inventory.md b/tests/fixtures/equivalence/review_state_live_repair/api_inventory.md new file mode 100644 index 000000000..f2579954b --- /dev/null +++ b/tests/fixtures/equivalence/review_state_live_repair/api_inventory.md @@ -0,0 +1,18 @@ +Live-Read-Assisted Review Repair APIs +- accept_reviewer_review_from_live_review: accepts a matching current-reviewer live review into reviewer_review channel state +- refresh_reviewer_review_from_live_preferred_review: refreshes stored reviewer_review state from the preferred live review for the active cycle +- repair_missing_reviewer_review_state: delegates repair through the same preferred live review refresh path + +Exact Frozen Scenarios +- matching current reviewer and valid submitted review +- reviewer mismatch +- no preferred review found +- head repair without review acceptance +- no-op repair path + +Out Of Scope For G2a +- ensure_review_entry +- accept_channel_event +- record_reviewer_activity +- set_current_reviewer +- mark_review_complete diff --git a/tests/fixtures/equivalence/review_state_live_repair/scenarios.json b/tests/fixtures/equivalence/review_state_live_repair/scenarios.json new file mode 100644 index 000000000..98aa1b981 --- /dev/null +++ b/tests/fixtures/equivalence/review_state_live_repair/scenarios.json @@ -0,0 +1,16 @@ +{ + "harness_id": "G2a live-read-assisted review repair inventory", + "scope": "live-read-assisted review repair", + "apis": [ + "accept_reviewer_review_from_live_review", + "refresh_reviewer_review_from_live_preferred_review", + "repair_missing_reviewer_review_state" + ], + "scenarios": [ + "matching current reviewer and valid submitted review", + "reviewer mismatch", + "no preferred review found", + "head repair without review acceptance", + "no-op repair path" + ] +} diff --git a/tests/fixtures/equivalence/review_submission_gap_repair/scenario_matrix.json b/tests/fixtures/equivalence/review_submission_gap_repair/scenario_matrix.json new file mode 100644 index 000000000..12b1a8eaa --- /dev/null +++ b/tests/fixtures/equivalence/review_submission_gap_repair/scenario_matrix.json @@ -0,0 +1,20 @@ +{ + "harness_id": "H4a review-submitted gap repair flow equivalence", + "out_of_scope": [ + "production cutover", + "other sweeper repair flows" + ], + "scenarios": [ + { + "scenario_id": "submitted_review_visible_without_exact_artifact", + "expected_reason": null, + "expected_diagnostic_reason": null, + "expected_repair_category": "review_submission_repair", + "expected_recommendation_payload": { + "author": "alice", + "submitted_at": "2026-03-25T11:00:00Z", + "commit_id": "head-1" + } + } + ] +} diff --git a/tests/fixtures/equivalence/reviewer_response/scenario_matrix.json b/tests/fixtures/equivalence/reviewer_response/scenario_matrix.json new file mode 100644 index 000000000..8435e70e5 --- /dev/null +++ b/tests/fixtures/equivalence/reviewer_response/scenario_matrix.json @@ -0,0 +1,50 @@ +{ + "harness_id": "H1a reviewer-response derivation equivalence", + "owner": "scripts.reviewer_bot_core.reviewer_response_policy.compute_reviewer_response_state", + "out_of_scope": [ + "mandatory approver escalation", + "label writes" + ], + "scenarios": [ + { + "id": "awaiting_reviewer_response_no_reviewer_activity", + "state": "awaiting_reviewer_response", + "reason": "no_reviewer_activity" + }, + { + "id": "awaiting_reviewer_response_review_head_stale", + "state": "awaiting_reviewer_response", + "reason": "review_head_stale" + }, + { + "id": "awaiting_reviewer_response_contributor_revision_newer", + "state": "awaiting_reviewer_response", + "reason": "contributor_revision_newer" + }, + { + "id": "awaiting_contributor_response_completion_missing", + "state": "awaiting_contributor_response", + "reason": "completion_missing" + }, + { + "id": "awaiting_write_approval_write_approval_missing", + "state": "awaiting_write_approval", + "reason": "write_approval_missing" + }, + { + "id": "projection_failed_pull_request_unavailable", + "state": "projection_failed", + "reason": "pull_request_unavailable" + }, + { + "id": "projection_failed_pull_request_head_unavailable", + "state": "projection_failed", + "reason": "pull_request_head_unavailable" + }, + { + "id": "projection_failed_live_review_state_unknown", + "state": "projection_failed", + "reason": "live_review_state_unknown" + } + ] +} diff --git a/tests/fixtures/equivalence/runtime_surface/deletion_manifest.json b/tests/fixtures/equivalence/runtime_surface/deletion_manifest.json new file mode 100644 index 000000000..fd3c73016 --- /dev/null +++ b/tests/fixtures/equivalence/runtime_surface/deletion_manifest.json @@ -0,0 +1,59 @@ +{ + "harness_id": "O3 runtime compatibility deletion manifest", + "artifact_classification": "active migration proof fixture", + "proof_artifacts": [ + { + "path": "tests/contract/reviewer_bot/test_runtime_protocols.py", + "classification": "rewritten final proof" + }, + { + "path": "tests/contract/reviewer_bot/test_adapter_contract.py", + "classification": "rewritten final proof" + }, + { + "path": "tests/contract/reviewer_bot/test_fake_runtime_contract.py", + "classification": "rewritten final proof" + } + ], + "paths": [ + { + "path": "scripts/reviewer_bot_lib/context.py", + "reason": "remove dead workflow-run handler compatibility surface" + }, + { + "path": "scripts/reviewer_bot_lib/bootstrap_runtime.py", + "reason": "remove dead workflow-run handler bootstrap branch" + }, + { + "path": "tests/fixtures/fake_runtime.py", + "reason": "remove dead workflow-run handler fake-runtime branch" + }, + { + "path": "tests/fixtures/focused_fake_services.py", + "reason": "remove dead workflow-run handler stub allowance and default mapping" + }, + { + "path": "tests/contract/reviewer_bot/test_adapter_contract.py", + "reason": "forbid deleted bootstrap workflow-run handler surface" + }, + { + "path": "tests/contract/reviewer_bot/test_fake_runtime_contract.py", + "reason": "forbid deleted fake-runtime workflow-run handler surface" + }, + { + "path": "tests/contract/reviewer_bot/test_runtime_protocols.py", + "reason": "forbid deleted workflow-run handler protocol expectation" + }, + { + "path": "tests/contract/reviewer_bot/test_review_state_contract.py", + "reason": "keep full contract suite aligned with retained split review-state owner surface" + } + ], + "deleted_paths": [ + "scripts/reviewer_bot_lib/context.py:EventHandlerContext.handle_workflow_run_event", + "scripts/reviewer_bot_lib/bootstrap_runtime.py:_BootstrapHandlerServices.handle_workflow_run_event", + "tests/fixtures/fake_runtime.py:FakeReviewerBotRuntime.handle_workflow_run_event", + "tests/fixtures/focused_fake_services.py:HandlerStub.ALLOWED[handle_workflow_run_event]", + "tests/fixtures/focused_fake_services.py:build_default_handler_map()[handle_workflow_run_event]" + ] +} diff --git a/tests/fixtures/equivalence/runtime_surface/triple_inventory.json b/tests/fixtures/equivalence/runtime_surface/triple_inventory.json new file mode 100644 index 000000000..cea35cf96 --- /dev/null +++ b/tests/fixtures/equivalence/runtime_surface/triple_inventory.json @@ -0,0 +1,90 @@ +{ + "harness_id": "F2a runtime/bootstrap/fake-runtime triple inventory", + "artifact_classification": "active migration proof fixture", + "proof_artifacts": [ + { + "path": "tests/contract/reviewer_bot/test_runtime_protocols.py", + "classification": "rewritten final proof" + }, + { + "path": "tests/contract/reviewer_bot/test_adapter_contract.py", + "classification": "rewritten final proof" + }, + { + "path": "tests/contract/reviewer_bot/test_fake_runtime_contract.py", + "classification": "rewritten final proof" + } + ], + "capability_triples": [ + { + "capability": "comment-event dispatch", + "runtime_forwarder": null, + "bootstrap_adapter": "scripts/reviewer_bot_lib/bootstrap_runtime.py:_BootstrapHandlerServices.handle_comment_event", + "fake_runtime_branch": "tests/fixtures/fake_runtime.py:handle_comment_event", + "classification": "retained final surface" + }, + { + "capability": "github issue snapshot access", + "runtime_forwarder": "scripts/reviewer_bot_lib/runtime.py:github_api", + "bootstrap_adapter": "scripts/reviewer_bot_lib/bootstrap_runtime.py:_BootstrapGitHubServices.get_issue_or_pr_snapshot", + "fake_runtime_branch": "tests/fixtures/fake_runtime.py:get_issue_or_pr_snapshot", + "classification": "retained final surface" + }, + { + "capability": "pull request reviews access", + "runtime_forwarder": "scripts/reviewer_bot_lib/runtime.py:get_pull_request_reviews", + "bootstrap_adapter": "scripts/reviewer_bot_lib/bootstrap_runtime.py:_BootstrapGitHubServices.get_pull_request_reviews", + "fake_runtime_branch": "tests/fixtures/fake_runtime.py:get_pull_request_reviews", + "classification": "retained final surface" + }, + { + "capability": "reviewer response projection", + "runtime_forwarder": null, + "bootstrap_adapter": "scripts/reviewer_bot_lib/bootstrap_runtime.py:_BootstrapReviewStateAdapterServices.compute_reviewer_response_state", + "fake_runtime_branch": "tests/fixtures/fake_runtime.py:compute_reviewer_response_state", + "classification": "retained final surface" + }, + { + "capability": "rebuild approval state", + "runtime_forwarder": "scripts/reviewer_bot_lib/runtime.py:rebuild_pr_approval_state", + "bootstrap_adapter": "scripts/reviewer_bot_lib/bootstrap_runtime.py:_BootstrapReviewStateAdapterServices.rebuild_pr_approval_state", + "fake_runtime_branch": "tests/fixtures/fake_runtime.py:rebuild_pr_approval_state", + "classification": "retained final surface" + }, + { + "capability": "mandatory approver satisfaction", + "runtime_forwarder": "scripts/reviewer_bot_lib/runtime.py:satisfy_mandatory_approver_requirement", + "bootstrap_adapter": "scripts/reviewer_bot_lib/bootstrap_runtime.py:_BootstrapReviewStateAdapterServices.satisfy_mandatory_approver_requirement", + "fake_runtime_branch": "tests/fixtures/fake_runtime.py:satisfy_mandatory_approver_requirement", + "classification": "retained final surface" + }, + { + "capability": "sync status labels", + "runtime_forwarder": null, + "bootstrap_adapter": "scripts/reviewer_bot_lib/bootstrap_runtime.py:_BootstrapWorkflowAdapterServices.sync_status_labels_for_items", + "fake_runtime_branch": "tests/fixtures/fake_runtime.py:sync_status_labels_for_items", + "classification": "retained final surface" + }, + { + "capability": "privileged branch lookup", + "runtime_forwarder": null, + "bootstrap_adapter": "scripts/reviewer_bot_lib/bootstrap_runtime.py:_BootstrapAutomationAdapterServices.find_open_pr_for_branch_status", + "fake_runtime_branch": "tests/fixtures/fake_runtime.py:find_open_pr_for_branch_status", + "classification": "retained final surface" + }, + { + "capability": "privileged pull request creation", + "runtime_forwarder": null, + "bootstrap_adapter": "scripts/reviewer_bot_lib/bootstrap_runtime.py:_BootstrapAutomationAdapterServices.create_pull_request", + "fake_runtime_branch": "tests/fixtures/fake_runtime.py:create_pull_request", + "classification": "retained final surface" + }, + { + "capability": "privileged accept-no-fls-changes execution", + "runtime_forwarder": null, + "bootstrap_adapter": "scripts/reviewer_bot_lib/bootstrap_runtime.py:_BootstrapCommandAdapterServices.handle_accept_no_fls_changes_command", + "fake_runtime_branch": "tests/fixtures/fake_runtime.py:handle_accept_no_fls_changes_command", + "classification": "retained final surface" + } + ] +} diff --git a/tests/fixtures/equivalence/support_layer/symbol_inventory.json b/tests/fixtures/equivalence/support_layer/symbol_inventory.json new file mode 100644 index 000000000..badf05c1e --- /dev/null +++ b/tests/fixtures/equivalence/support_layer/symbol_inventory.json @@ -0,0 +1,201 @@ +{ + "harness_id": "F1a support-layer symbol inventory", + "artifact_classification": "active migration proof fixture", + "proof_artifacts": [ + { + "path": "tests/contract/reviewer_bot/test_support_layer_ownership.py", + "classification": "active migration proof" + } + ], + "symbols": [ + { + "symbol": "scripts.reviewer_bot_core.live_review_support.projection_failure_result", + "classification": "retained support owner", + "production_importers": [ + "scripts/reviewer_bot_core/approval_policy.py" + ], + "test_or_fixture_importers": [ + "tests/unit/reviewer_bot/test_approval_policy_equivalence.py" + ] + }, + { + "symbol": "scripts.reviewer_bot_core.live_review_support.read_pull_request_result", + "classification": "retained support owner", + "production_importers": [ + "scripts/reviewer_bot_core/approval_policy.py", + "scripts/reviewer_bot_core/review_state_live_repair.py", + "scripts/reviewer_bot_core/reviewer_response_policy.py" + ], + "test_or_fixture_importers": [ + "tests/unit/reviewer_bot/test_review_read_support.py", + "tests/unit/reviewer_bot/test_review_state_equivalence.py" + ] + }, + { + "symbol": "scripts.reviewer_bot_core.live_review_support.read_pull_request_reviews_result", + "classification": "retained support owner", + "production_importers": [ + "scripts/reviewer_bot_core/approval_policy.py", + "scripts/reviewer_bot_core/reviewer_response_policy.py", + "scripts/reviewer_bot_lib/reviews.py" + ], + "test_or_fixture_importers": [ + "tests/unit/reviewer_bot/test_review_read_support.py", + "tests/unit/reviewer_bot/test_approval_policy_equivalence.py", + "tests/unit/reviewer_bot/test_reviewer_response_equivalence.py" + ] + }, + { + "symbol": "scripts.reviewer_bot_core.live_review_support.permission_status", + "classification": "retained support owner", + "production_importers": [ + "scripts/reviewer_bot_core/approval_policy.py", + "scripts/reviewer_bot_lib/reviews.py" + ], + "test_or_fixture_importers": [ + "tests/unit/reviewer_bot/test_review_read_support.py", + "tests/unit/reviewer_bot/test_approval_policy_equivalence.py" + ] + }, + { + "symbol": "scripts.reviewer_bot_core.live_review_support.parse_github_timestamp", + "classification": "retained support owner", + "production_importers": [ + "scripts/reviewer_bot_core/approval_policy.py", + "scripts/reviewer_bot_core/review_state_machine.py", + "scripts/reviewer_bot_core/reviewer_response_policy.py", + "scripts/reviewer_bot_lib/reviews.py" + ], + "test_or_fixture_importers": [ + "tests/unit/reviewer_bot/test_review_read_support.py" + ] + }, + { + "symbol": "scripts.reviewer_bot_core.live_review_support.normalize_reviews_with_parsed_timestamps", + "classification": "retained support owner", + "production_importers": [ + "scripts/reviewer_bot_core/approval_policy.py" + ], + "test_or_fixture_importers": [ + "tests/unit/reviewer_bot/test_reviews_projection.py" + ] + }, + { + "symbol": "scripts.reviewer_bot_core.live_review_support.filter_current_head_reviews_for_cycle", + "classification": "retained support owner", + "production_importers": [ + "scripts/reviewer_bot_core/approval_policy.py" + ], + "test_or_fixture_importers": [ + "tests/unit/reviewer_bot/test_reviews_projection.py" + ] + }, + { + "symbol": "scripts.reviewer_bot_core.live_review_support.collect_permission_statuses", + "classification": "retained support owner", + "production_importers": [ + "scripts/reviewer_bot_core/approval_policy.py" + ], + "test_or_fixture_importers": [ + "tests/unit/reviewer_bot/test_reviews_projection.py" + ] + }, + { + "symbol": "scripts.reviewer_bot_lib.reviews.parse_github_timestamp", + "classification": "retained compatibility forwarder", + "production_importers": [ + "scripts/reviewer_bot_lib/runtime.py" + ], + "test_or_fixture_importers": [ + "tests/unit/reviewer_bot/test_approval_policy_equivalence.py", + "tests/unit/reviewer_bot/test_review_state_equivalence.py", + "tests/unit/reviewer_bot/test_reviewer_response_equivalence.py", + "tests/unit/reviewer_bot/test_reviews_projection.py" + ] + }, + { + "symbol": "scripts.reviewer_bot_lib.reviews.list_open_items_with_status_labels", + "classification": "retained support behavior", + "production_importers": [ + "scripts/reviewer_bot_lib/maintenance.py" + ], + "test_or_fixture_importers": [ + "tests/unit/reviewer_bot/test_reviews_api_contract.py" + ] + }, + { + "symbol": "scripts.reviewer_bot_lib.reviews.rebuild_pr_approval_state", + "classification": "retained final surface", + "production_importers": [ + "scripts/reviewer_bot_lib/bootstrap_runtime.py", + "scripts/reviewer_bot_lib/lifecycle.py", + "scripts/reviewer_bot_lib/runtime.py", + "scripts/reviewer_bot_lib/sweeper.py" + ], + "test_or_fixture_importers": [ + "tests/fixtures/fake_runtime.py" + ] + }, + { + "symbol": "scripts.reviewer_bot_lib.reviews.rebuild_pr_approval_state_result", + "classification": "retained final surface", + "production_importers": [ + "scripts/reviewer_bot_lib/reconcile.py" + ], + "test_or_fixture_importers": [] + }, + { + "symbol": "scripts.reviewer_bot_lib.sweeper.sweep_deferred_gaps", + "classification": "retained final surface", + "production_importers": [ + "scripts/reviewer_bot_lib/maintenance.py" + ], + "test_or_fixture_importers": [ + "tests/unit/reviewer_bot/test_sweeper_logic.py" + ] + }, + { + "symbol": "scripts.reviewer_bot_lib.deferred_gap_bookkeeping._clear_source_event_key", + "classification": "retained support owner", + "production_importers": [ + "scripts/reviewer_bot_lib/reconcile.py", + "scripts/reviewer_bot_lib/sweeper.py" + ], + "test_or_fixture_importers": [ + "tests/unit/reviewer_bot/test_deferred_gap_bookkeeping.py" + ] + }, + { + "symbol": "scripts.reviewer_bot_lib.deferred_gap_bookkeeping._mark_reconciled_source_event", + "classification": "retained support owner", + "production_importers": [ + "scripts/reviewer_bot_lib/reconcile.py", + "scripts/reviewer_bot_lib/sweeper.py" + ], + "test_or_fixture_importers": [ + "tests/unit/reviewer_bot/test_deferred_gap_bookkeeping.py" + ] + }, + { + "symbol": "scripts.reviewer_bot_lib.deferred_gap_bookkeeping._update_deferred_gap", + "classification": "retained support owner", + "production_importers": [ + "scripts/reviewer_bot_lib/reconcile.py", + "scripts/reviewer_bot_lib/sweeper.py" + ], + "test_or_fixture_importers": [ + "tests/unit/reviewer_bot/test_deferred_gap_bookkeeping.py" + ] + }, + { + "symbol": "scripts.reviewer_bot_lib.deferred_gap_bookkeeping._was_reconciled_source_event", + "classification": "retained support owner", + "production_importers": [ + "scripts/reviewer_bot_lib/sweeper.py" + ], + "test_or_fixture_importers": [ + "tests/unit/reviewer_bot/test_deferred_gap_bookkeeping.py" + ] + } + ] +} diff --git a/tests/fixtures/fake_clock.py b/tests/fixtures/fake_clock.py new file mode 100644 index 000000000..5bed4f631 --- /dev/null +++ b/tests/fixtures/fake_clock.py @@ -0,0 +1,18 @@ +from __future__ import annotations + +from datetime import datetime, timedelta, timezone + + +class FakeClock: + def __init__(self, now: datetime | None = None): + self._now = now or datetime(2026, 1, 1, tzinfo=timezone.utc) + + def now(self) -> datetime: + return self._now + + def set(self, now: datetime) -> None: + self._now = now + + def advance(self, *, seconds: float = 0, delta: timedelta | None = None) -> datetime: + self._now = self._now + (delta or timedelta(seconds=seconds)) + return self._now diff --git a/tests/fixtures/fake_jitter.py b/tests/fixtures/fake_jitter.py new file mode 100644 index 000000000..df3a8abeb --- /dev/null +++ b/tests/fixtures/fake_jitter.py @@ -0,0 +1,18 @@ +from __future__ import annotations + + +class DeterministicJitter: + def __init__(self, values: float | list[float] = 0.0): + self._values = [float(values)] if isinstance(values, (float, int)) else [float(value) for value in values] + self.calls: list[tuple[float, float]] = [] + self._index = 0 + + def uniform(self, lower: float, upper: float) -> float: + self.calls.append((lower, upper)) + if not self._values: + return lower + if self._index >= len(self._values): + return float(self._values[-1]) + value = float(self._values[self._index]) + self._index += 1 + return value diff --git a/tests/fixtures/fake_runtime.py b/tests/fixtures/fake_runtime.py new file mode 100644 index 000000000..372aae27d --- /dev/null +++ b/tests/fixtures/fake_runtime.py @@ -0,0 +1,813 @@ +from __future__ import annotations + +import random +import sys +import time +from datetime import datetime, timezone +from types import SimpleNamespace +from typing import Any, Callable + +from scripts.reviewer_bot_lib import automation as automation_module +from scripts.reviewer_bot_lib import commands as commands_module +from scripts.reviewer_bot_lib import config as config_module +from scripts.reviewer_bot_lib import github_api as github_api_module +from scripts.reviewer_bot_lib import lease_lock as lease_lock_module +from scripts.reviewer_bot_lib import lifecycle as lifecycle_module +from scripts.reviewer_bot_lib import queue as queue_module +from scripts.reviewer_bot_lib import reconcile as reconcile_module +from scripts.reviewer_bot_lib import review_state as review_state_module +from scripts.reviewer_bot_lib import reviews as reviews_module +from scripts.reviewer_bot_lib import state_store as state_store_module +from scripts.reviewer_bot_lib.config import ( + AUTHOR_ASSOCIATION_TRUST_ALLOWLIST, + BOT_MENTION, + BOT_NAME, + DEFERRED_DISCOVERY_BOOTSTRAP_WINDOW_SECONDS, + DEFERRED_DISCOVERY_OVERLAP_SECONDS, + EVENT_INTENT_MUTATING, + EVENT_INTENT_NON_MUTATING_DEFER, + EVENT_INTENT_NON_MUTATING_READONLY, + FLS_AUDIT_LABEL, + LOCK_API_RETRY_LIMIT, + LOCK_API_RETRY_LIMIT_ENV, + LOCK_LEASE_TTL_SECONDS, + LOCK_LEASE_TTL_SECONDS_ENV, + LOCK_MAX_WAIT_SECONDS, + LOCK_MAX_WAIT_SECONDS_ENV, + LOCK_REF_BOOTSTRAP_BRANCH, + LOCK_REF_BOOTSTRAP_BRANCH_ENV, + LOCK_REF_NAME, + LOCK_REF_NAME_ENV, + LOCK_RENEWAL_WINDOW_SECONDS, + LOCK_RENEWAL_WINDOW_SECONDS_ENV, + LOCK_RETRY_BASE_SECONDS, + LOCK_RETRY_BASE_SECONDS_ENV, + REVIEW_FRESHNESS_RUNBOOK_PATH, + REVIEWER_REQUEST_422_TEMPLATE, + STATE_READ_RETRY_BASE_SECONDS, + STATE_READ_RETRY_BASE_SECONDS_ENV, + STATE_READ_RETRY_LIMIT, + STATE_READ_RETRY_LIMIT_ENV, + STATUS_PROJECTION_EPOCH, + TRANSITION_PERIOD_DAYS, + AssignmentAttempt, + GitHubApiResult, + MemberFetchResult, +) +from tests.fixtures.fake_clock import FakeClock +from tests.fixtures.fake_jitter import DeterministicJitter +from tests.fixtures.fake_sleeper import RecordingSleeper +from tests.fixtures.fake_uuid import FixedUuidSource +from tests.fixtures.focused_fake_services import ( + ArtifactDownloadTransportStub, + ConfigBag, + DeferredPayloadStore, + GitHubStub, + GraphQLTransportStub, + HandlerStub, + LockStub, + OutputCapture, + RestTransportStub, + StateStoreStub, + TouchTrackerStub, + WorkflowBehaviorStub, + build_default_handler_map, +) +from tests.fixtures.recording_logger import RecordingLogger + + +class FakeRuntimeInfraServices: + def __init__(self, *, config, outputs, deferred_payloads, logger, rest_transport, graphql_transport, artifact_download_transport, clock, sleeper, jitter, uuid_source, touch_tracker): + self.config = config + self.outputs = outputs + self.deferred_payloads = deferred_payloads + self.logger = logger + self.rest_transport = rest_transport + self.graphql_transport = graphql_transport + self.artifact_download_transport = artifact_download_transport + self.clock = clock + self.sleeper = sleeper + self.jitter = jitter + self.uuid_source = uuid_source + self.touch_tracker = touch_tracker + + +class FakeRuntimeDomainServices: + def __init__(self, *, state_store, github, locks, handlers, workflow, adapters, compat): + self.state_store = state_store + self.github = github + self.locks = locks + self.handlers = handlers + self.workflow = workflow + self.adapters = adapters + self.compat = compat + + +class FakeRuntimeAdapterServices: + def __init__(self, runtime: "FakeReviewerBotRuntime"): + self._runtime = runtime + self.workflow = runtime.workflow + self.review = runtime.compat.review + self.review_state = runtime.compat.review + self.commands = runtime.compat.review + self.queue = runtime.compat.review + self.automation = runtime.compat.automation + + def process_pass_until_expirations(self, state: dict): + return self._runtime.workflow.process_pass_until_expirations(state) + + def sync_members_with_queue(self, state: dict): + return self._runtime.workflow.sync_members_with_queue(state) + + def sync_status_labels_for_items(self, state: dict, issue_numbers): + return self._runtime.workflow.sync_status_labels_for_items(state, issue_numbers) + + +def runtime_instance_override(runtime: "FakeReviewerBotRuntime", name: str): + override = runtime.__dict__.get(name) + return override if callable(override) else None + + +class FakeRuntimeGitHubCompatibility: + def __init__(self, runtime: "FakeReviewerBotRuntime"): + self._runtime = runtime + + def get_issue_assignees(self, issue_number: int): + return github_api_module.get_issue_assignees(self._runtime, issue_number) + + def request_pr_reviewer_assignment(self, issue_number: int, username: str): + return github_api_module.request_pr_reviewer_assignment(self._runtime, issue_number, username) + + def assign_issue_assignee(self, issue_number: int, username: str): + return github_api_module.assign_issue_assignee(self._runtime, issue_number, username) + + def post_comment(self, issue_number: int, body: str) -> bool: + return github_api_module.post_comment(self._runtime, issue_number, body) + + def add_reaction(self, comment_id: int, reaction: str) -> bool: + return github_api_module.add_reaction(self._runtime, comment_id, reaction) + + def get_user_permission_status(self, username: str, required_permission: str = "triage") -> str: + return github_api_module.get_user_permission_status(self._runtime, username, required_permission) + + def check_user_permission(self, username: str, required_permission: str = "triage"): + return github_api_module.check_user_permission(self._runtime, username, required_permission) + + def get_repo_labels(self): + return github_api_module.get_repo_labels(self._runtime) + + def add_label(self, issue_number: int, label: str) -> bool: + return github_api_module.add_label(self._runtime, issue_number, label) + + def remove_label(self, issue_number: int, label: str) -> bool: + return github_api_module.remove_label(self._runtime, issue_number, label) + + def remove_issue_assignee(self, issue_number: int, username: str) -> bool: + return github_api_module.remove_issue_assignee(self._runtime, issue_number, username) + + def remove_pr_reviewer(self, issue_number: int, username: str) -> bool: + return github_api_module.remove_pr_reviewer(self._runtime, issue_number, username) + + def get_issue_or_pr_snapshot(self, issue_number: int) -> dict | None: + payload = self._runtime.github_api("GET", f"issues/{issue_number}") + return payload if isinstance(payload, dict) else None + + def get_pull_request_reviews(self, issue_number: int): + return reviews_module.get_pull_request_reviews(self._runtime, issue_number) + + def github_graphql(self, query: str, variables=None, *, token=None): + return github_api_module.github_graphql(self._runtime, query, variables, token=token) + + def get_github_graphql_token(self, *, prefer_board_token: bool = False) -> str: + if prefer_board_token: + token = self._runtime.get_config_value("REVIEWER_BOARD_TOKEN") + if token: + return token + token = self._runtime.get_config_value("GITHUB_GRAPHQL_TOKEN") or self._runtime.get_config_value("GITHUB_TOKEN") + if token: + return token + raise RuntimeError("REVIEWER_BOARD_TOKEN not set") + + +class FakeRuntimeReviewCompatibility: + def __init__(self, runtime: "FakeReviewerBotRuntime"): + self._runtime = runtime + + def maybe_record_head_observation_repair(self, issue_number: int, review_data: dict): + override = runtime_instance_override(self._runtime, "maybe_record_head_observation_repair") + if override is not None: + return override(issue_number, review_data) + return lifecycle_module.maybe_record_head_observation_repair(self._runtime, issue_number, review_data) + + def handle_transition_notice(self, state: dict, issue_number: int, reviewer: str) -> bool: + return lifecycle_module.handle_transition_notice(self._runtime, state, issue_number, reviewer) + + def ensure_review_entry(self, state: dict, issue_number: int, create: bool = False): + return review_state_module.ensure_review_entry(state, issue_number, create=create) + + def set_current_reviewer(self, state: dict, issue_number: int, reviewer: str, assignment_method: str | None = None) -> None: + return review_state_module.set_current_reviewer(state, issue_number, reviewer, assignment_method=assignment_method) + + def mark_review_complete(self, state: dict, issue_number: int, reviewer: str | None, source: str) -> bool: + return review_state_module.mark_review_complete(state, issue_number, reviewer, source) + + def update_reviewer_activity(self, state: dict, issue_number: int, reviewer: str) -> bool: + return review_state_module.update_reviewer_activity(state, issue_number, reviewer) + + def list_open_items_with_status_labels(self) -> list[int]: + return reviews_module.list_open_items_with_status_labels(self._runtime) + + def handle_pass_command(self, state: dict, issue_number: int, comment_author: str, reason: str | None, request=None): + return commands_module.handle_pass_command(self._runtime, state, issue_number, comment_author, reason, request=request) + + def handle_pass_until_command(self, state: dict, issue_number: int, comment_author: str, return_date: str, reason: str | None, request=None): + return commands_module.handle_pass_until_command(self._runtime, state, issue_number, comment_author, return_date, reason, request=request) + + def handle_label_command(self, state: dict, issue_number: int, label_string: str, request=None): + return commands_module.handle_label_command(self._runtime, state, issue_number, label_string, request=request) + + def handle_sync_members_command(self, state: dict): + return commands_module.handle_sync_members_command(self._runtime, state) + + def handle_queue_command(self, state: dict): + return commands_module.handle_queue_command(self._runtime, state) + + def handle_commands_command(self): + return commands_module.handle_commands_command(self._runtime) + + def handle_claim_command(self, state: dict, issue_number: int, comment_author: str, request=None): + return commands_module.handle_claim_command(self._runtime, state, issue_number, comment_author, request=request) + + def handle_release_command(self, state: dict, issue_number: int, comment_author: str, args=None, request=None): + return commands_module.handle_release_command(self._runtime, state, issue_number, comment_author, args, request=request) + + def handle_assign_command(self, state: dict, issue_number: int, username: str, request=None): + return commands_module.handle_assign_command(self._runtime, state, issue_number, username, request=request) + + def handle_assign_from_queue_command(self, state: dict, issue_number: int, request=None): + return commands_module.handle_assign_from_queue_command(self._runtime, state, issue_number, request=request) + + def handle_rectify_command(self, state: dict, issue_number: int, comment_author: str): + return reconcile_module.handle_rectify_command(self._runtime, state, issue_number, comment_author) + + def get_commands_help(self) -> str: + return config_module.get_commands_help() + + def get_next_reviewer(self, state: dict, skip_usernames=None): + return queue_module.get_next_reviewer(state, skip_usernames) + + def strip_code_blocks(self, comment_body: str) -> str: + return commands_module.strip_code_blocks(comment_body) + + def parse_command(self, comment_body: str): + return commands_module.parse_command(self._runtime, comment_body) + + def record_assignment(self, state: dict, github: str, issue_number: int, kind: str) -> None: + return queue_module.record_assignment(state, github, issue_number, kind) + + def reposition_member_as_next(self, state: dict, username: str) -> bool: + return queue_module.reposition_member_as_next(state, username) + + def compute_reviewer_response_state(self, issue_number: int, state: dict, *, issue_snapshot=None): + override = runtime_instance_override(self._runtime, "compute_reviewer_response_state") + if override is not None: + return override(issue_number, state, issue_snapshot=issue_snapshot) + return reviews_module.compute_reviewer_response_state(self._runtime, issue_number, state, issue_snapshot=issue_snapshot) + + +class FakeRuntimeStateLockCompatibility: + def __init__(self, runtime: "FakeReviewerBotRuntime"): + self._runtime = runtime + + def parse_iso8601_timestamp(self, value: Any): + return state_store_module.parse_iso8601_timestamp(value) + + def normalize_lock_metadata(self, lock_meta: dict | None): + return state_store_module.normalize_lock_metadata(lock_meta) + + def get_state_issue(self): + return state_store_module.get_state_issue(self._runtime) + + def clear_lock_metadata(self): + return lease_lock_module.clear_lock_metadata(self._runtime) + + def get_state_issue_snapshot(self): + return state_store_module.get_state_issue_snapshot(self._runtime) + + def conditional_patch_state_issue(self, body: str, etag: str | None = None): + return state_store_module.conditional_patch_state_issue(self._runtime, body, etag) + + def render_state_issue_body(self, state: dict, base_body: str | None = None, *, preserve_state_block: bool = False): + return state_store_module.render_state_issue_body(state, base_body, preserve_state_block=preserve_state_block) + + def get_state_issue_html_url(self): + return lease_lock_module.get_state_issue_html_url(self._runtime) + + def get_lock_ref_display(self): + return lease_lock_module.get_lock_ref_display(self._runtime) + + def get_lock_ref_snapshot(self): + return lease_lock_module.get_lock_ref_snapshot(self._runtime) + + def build_lock_metadata(self, *args, **kwargs): + return lease_lock_module.build_lock_metadata(self._runtime, *args, **kwargs) + + def create_lock_commit(self, parent_sha, tree_sha, lock_meta): + return lease_lock_module.create_lock_commit(self._runtime, parent_sha, tree_sha, lock_meta) + + def cas_update_lock_ref(self, new_sha): + return lease_lock_module.cas_update_lock_ref(self._runtime, new_sha) + + def lock_is_currently_valid(self, lock_meta: dict, now=None): + return lease_lock_module.lock_is_currently_valid(self._runtime, lock_meta, now) + + def renew_state_issue_lease_lock(self, context): + return lease_lock_module.renew_state_issue_lease_lock(self._runtime, context) + + def ensure_state_issue_lease_lock_fresh(self): + return self._runtime.locks.refresh() + + def acquire_state_issue_lease_lock(self): + return self._runtime.locks.acquire() + + def release_state_issue_lease_lock(self): + return self._runtime.locks.release() + + +class FakeRuntimeAutomationCompatibility: + def __init__(self, runtime: "FakeReviewerBotRuntime"): + self._runtime = runtime + + def run_command(self, command, cwd, check=False): + override = runtime_instance_override(self._runtime, "run_command") + if override is not None: + return override(command, cwd, check=check) + return automation_module.run_command(command, cwd=cwd, check=check) + + def summarize_output(self, result, limit: int = 20) -> str: + override = runtime_instance_override(self._runtime, "summarize_output") + if override is not None: + return override(result, limit) + return automation_module.summarize_output(result, limit=limit) + + def list_changed_files(self, repo_root): + override = runtime_instance_override(self._runtime, "list_changed_files") + if override is not None: + return override(repo_root) + return automation_module.list_changed_files(repo_root) + + def get_default_branch(self) -> str: + override = runtime_instance_override(self._runtime, "get_default_branch") + if override is not None: + return override() + return automation_module.get_default_branch(self._runtime) + + def find_open_pr_for_branch_status(self, branch: str): + override = runtime_instance_override(self._runtime, "find_open_pr_for_branch_status") + if override is not None: + return override(branch) + return automation_module.find_open_pr_for_branch_status(self._runtime, branch) + + def create_pull_request(self, branch: str, base: str, issue_number: int): + override = runtime_instance_override(self._runtime, "create_pull_request") + if override is not None: + return override(branch, base, issue_number) + return automation_module.create_pull_request(self._runtime, branch, base, issue_number) + + def fetch_members(self): + override = runtime_instance_override(self._runtime, "fetch_members") + if override is not None: + result = override() + else: + result = self._runtime._fetch_members() + if isinstance(result, list): + return MemberFetchResult(ok=True, producers=result) + return result + + def handle_accept_no_fls_changes_command(self, issue_number: int, comment_author: str, request=None): + return automation_module.handle_accept_no_fls_changes_command(self._runtime, issue_number, comment_author, request=request) + + +class FakeReviewerBotRuntime: + BOT_NAME = BOT_NAME + BOT_MENTION = BOT_MENTION + FLS_AUDIT_LABEL = FLS_AUDIT_LABEL + AUTHOR_ASSOCIATION_TRUST_ALLOWLIST = AUTHOR_ASSOCIATION_TRUST_ALLOWLIST + REVIEWER_REQUEST_422_TEMPLATE = REVIEWER_REQUEST_422_TEMPLATE + REVIEW_FRESHNESS_RUNBOOK_PATH = REVIEW_FRESHNESS_RUNBOOK_PATH + REVIEW_DEADLINE_DAYS = 14 + TRANSITION_PERIOD_DAYS = TRANSITION_PERIOD_DAYS + DEFERRED_DISCOVERY_OVERLAP_SECONDS = DEFERRED_DISCOVERY_OVERLAP_SECONDS + DEFERRED_DISCOVERY_BOOTSTRAP_WINDOW_SECONDS = DEFERRED_DISCOVERY_BOOTSTRAP_WINDOW_SECONDS + GitHubApiResult = GitHubApiResult + AssignmentAttempt = AssignmentAttempt + EVENT_INTENT_MUTATING = EVENT_INTENT_MUTATING + EVENT_INTENT_NON_MUTATING_DEFER = EVENT_INTENT_NON_MUTATING_DEFER + EVENT_INTENT_NON_MUTATING_READONLY = EVENT_INTENT_NON_MUTATING_READONLY + STATUS_PROJECTION_EPOCH = STATUS_PROJECTION_EPOCH + datetime = datetime + timezone = timezone + + def __init__(self, monkeypatch, *, github=None): + self.sys = sys + self.random = random + self.time = time + self.clock = FakeClock(datetime(2026, 1, 1, tzinfo=timezone.utc)) + self.sleeper = RecordingSleeper() + self.jitter = DeterministicJitter(0.0) + self.uuid_source = FixedUuidSource("fake-runtime-uuid") + self.logger = RecordingLogger() + self.ACTIVE_LEASE_CONTEXT = object() + self.config = ConfigBag(monkeypatch) + self.config.set("STATE_ISSUE_NUMBER", "314") + self.outputs = OutputCapture() + self.deferred_payloads = DeferredPayloadStore() + self.github = GitHubStub(github) + self.github.bind_runtime(self) + self.rest_transport = RestTransportStub(self.github) + self.graphql_transport = GraphQLTransportStub() + self.artifact_download_transport = ArtifactDownloadTransportStub() + self.touch_tracker = TouchTrackerStub() + self.state_store = StateStoreStub() + self.locks = LockStub() + self.workflow = WorkflowBehaviorStub() + self.workflow.bind_runtime(self) + self._fetch_members = lambda: MemberFetchResult(ok=True, producers=[]) + self.compat = SimpleNamespace( + github=FakeRuntimeGitHubCompatibility(self), + review=FakeRuntimeReviewCompatibility(self), + state_lock=FakeRuntimeStateLockCompatibility(self), + automation=FakeRuntimeAutomationCompatibility(self), + ) + self.adapters = FakeRuntimeAdapterServices(self) + self.handlers = HandlerStub(build_default_handler_map(self)) + self.infra = FakeRuntimeInfraServices( + config=self.config, + outputs=self.outputs, + deferred_payloads=self.deferred_payloads, + logger=self.logger, + rest_transport=self.rest_transport, + graphql_transport=self.graphql_transport, + artifact_download_transport=self.artifact_download_transport, + clock=self.clock, + sleeper=self.sleeper, + jitter=self.jitter, + uuid_source=self.uuid_source, + touch_tracker=self.touch_tracker, + ) + self.domain = FakeRuntimeDomainServices( + state_store=self.state_store, + github=self.github, + locks=self.locks, + handlers=self.handlers, + workflow=self.workflow, + adapters=self.adapters, + compat=self.compat, + ) + + def get_config_value(self, name: str, default: str = "") -> str: + return self.config.get(name, default) + + def set_config_value(self, name: str, value: Any) -> None: + self.config.set(name, value) + + def get_github_token(self) -> str: + token = self.get_config_value("GITHUB_TOKEN") + if not token: + raise SystemExit(1) + return token + + def state_issue_number(self) -> int: + return int(self.get_config_value("STATE_ISSUE_NUMBER", "0") or 0) + + def state_read_retry_limit(self) -> int: + return int(self.get_config_value(STATE_READ_RETRY_LIMIT_ENV, str(STATE_READ_RETRY_LIMIT)) or 0) + + def state_read_retry_base_seconds(self) -> float: + return float(self.get_config_value(STATE_READ_RETRY_BASE_SECONDS_ENV, str(STATE_READ_RETRY_BASE_SECONDS)) or 0.0) + + def lock_api_retry_limit(self) -> int: + return int(self.get_config_value(LOCK_API_RETRY_LIMIT_ENV, str(LOCK_API_RETRY_LIMIT)) or 0) + + def lock_retry_base_seconds(self) -> float: + return float(self.get_config_value(LOCK_RETRY_BASE_SECONDS_ENV, str(LOCK_RETRY_BASE_SECONDS)) or 0.0) + + def lock_lease_ttl_seconds(self) -> int: + return int(self.get_config_value(LOCK_LEASE_TTL_SECONDS_ENV, str(LOCK_LEASE_TTL_SECONDS)) or 0) + + def lock_max_wait_seconds(self) -> int: + return int(self.get_config_value(LOCK_MAX_WAIT_SECONDS_ENV, str(LOCK_MAX_WAIT_SECONDS)) or 0) + + def lock_renewal_window_seconds(self) -> int: + return int(self.get_config_value(LOCK_RENEWAL_WINDOW_SECONDS_ENV, str(LOCK_RENEWAL_WINDOW_SECONDS)) or 0) + + def lock_ref_name(self) -> str: + return self.get_config_value(LOCK_REF_NAME_ENV, f"refs/{LOCK_REF_NAME}") + + def lock_ref_bootstrap_branch(self) -> str: + return self.get_config_value(LOCK_REF_BOOTSTRAP_BRANCH_ENV, LOCK_REF_BOOTSTRAP_BRANCH) + + def write_output(self, name: str, value: str) -> None: + self.outputs.write(name, value) + + def assert_lock_held(self, context: str) -> None: + if self.ACTIVE_LEASE_CONTEXT is None: + raise RuntimeError(f"Mutating path reached without lease lock: {context}") + + def load_deferred_payload(self) -> dict: + return self.deferred_payloads.load() + + def parse_iso8601_timestamp(self, value: Any): + return self.compat.state_lock.parse_iso8601_timestamp(value) + + def satisfy_mandatory_approver_requirement(self, state: dict, issue_number: int, approver: str) -> bool: + return reviews_module.satisfy_mandatory_approver_requirement(self, state, issue_number, approver) + + def load_state(self, *, fail_on_unavailable: bool = False) -> dict: + return self.state_store.load_state(fail_on_unavailable=fail_on_unavailable) + + def save_state(self, state: dict) -> bool: + return self.state_store.save_state(state) + + def ensure_state_issue_lease_lock_fresh(self) -> bool: + return self.compat.state_lock.ensure_state_issue_lease_lock_fresh() + + def acquire_state_issue_lease_lock(self): + context = self.compat.state_lock.acquire_state_issue_lease_lock() + if context is not None: + self.ACTIVE_LEASE_CONTEXT = context + return context + + def release_state_issue_lease_lock(self) -> bool: + released = self.compat.state_lock.release_state_issue_lease_lock() + if released: + self.ACTIVE_LEASE_CONTEXT = None + return released + + def process_pass_until_expirations(self, state: dict): + return self.workflow.process_pass_until_expirations(state) + + def sync_members_with_queue(self, state: dict): + return self.workflow.sync_members_with_queue(state) + + def sync_status_labels_for_items(self, state: dict, issue_numbers): + return self.workflow.sync_status_labels_for_items(state, issue_numbers) + + def ensure_review_entry(self, state: dict, issue_number: int, create: bool = False): + return review_state_module.ensure_review_entry(state, issue_number, create=create) + + def get_issue_assignees(self, issue_number: int): + return self.compat.github.get_issue_assignees(issue_number) + + def request_pr_reviewer_assignment(self, issue_number: int, username: str): + return self.compat.github.request_pr_reviewer_assignment(issue_number, username) + + def assign_issue_assignee(self, issue_number: int, username: str): + return self.compat.github.assign_issue_assignee(issue_number, username) + + def post_comment(self, issue_number: int, body: str) -> bool: + return self.compat.github.post_comment(issue_number, body) + + def add_reaction(self, comment_id: int, reaction: str) -> bool: + return self.compat.github.add_reaction(comment_id, reaction) + + def get_user_permission_status(self, username: str, required_permission: str = "triage") -> str: + return self.compat.github.get_user_permission_status(username, required_permission) + + def check_user_permission(self, username: str, required_permission: str = "triage"): + return self.compat.github.check_user_permission(username, required_permission) + + def get_repo_labels(self): + return self.compat.github.get_repo_labels() + + def add_label(self, issue_number: int, label: str) -> bool: + return self.compat.github.add_label(issue_number, label) + + def remove_label(self, issue_number: int, label: str) -> bool: + return self.compat.github.remove_label(issue_number, label) + + def remove_issue_assignee(self, issue_number: int, username: str) -> bool: + return self.compat.github.remove_issue_assignee(issue_number, username) + + def remove_pr_reviewer(self, issue_number: int, username: str) -> bool: + return self.compat.github.remove_pr_reviewer(issue_number, username) + + def get_issue_or_pr_snapshot(self, issue_number: int) -> dict | None: + return self.compat.github.get_issue_or_pr_snapshot(issue_number) + + def get_pull_request_reviews(self, issue_number: int): + return self.compat.github.get_pull_request_reviews(issue_number) + + def list_open_items_with_status_labels(self) -> list[int]: + return self.compat.review.list_open_items_with_status_labels() + + def maybe_record_head_observation_repair(self, issue_number: int, review_data: dict): + return self.compat.review.maybe_record_head_observation_repair(issue_number, review_data) + + def set_current_reviewer(self, state: dict, issue_number: int, reviewer: str, assignment_method: str | None = None) -> None: + return self.compat.review.set_current_reviewer(state, issue_number, reviewer, assignment_method=assignment_method) + + def mark_review_complete(self, state: dict, issue_number: int, reviewer: str | None, source: str) -> bool: + return self.compat.review.mark_review_complete(state, issue_number, reviewer, source) + + def update_reviewer_activity(self, state: dict, issue_number: int, reviewer: str) -> bool: + return self.compat.review.update_reviewer_activity(state, issue_number, reviewer) + + def accept_reviewer_review_from_live_review(self, review_data: dict, review: dict, *, actor: str | None = None) -> bool: + return review_state_module.accept_reviewer_review_from_live_review(review_data, review, actor=actor) + + def refresh_reviewer_review_from_live_preferred_review(self, issue_number: int, review_data: dict, *, pull_request=None, reviews=None, actor: str | None = None): + return review_state_module.refresh_reviewer_review_from_live_preferred_review( + self, + issue_number, + review_data, + pull_request=pull_request, + reviews=reviews, + actor=actor, + ) + + def repair_missing_reviewer_review_state(self, issue_number: int, review_data: dict, *, reviews=None) -> bool: + return review_state_module.repair_missing_reviewer_review_state(self, issue_number, review_data, reviews=reviews) + + def list_open_tracked_review_items(self, state: dict) -> list[int]: + return review_state_module.list_open_tracked_review_items(state) + + def semantic_key_seen(self, review_data: dict, channel_name: str, semantic_key: str) -> bool: + return review_state_module.semantic_key_seen(review_data, channel_name, semantic_key) + + def get_next_reviewer(self, state: dict, skip_usernames=None): + return self.compat.review.get_next_reviewer(state, skip_usernames) + + def strip_code_blocks(self, comment_body: str) -> str: + return self.compat.review.strip_code_blocks(comment_body) + + def parse_command(self, comment_body: str): + return self.compat.review.parse_command(comment_body) + + def record_assignment(self, state: dict, github: str, issue_number: int, kind: str) -> None: + return self.compat.review.record_assignment(state, github, issue_number, kind) + + def reposition_member_as_next(self, state: dict, username: str) -> bool: + return self.compat.review.reposition_member_as_next(state, username) + + def compute_reviewer_response_state(self, issue_number: int, state: dict, *, issue_snapshot=None): + return self.compat.review.compute_reviewer_response_state(issue_number, state, issue_snapshot=issue_snapshot) + + def rebuild_pr_approval_state(self, *args, pull_request=None, reviews=None): + if len(args) == 2: + issue_number, review_data = args + elif len(args) == 3: + _bot, issue_number, review_data = args + else: + raise TypeError("unexpected rebuild_pr_approval_state args") + return reviews_module.rebuild_pr_approval_state( + self, + issue_number, + review_data, + pull_request=pull_request, + reviews=reviews, + ) + + def get_github_graphql_token(self, *, prefer_board_token: bool = False) -> str: + return self.compat.github.get_github_graphql_token(prefer_board_token=prefer_board_token) + + def github_graphql(self, query: str, variables=None, *, token=None): + return self.compat.github.github_graphql(query, variables, token=token) + + def handle_transition_notice(self, state: dict, issue_number: int, reviewer: str) -> bool: + return self.compat.review.handle_transition_notice(state, issue_number, reviewer) + + def handle_pass_command(self, state: dict, issue_number: int, comment_author: str, reason: str | None, request=None): + return self.compat.review.handle_pass_command(state, issue_number, comment_author, reason, request=request) + + def handle_pass_until_command(self, state: dict, issue_number: int, comment_author: str, return_date: str, reason: str | None, request=None): + return self.compat.review.handle_pass_until_command(state, issue_number, comment_author, return_date, reason, request=request) + + def handle_label_command(self, state: dict, issue_number: int, label_string: str, request=None): + return self.compat.review.handle_label_command(state, issue_number, label_string, request=request) + + def handle_sync_members_command(self, state: dict): + return self.compat.review.handle_sync_members_command(state) + + def handle_queue_command(self, state: dict): + return self.compat.review.handle_queue_command(state) + + def handle_commands_command(self): + return self.compat.review.handle_commands_command() + + def handle_claim_command(self, state: dict, issue_number: int, comment_author: str, request=None): + return self.compat.review.handle_claim_command(state, issue_number, comment_author, request=request) + + def handle_release_command(self, state: dict, issue_number: int, comment_author: str, args=None, request=None): + return self.compat.review.handle_release_command(state, issue_number, comment_author, args, request=request) + + def handle_assign_command(self, state: dict, issue_number: int, username: str, request=None): + return self.compat.review.handle_assign_command(state, issue_number, username, request=request) + + def handle_assign_from_queue_command(self, state: dict, issue_number: int, request=None): + return self.compat.review.handle_assign_from_queue_command(state, issue_number, request=request) + + def handle_accept_no_fls_changes_command(self, issue_number: int, comment_author: str, request=None): + return self.compat.automation.handle_accept_no_fls_changes_command(issue_number, comment_author, request=request) + + def handle_rectify_command(self, state: dict, issue_number: int, comment_author: str): + return self.compat.review.handle_rectify_command(state, issue_number, comment_author) + + def get_commands_help(self) -> str: + return self.compat.review.get_commands_help() + + def fetch_members(self): + return self.compat.automation.fetch_members() + + def run_command(self, command, cwd, check=False): + return self.compat.automation.run_command(command, cwd, check=check) + + def summarize_output(self, result, limit: int = 20) -> str: + return self.compat.automation.summarize_output(result, limit=limit) + + def list_changed_files(self, repo_root): + return self.compat.automation.list_changed_files(repo_root) + + def get_default_branch(self) -> str: + return self.compat.automation.get_default_branch() + + def find_open_pr_for_branch_status(self, branch: str): + return self.compat.automation.find_open_pr_for_branch_status(branch) + + def create_pull_request(self, branch: str, base: str, issue_number: int): + return self.compat.automation.create_pull_request(branch, base, issue_number) + + def handle_issue_or_pr_opened(self, state: dict) -> bool: + return self.handlers.call("handle_issue_or_pr_opened", state) + + def handle_labeled_event(self, state: dict) -> bool: + return self.handlers.call("handle_labeled_event", state) + + def handle_issue_edited_event(self, state: dict) -> bool: + return self.handlers.call("handle_issue_edited_event", state) + + def handle_closed_event(self, state: dict) -> bool: + return self.handlers.call("handle_closed_event", state) + + def handle_pull_request_target_synchronize(self, state: dict) -> bool: + return self.handlers.call("handle_pull_request_target_synchronize", state) + + def handle_comment_event(self, state: dict) -> bool: + return self.handlers.call("handle_comment_event", state) + + def handle_manual_dispatch(self, state: dict) -> bool: + return self.handlers.call("handle_manual_dispatch", state) + + def handle_scheduled_check_result(self, state: dict): + return self.handlers.call("handle_scheduled_check_result", state) + + def github_api(self, method: str, endpoint: str, data=None): + override = runtime_instance_override(self, "github_api") + if override is not None: + return override(method, endpoint, data=data) + return self.github.github_api(method, endpoint, data=data) + + def github_api_request(self, method: str, endpoint: str, data=None, extra_headers=None, **kwargs): + override = runtime_instance_override(self, "github_api_request") + if override is not None: + return override(method, endpoint, data=data, extra_headers=extra_headers, **kwargs) + return self.github.github_api_request( + method, + endpoint, + data=data, + extra_headers=extra_headers, + **kwargs, + ) + + def collect_touched_item(self, issue_number: int) -> None: + self.touch_tracker.collect(issue_number) + + def drain_touched_items(self) -> list[int]: + return self.touch_tracker.drain() + + def stub_lock(self, *, acquire=None, release=None, refresh=None) -> None: + self.locks.stub(acquire=acquire, release=release, refresh=refresh) + + def stub_deferred_payload(self, payload: dict) -> None: + self.deferred_payloads.set_payload(payload) + + def stub_state_sequence(self, *states: dict) -> None: + self.state_store.stub_state_sequence(*states) + + def stub_state_unavailable(self, message: str = "state unavailable") -> None: + self.state_store.stub_state_unavailable(message) + + def record_saves(self, snapshots: list) -> None: + self.state_store.record_saves(snapshots) + + def stub_pass_until(self, func: Callable[[dict], tuple[dict, list[str]]]) -> None: + self.workflow.stub_pass_until(func) + + def stub_sync_members(self, func: Callable[[dict], tuple[dict, list[str]]]) -> None: + self.workflow.stub_sync_members(func) + + def stub_sync_status_labels(self, func: Callable[[dict, Any], bool]) -> None: + self.workflow.stub_sync_status_labels(func) + + def stub_fetch_members(self, func: Callable[[], MemberFetchResult | list[dict]]) -> None: + self._fetch_members = func diff --git a/tests/fixtures/fake_sleeper.py b/tests/fixtures/fake_sleeper.py new file mode 100644 index 000000000..7be0e4288 --- /dev/null +++ b/tests/fixtures/fake_sleeper.py @@ -0,0 +1,9 @@ +from __future__ import annotations + + +class RecordingSleeper: + def __init__(self): + self.calls: list[float] = [] + + def sleep(self, seconds: float) -> None: + self.calls.append(seconds) diff --git a/tests/fixtures/fake_uuid.py b/tests/fixtures/fake_uuid.py new file mode 100644 index 000000000..dc7ddf274 --- /dev/null +++ b/tests/fixtures/fake_uuid.py @@ -0,0 +1,19 @@ +from __future__ import annotations + + +class FixedUuidSource: + def __init__(self, values: str | list[str] = "fixed-uuid"): + self._values = [values] if isinstance(values, str) else list(values) + self.issued: list[str] = [] + self._index = 0 + + def uuid4_hex(self) -> str: + if not self._values: + value = "fixed-uuid" + elif self._index >= len(self._values): + value = self._values[-1] + else: + value = self._values[self._index] + self._index += 1 + self.issued.append(value) + return value diff --git a/tests/fixtures/focused_fake_services.py b/tests/fixtures/focused_fake_services.py new file mode 100644 index 000000000..6ac089d8c --- /dev/null +++ b/tests/fixtures/focused_fake_services.py @@ -0,0 +1,525 @@ +from __future__ import annotations + +import json +from copy import deepcopy +from typing import Any, Callable +from urllib.parse import urlparse + + +class ConfigBag: + def __init__(self, monkeypatch): + self._monkeypatch = monkeypatch + self.values: dict[str, str] = {} + + def get(self, name: str, default: str = "") -> str: + return self.values.get(name, default) + + def set(self, name: str, value) -> None: + rendered = str(value) + self.values[name] = rendered + + +class OutputCapture: + def __init__(self): + self.writes: list[tuple[str, str]] = [] + + def write(self, name: str, value: str) -> None: + self.writes.append((name, value)) + + +class DeferredPayloadStore: + def __init__(self): + self._payload: dict = {} + + def set_payload(self, payload: dict) -> None: + self._payload = payload + + def load(self) -> dict: + return self._payload + + +class StateStoreStub: + def __init__(self): + self._load: Callable[..., dict] = lambda *, fail_on_unavailable=False: {"active_reviews": {}} + self._save: Callable[[dict], bool] = lambda state: True + self.load_calls: list[dict[str, Any]] = [] + self.save_calls: list[dict] = [] + + def stub_load(self, func: Callable[..., dict]) -> None: + self._load = func + + def stub_save(self, func: Callable[[dict], bool]) -> None: + self._save = func + + def stub_state_sequence(self, *states: dict) -> None: + state_queue = [deepcopy(state) for state in states] + + def fake_load_state(*, fail_on_unavailable: bool = False): + del fail_on_unavailable + if not state_queue: + raise AssertionError("No more fake states queued") + if len(state_queue) == 1: + return state_queue[0] + return state_queue.pop(0) + + self.stub_load(fake_load_state) + + def stub_state_unavailable(self, message: str = "state unavailable") -> None: + def fake_load_state(*, fail_on_unavailable: bool = False): + assert fail_on_unavailable is True + raise RuntimeError(message) + + self.stub_load(fake_load_state) + + def record_saves(self, snapshots: list) -> None: + def fake_save_state(state: dict) -> bool: + snapshots.append(json.loads(json.dumps(state))) + return True + + self.stub_save(fake_save_state) + + def load_state(self, *, fail_on_unavailable: bool = False) -> dict: + self.load_calls.append({"fail_on_unavailable": fail_on_unavailable}) + return self._load(fail_on_unavailable=fail_on_unavailable) + + def save_state(self, state: dict) -> bool: + self.save_calls.append(json.loads(json.dumps(state))) + return self._save(state) + + +class LockStub: + def __init__(self): + self._acquire: Callable[[], Any] = lambda: None + self._release: Callable[[], bool] = lambda: True + self._refresh: Callable[[], bool] = lambda: True + self.calls: list[str] = [] + + def stub(self, *, acquire=None, release=None, refresh=None) -> None: + if acquire is not None: + self._acquire = acquire + if release is not None: + self._release = release + if refresh is not None: + self._refresh = refresh + + def acquire(self): + self.calls.append("acquire") + return self._acquire() + + def release(self) -> bool: + self.calls.append("release") + return self._release() + + def refresh(self) -> bool: + self.calls.append("refresh") + return self._refresh() + + +class GitHubStub: + def __init__(self, github=None): + self._github = github + self._runtime = None + + def bind_runtime(self, runtime) -> None: + self._runtime = runtime + + def stub(self, github) -> None: + self._github = github + + def github_api(self, method: str, endpoint: str, data=None): + if self._github is None: + raise AssertionError(f"No GitHub stub configured for {method} {endpoint}") + return self._github.github_api(method, endpoint, data=data) + + def github_api_request(self, method: str, endpoint: str, data=None, extra_headers=None, **kwargs): + if self._github is None: + raise AssertionError(f"No GitHub request stub configured for {method} {endpoint}") + return self._github.github_api_request( + method, + endpoint, + data=data, + extra_headers=extra_headers, + **kwargs, + ) + + def _runtime_required(self): + if self._runtime is None: + raise AssertionError("GitHubStub runtime not bound") + return self._runtime + + def _instance_override(self, name: str): + runtime = self._runtime_required() + override = runtime.__dict__.get(name) + return override if callable(override) else None + + def get_github_token(self): + override = self._instance_override("get_github_token") + if override is not None: + return override() + from scripts.reviewer_bot_lib import github_api as github_api_module + + return github_api_module.get_github_token(self._runtime_required()) + + def get_github_graphql_token(self, *, prefer_board_token=False): + override = self._instance_override("get_github_graphql_token") + if override is not None: + return override(prefer_board_token=prefer_board_token) + from scripts.reviewer_bot_lib import github_api as github_api_module + + return github_api_module.get_github_graphql_token(self._runtime_required(), prefer_board_token=prefer_board_token) + + def github_graphql(self, query, variables=None, *, token=None): + override = self._instance_override("github_graphql") + if override is not None: + return override(query, variables, token=token) + from scripts.reviewer_bot_lib import github_api as github_api_module + + return github_api_module.github_graphql(self._runtime_required(), query, variables, token=token) + + def post_comment(self, issue_number: int, body: str): + override = self._instance_override("post_comment") + if override is not None: + return override(issue_number, body) + from scripts.reviewer_bot_lib import github_api as github_api_module + + return github_api_module.post_comment(self._runtime_required(), issue_number, body) + + def get_repo_labels(self): + override = self._instance_override("get_repo_labels") + if override is not None: + return override() + from scripts.reviewer_bot_lib import github_api as github_api_module + + return github_api_module.get_repo_labels(self._runtime_required()) + + def add_label(self, issue_number: int, label: str): + override = self._instance_override("add_label") + if override is not None: + return override(issue_number, label) + from scripts.reviewer_bot_lib import github_api as github_api_module + + return github_api_module.add_label(self._runtime_required(), issue_number, label) + + def remove_label(self, issue_number: int, label: str): + override = self._instance_override("remove_label") + if override is not None: + return override(issue_number, label) + from scripts.reviewer_bot_lib import github_api as github_api_module + + return github_api_module.remove_label(self._runtime_required(), issue_number, label) + + def ensure_label_exists(self, label: str, *, color=None, description=None): + override = self._instance_override("ensure_label_exists") + if override is not None: + return override(label, color=color, description=description) + from scripts.reviewer_bot_lib import github_api as github_api_module + + return github_api_module.ensure_label_exists(self._runtime_required(), label, color=color, description=description) + + def get_issue_assignees(self, issue_number: int): + override = self._instance_override("get_issue_assignees") + if override is not None: + return override(issue_number) + from scripts.reviewer_bot_lib import github_api as github_api_module + + return github_api_module.get_issue_assignees(self._runtime_required(), issue_number) + + def request_pr_reviewer_assignment(self, issue_number: int, username: str): + override = self._instance_override("request_pr_reviewer_assignment") + if override is not None: + return override(issue_number, username) + from scripts.reviewer_bot_lib import github_api as github_api_module + + return github_api_module.request_pr_reviewer_assignment(self._runtime_required(), issue_number, username) + + def assign_issue_assignee(self, issue_number: int, username: str): + override = self._instance_override("assign_issue_assignee") + if override is not None: + return override(issue_number, username) + from scripts.reviewer_bot_lib import github_api as github_api_module + + return github_api_module.assign_issue_assignee(self._runtime_required(), issue_number, username) + + def get_assignment_failure_comment(self, reviewer: str, attempt): + override = self._instance_override("get_assignment_failure_comment") + if override is not None: + return override(reviewer, attempt) + return None + + def add_reaction(self, comment_id: int, reaction: str): + override = self._instance_override("add_reaction") + if override is not None: + return override(comment_id, reaction) + from scripts.reviewer_bot_lib import github_api as github_api_module + + return github_api_module.add_reaction(self._runtime_required(), comment_id, reaction) + + def remove_issue_assignee(self, issue_number: int, username: str): + override = self._instance_override("remove_issue_assignee") + if override is None: + override = self._instance_override("unassign_reviewer") + if override is not None: + return override(issue_number, username) + from scripts.reviewer_bot_lib import github_api as github_api_module + + return github_api_module.remove_issue_assignee(self._runtime_required(), issue_number, username) + + def remove_pr_reviewer(self, issue_number: int, username: str): + override = self._instance_override("remove_pr_reviewer") + if override is None: + override = self._instance_override("unassign_reviewer") + if override is not None: + return override(issue_number, username) + from scripts.reviewer_bot_lib import github_api as github_api_module + + return github_api_module.remove_pr_reviewer(self._runtime_required(), issue_number, username) + + def get_user_permission_status(self, username: str, required_permission="triage"): + override = self._instance_override("get_user_permission_status") + if override is not None: + return override(username, required_permission) + from scripts.reviewer_bot_lib import github_api as github_api_module + + return github_api_module.get_user_permission_status(self._runtime_required(), username, required_permission) + + def check_user_permission(self, username: str, required_permission="triage"): + override = self._instance_override("check_user_permission") + if override is not None: + return override(username, required_permission) + from scripts.reviewer_bot_lib import github_api as github_api_module + + return github_api_module.check_user_permission(self._runtime_required(), username, required_permission) + + def get_issue_or_pr_snapshot(self, issue_number: int): + override = self._instance_override("get_issue_or_pr_snapshot") + if override is not None: + return override(issue_number) + return self.github_api("GET", f"issues/{issue_number}") + + def get_pull_request_reviews(self, issue_number: int): + override = self._instance_override("get_pull_request_reviews") + if override is not None: + return override(issue_number) + from scripts.reviewer_bot_lib import reviews as reviews_module + + return reviews_module.get_pull_request_reviews(self._runtime_required(), issue_number) + + +class RestTransportStub: + def __init__(self, github_stub: GitHubStub): + self._github = github_stub + self.calls: list[dict[str, Any]] = [] + self._direct_request: Callable[..., Any] | None = None + + def stub(self, func: Callable[..., Any]) -> None: + self._direct_request = func + + def request(self, method: str, url: str, *, headers=None, json_data=None, timeout_seconds=None): + self.calls.append( + { + "method": method, + "url": url, + "headers": headers, + "json_data": json_data, + "timeout_seconds": timeout_seconds, + } + ) + parsed = urlparse(url) + parts = parsed.path.strip("/").split("/") + if self._direct_request is not None and (len(parts) < 4 or parts[0] != "repos"): + return self._direct_request( + method=method, + url=url, + headers=headers, + json_data=json_data, + timeout_seconds=timeout_seconds, + ) + if len(parts) >= 4 and parts[0] == "repos": + endpoint = "/".join(parts[3:]) + else: + endpoint = parsed.path.lstrip("/") + result = self._github.github_api_request(method, endpoint, data=json_data) + + class _Response: + def __init__(self, api_result): + self.status_code = api_result.status_code or 0 + self.headers = api_result.headers + self.text = api_result.text + self._payload = api_result.payload + if api_result.payload is None: + self.content = b"" + elif isinstance(api_result.payload, Exception): + self.content = b"invalid-json" + else: + self.content = json.dumps(api_result.payload).encode("utf-8") + + def json(self): + if isinstance(self._payload, Exception): + raise self._payload + return self._payload + + return _Response(result) + + +class GraphQLTransportStub: + def __init__(self): + self._query: Callable[..., Any] | None = lambda **kwargs: (_ for _ in ()).throw(AssertionError("No GraphQL stub configured")) + self._sequence: list[Any] | None = None + self.calls: list[dict[str, Any]] = [] + + def stub(self, func: Callable[..., Any]) -> None: + self._sequence = None + self._query = func + + def stub_sequence(self, responses: list[Any]) -> None: + if not responses: + raise ValueError("response sequence cannot be empty") + self._query = None + self._sequence = list(responses) + + def query(self, url: str, *, headers=None, query: str, variables=None, timeout_seconds=None): + self.calls.append( + { + "url": url, + "headers": headers, + "query": query, + "variables": variables, + "timeout_seconds": timeout_seconds, + } + ) + if self._sequence is not None: + response = self._sequence[0] + if len(self._sequence) > 1: + del self._sequence[0] + if isinstance(response, Exception): + raise response + return response + assert self._query is not None + return self._query(url=url, headers=headers, query=query, variables=variables, timeout_seconds=timeout_seconds) + + +class ArtifactDownloadTransportStub: + def __init__(self): + self._download: Callable[..., Any] | None = lambda **kwargs: (_ for _ in ()).throw(AssertionError("No artifact download stub configured")) + self._sequence: list[Any] | None = None + self.calls: list[dict[str, Any]] = [] + + def stub(self, func: Callable[..., Any]) -> None: + self._sequence = None + self._download = func + + def stub_sequence(self, responses: list[Any]) -> None: + if not responses: + raise ValueError("response sequence cannot be empty") + self._download = None + self._sequence = list(responses) + + def download(self, url: str, *, headers=None, timeout_seconds=None): + self.calls.append({"url": url, "headers": headers, "timeout_seconds": timeout_seconds}) + if self._sequence is not None: + response = self._sequence[0] + if len(self._sequence) > 1: + del self._sequence[0] + if isinstance(response, Exception): + raise response + return response + assert self._download is not None + return self._download(url=url, headers=headers, timeout_seconds=timeout_seconds) + + +class HandlerStub: + ALLOWED = { + "handle_issue_or_pr_opened", + "handle_labeled_event", + "handle_issue_edited_event", + "handle_closed_event", + "handle_pull_request_target_synchronize", + "handle_comment_event", + "handle_manual_dispatch", + "handle_scheduled_check_result", + } + + def __init__(self, defaults: dict[str, Callable[[dict], bool]]): + self._handlers: dict[str, Callable[[dict], bool]] = defaults + + def stub(self, name: str, func: Callable[[dict], bool]) -> None: + if name not in self.ALLOWED: + raise AssertionError(f"Unsupported runtime handler override: {name}") + self._handlers[name] = func + + def call(self, name: str, state: dict) -> bool: + return self._handlers[name](state) + + def __getattr__(self, name: str): + if name in self.ALLOWED: + return lambda state: self._handlers[name](state) + raise AttributeError(name) + + +class TouchTrackerStub: + def __init__(self): + self._touched: list[int] = [] + + def collect(self, issue_number: int | None) -> None: + if isinstance(issue_number, int) and issue_number not in self._touched: + self._touched.append(issue_number) + + def drain(self) -> list[int]: + items = list(self._touched) + self._touched.clear() + return items + + +class WorkflowBehaviorStub: + def __init__(self): + self._runtime = None + self._process_pass_until: Callable[[dict], tuple[dict, list[str]]] = lambda state: (state, []) + self._sync_members: Callable[[dict], tuple[dict, list[str]]] = lambda state: (state, []) + self._sync_status_labels: Callable[[dict, Any], bool] = lambda state, issue_numbers: False + self.calls: list[dict[str, Any]] = [] + + def bind_runtime(self, runtime) -> None: + self._runtime = runtime + + def process_pass_until_expirations(self, state: dict): + self.calls.append({"name": "process_pass_until_expirations", "state": deepcopy(state)}) + return self._process_pass_until(state) + + def sync_members_with_queue(self, state: dict): + self.calls.append({"name": "sync_members_with_queue", "state": deepcopy(state)}) + return self._sync_members(state) + + def sync_status_labels_for_items(self, state: dict, issue_numbers): + self.calls.append({"name": "sync_status_labels_for_items", "state": deepcopy(state), "issue_numbers": list(issue_numbers)}) + return self._sync_status_labels(state, issue_numbers) + + def fetch_members(self): + if self._runtime is None: + raise AssertionError("WorkflowBehaviorStub runtime not bound") + return self._runtime.compat.automation.fetch_members() + + def stub_pass_until(self, func: Callable[[dict], tuple[dict, list[str]]]) -> None: + self._process_pass_until = func + + def stub_sync_members(self, func: Callable[[dict], tuple[dict, list[str]]]) -> None: + self._sync_members = func + + def stub_sync_status_labels(self, func: Callable[[dict, Any], bool]) -> None: + self._sync_status_labels = func + + +def build_default_handler_map(runtime) -> dict[str, Callable[[dict], bool]]: + from scripts.reviewer_bot_lib import comment_routing as comment_routing_module + from scripts.reviewer_bot_lib import lifecycle as lifecycle_module + from scripts.reviewer_bot_lib import maintenance as maintenance_module + + return { + "handle_issue_or_pr_opened": lambda state: lifecycle_module.handle_issue_or_pr_opened(runtime, state), + "handle_labeled_event": lambda state: lifecycle_module.handle_labeled_event(runtime, state), + "handle_issue_edited_event": lambda state: lifecycle_module.handle_issue_edited_event(runtime, state), + "handle_closed_event": lambda state: lifecycle_module.handle_closed_event(runtime, state), + "handle_pull_request_target_synchronize": lambda state: lifecycle_module.handle_pull_request_target_synchronize(runtime, state), + "handle_comment_event": lambda state: comment_routing_module.handle_comment_event(runtime, state), + "handle_manual_dispatch": lambda state: maintenance_module.handle_manual_dispatch(runtime, state), + "handle_scheduled_check_result": lambda state: maintenance_module.handle_scheduled_check_result(runtime, state), + } diff --git a/tests/fixtures/http_responses.py b/tests/fixtures/http_responses.py new file mode 100644 index 000000000..37516840a --- /dev/null +++ b/tests/fixtures/http_responses.py @@ -0,0 +1,15 @@ +class FakeGitHubResponse: + def __init__(self, status_code, payload=None, text="", headers=None): + self.status_code = status_code + self._payload = payload + self.text = text + self.headers = headers or {} + self.content = b"" if payload is None and not text else b"x" + + def json(self): + if isinstance(self._payload, Exception): + raise self._payload + return self._payload + + +__all__ = ["FakeGitHubResponse"] diff --git a/tests/fixtures/observer_payloads/helper_pr_comment_automation_noop.json b/tests/fixtures/observer_payloads/helper_pr_comment_automation_noop.json new file mode 100644 index 000000000..056ce2f40 --- /dev/null +++ b/tests/fixtures/observer_payloads/helper_pr_comment_automation_noop.json @@ -0,0 +1,21 @@ +{ + "fixture_metadata": { + "fixture_id": "helper_pr_comment_automation_noop", + "contract_class": "python_helper_output", + "contract_source": "production Python helper", + "source_function": "scripts.reviewer_bot_lib.comment_routing.build_pr_comment_observer_payload" + }, + "payload": { + "schema_version": 1, + "kind": "observer_noop", + "reason": "ignored_non_human_automation", + "source_workflow_name": "Reviewer Bot PR Comment Observer", + "source_workflow_file": ".github/workflows/reviewer-bot-pr-comment-observer.yml", + "source_run_id": 998, + "source_run_attempt": 2, + "source_event_name": "issue_comment", + "source_event_action": "created", + "source_event_key": "issue_comment:101", + "pr_number": 42 + } +} diff --git a/tests/fixtures/observer_payloads/helper_pr_comment_trusted_direct_noop.json b/tests/fixtures/observer_payloads/helper_pr_comment_trusted_direct_noop.json new file mode 100644 index 000000000..308145f4f --- /dev/null +++ b/tests/fixtures/observer_payloads/helper_pr_comment_trusted_direct_noop.json @@ -0,0 +1,21 @@ +{ + "fixture_metadata": { + "fixture_id": "helper_pr_comment_trusted_direct_noop", + "contract_class": "python_helper_output", + "contract_source": "production Python helper", + "source_function": "scripts.reviewer_bot_lib.comment_routing.build_pr_comment_observer_payload" + }, + "payload": { + "schema_version": 1, + "kind": "observer_noop", + "reason": "trusted_direct_same_repo_human_comment", + "source_workflow_name": "Reviewer Bot PR Comment Observer", + "source_workflow_file": ".github/workflows/reviewer-bot-pr-comment-observer.yml", + "source_run_id": 999, + "source_run_attempt": 1, + "source_event_name": "issue_comment", + "source_event_action": "created", + "source_event_key": "issue_comment:100", + "pr_number": 42 + } +} diff --git a/tests/fixtures/observer_payloads/workflow_pr_comment_deferred.json b/tests/fixtures/observer_payloads/workflow_pr_comment_deferred.json new file mode 100644 index 000000000..badc5e777 --- /dev/null +++ b/tests/fixtures/observer_payloads/workflow_pr_comment_deferred.json @@ -0,0 +1,32 @@ +{ + "fixture_metadata": { + "fixture_id": "workflow_pr_comment_deferred", + "contract_class": "workflow_emitted_payload", + "contract_source": "workflow YAML", + "source_workflow_file": ".github/workflows/reviewer-bot-pr-comment-router.yml" + }, + "payload": { + "payload_kind": "deferred_comment", + "schema_version": 3, + "source_workflow_name": "Reviewer Bot PR Comment Router", + "source_workflow_file": ".github/workflows/reviewer-bot-pr-comment-router.yml", + "source_run_id": 401, + "source_run_attempt": 3, + "source_event_name": "issue_comment", + "source_event_action": "created", + "source_event_key": "issue_comment:501", + "pr_number": 42, + "comment_id": 501, + "comment_body": "hello\n@guidelines-bot /queue", + "comment_created_at": "2026-03-20T20:48:25Z", + "comment_author": "contributor", + "comment_author_id": 7001, + "comment_user_type": "User", + "comment_sender_type": "User", + "comment_installation_id": null, + "comment_performed_via_github_app": false, + "issue_author": "dana", + "issue_state": "open", + "issue_labels": ["triage"] + } +} diff --git a/tests/fixtures/observer_payloads/workflow_pr_review_comment_deferred.json b/tests/fixtures/observer_payloads/workflow_pr_review_comment_deferred.json new file mode 100644 index 000000000..01c3dce7b --- /dev/null +++ b/tests/fixtures/observer_payloads/workflow_pr_review_comment_deferred.json @@ -0,0 +1,33 @@ +{ + "fixture_metadata": { + "fixture_id": "workflow_pr_review_comment_deferred", + "contract_class": "workflow_emitted_payload", + "contract_source": "workflow YAML", + "source_workflow_file": ".github/workflows/reviewer-bot-pr-review-comment-observer.yml" + }, + "payload": { + "payload_kind": "deferred_review_comment", + "schema_version": 3, + "source_workflow_name": "Reviewer Bot PR Review Comment Observer", + "source_workflow_file": ".github/workflows/reviewer-bot-pr-review-comment-observer.yml", + "source_run_id": 404, + "source_run_attempt": 6, + "source_event_name": "pull_request_review_comment", + "source_event_action": "created", + "source_event_key": "pull_request_review_comment:701", + "pr_number": 42, + "comment_id": 701, + "comment_body": "@guidelines-bot /queue", + "comment_created_at": "2026-03-20T21:00:00Z", + "comment_author": "reviewer2", + "comment_author_id": 7004, + "comment_user_type": "User", + "comment_sender_type": "User", + "comment_installation_id": null, + "comment_performed_via_github_app": false, + "issue_author": "dana", + "issue_state": "open", + "issue_labels": ["coding guideline"], + "source_artifact_name": "reviewer-bot-review-comment-context-404-attempt-6" + } +} diff --git a/tests/fixtures/observer_payloads/workflow_pr_review_dismissed_deferred.json b/tests/fixtures/observer_payloads/workflow_pr_review_dismissed_deferred.json new file mode 100644 index 000000000..94d440069 --- /dev/null +++ b/tests/fixtures/observer_payloads/workflow_pr_review_dismissed_deferred.json @@ -0,0 +1,25 @@ +{ + "fixture_metadata": { + "fixture_id": "workflow_pr_review_dismissed_deferred", + "contract_class": "workflow_emitted_payload", + "contract_source": "workflow YAML", + "source_workflow_file": ".github/workflows/reviewer-bot-pr-review-dismissed-observer.yml" + }, + "payload": { + "payload_kind": "deferred_review_dismissed", + "schema_version": 3, + "source_workflow_name": "Reviewer Bot PR Review Dismissed Observer", + "source_workflow_file": ".github/workflows/reviewer-bot-pr-review-dismissed-observer.yml", + "source_run_id": 403, + "source_run_attempt": 5, + "source_event_name": "pull_request_review", + "source_event_action": "dismissed", + "source_event_key": "pull_request_review_dismissed:602", + "pr_number": 42, + "review_id": 602, + "source_commit_id": "fedcba654321", + "actor_login": "maintainer1", + "actor_id": 7003, + "source_artifact_name": "reviewer-bot-review-dismissed-context-403-attempt-5" + } +} diff --git a/tests/fixtures/observer_payloads/workflow_pr_review_submitted_deferred.json b/tests/fixtures/observer_payloads/workflow_pr_review_submitted_deferred.json new file mode 100644 index 000000000..7ab1d2182 --- /dev/null +++ b/tests/fixtures/observer_payloads/workflow_pr_review_submitted_deferred.json @@ -0,0 +1,27 @@ +{ + "fixture_metadata": { + "fixture_id": "workflow_pr_review_submitted_deferred", + "contract_class": "workflow_emitted_payload", + "contract_source": "workflow YAML", + "source_workflow_file": ".github/workflows/reviewer-bot-pr-review-submitted-observer.yml" + }, + "payload": { + "payload_kind": "deferred_review_submitted", + "schema_version": 3, + "source_workflow_name": "Reviewer Bot PR Review Submitted Observer", + "source_workflow_file": ".github/workflows/reviewer-bot-pr-review-submitted-observer.yml", + "source_run_id": 402, + "source_run_attempt": 4, + "source_event_name": "pull_request_review", + "source_event_action": "submitted", + "source_event_key": "pull_request_review:601", + "pr_number": 42, + "review_id": 601, + "source_submitted_at": "2026-03-20T20:50:00Z", + "source_review_state": "approved", + "source_commit_id": "abc123def456", + "actor_login": "reviewer1", + "actor_id": 7002, + "source_artifact_name": "reviewer-bot-review-submitted-context-402-attempt-4" + } +} diff --git a/tests/fixtures/reconcile_harness.py b/tests/fixtures/reconcile_harness.py new file mode 100644 index 000000000..c1fc7de94 --- /dev/null +++ b/tests/fixtures/reconcile_harness.py @@ -0,0 +1,314 @@ +from __future__ import annotations + +from dataclasses import dataclass + +from scripts.reviewer_bot_lib import comment_application, lifecycle, reconcile + +from .fake_runtime import FakeReviewerBotRuntime +from .reviewer_bot_builders import pull_request_payload, review_payload +from .reviewer_bot_env import set_env_values, set_workflow_run_event_payload +from .reviewer_bot_fakes import RouteGitHubApi, github_result + + +def review_submitted_payload( + *, + pr_number: int, + review_id: int, + source_event_key: str, + source_submitted_at: str, + source_review_state: str, + source_commit_id: str, + actor_login: str, + source_run_id: int, + source_run_attempt: int, +) -> dict: + return { + "payload_kind": "deferred_review_submitted", + "schema_version": 3, + "source_workflow_name": "Reviewer Bot PR Review Submitted Observer", + "source_workflow_file": ".github/workflows/reviewer-bot-pr-review-submitted-observer.yml", + "source_run_id": source_run_id, + "source_run_attempt": source_run_attempt, + "source_event_name": "pull_request_review", + "source_event_action": "submitted", + "source_event_key": source_event_key, + "pr_number": pr_number, + "review_id": review_id, + "source_submitted_at": source_submitted_at, + "source_review_state": source_review_state, + "source_commit_id": source_commit_id, + "actor_login": actor_login, + } + + +def issue_comment_payload( + *, + pr_number: int, + comment_id: int, + source_event_key: str, + body: str, + comment_class: str, + has_non_command_text: bool, + source_created_at: str, + actor_login: str, + source_run_id: int, + source_run_attempt: int, +) -> dict: + del comment_class, has_non_command_text + return { + "payload_kind": "deferred_comment", + "schema_version": 3, + "source_workflow_name": "Reviewer Bot PR Comment Router", + "source_workflow_file": ".github/workflows/reviewer-bot-pr-comment-router.yml", + "source_run_id": source_run_id, + "source_run_attempt": source_run_attempt, + "source_event_name": "issue_comment", + "source_event_action": "created", + "source_event_key": source_event_key, + "pr_number": pr_number, + "comment_id": comment_id, + "comment_body": body, + "comment_created_at": source_created_at, + "comment_author": actor_login, + "comment_author_id": 7001, + "comment_user_type": "User", + "comment_sender_type": "User", + "comment_installation_id": None, + "comment_performed_via_github_app": False, + "issue_author": "dana", + "issue_state": "open", + "issue_labels": ["coding guideline"], + } + + +def review_comment_payload( + *, + pr_number: int, + comment_id: int, + source_event_key: str, + body: str, + comment_class: str, + has_non_command_text: bool, + source_created_at: str, + actor_login: str, + actor_id: int, + actor_class: str, + pull_request_review_id: int, + in_reply_to_id: int, + source_run_id: int, + source_run_attempt: int, +) -> dict: + del comment_class, has_non_command_text, actor_class, pull_request_review_id, in_reply_to_id + return { + "payload_kind": "deferred_review_comment", + "schema_version": 3, + "source_workflow_name": "Reviewer Bot PR Review Comment Observer", + "source_workflow_file": ".github/workflows/reviewer-bot-pr-review-comment-observer.yml", + "source_run_id": source_run_id, + "source_run_attempt": source_run_attempt, + "source_event_name": "pull_request_review_comment", + "source_event_action": "created", + "source_event_key": source_event_key, + "pr_number": pr_number, + "comment_id": comment_id, + "comment_body": body, + "comment_created_at": source_created_at, + "comment_author": actor_login, + "comment_author_id": actor_id, + "comment_user_type": "User", + "comment_sender_type": "User", + "comment_installation_id": None, + "comment_performed_via_github_app": False, + "issue_author": "dana", + "issue_state": "open", + "issue_labels": ["coding guideline"], + } + + +@dataclass +class ReconcileHarness: + monkeypatch: object + payload: dict + + def __post_init__(self) -> None: + self.github = RouteGitHubApi() + self.runtime = FakeReviewerBotRuntime(self.monkeypatch, github=self.github) + self.runtime.ACTIVE_LEASE_CONTEXT = object() + self.config = self.runtime.config + self.deferred_payloads = self.runtime.deferred_payloads + self.runtime.stub_deferred_payload(self.payload) + self.wrapper_set_trigger_from_payload(self.payload) + + def handle_workflow_run_event_result(self, state: dict) -> reconcile.WorkflowRunHandlerResult: + return reconcile.handle_workflow_run_event_result(self.runtime, state) + + def set_payload(self, payload: dict) -> dict: + self.payload = payload + self.deferred_payloads.set_payload(payload) + self.wrapper_set_trigger_from_payload(payload) + return payload + + def wrapper_set_trigger_from_payload(self, payload: dict, *, conclusion: str = "success") -> None: + set_workflow_run_event_payload(self.config, payload["source_workflow_name"]) + set_env_values( + self.config, + WORKFLOW_RUN_TRIGGERING_ID=payload["source_run_id"], + WORKFLOW_RUN_TRIGGERING_ATTEMPT=payload["source_run_attempt"], + WORKFLOW_RUN_TRIGGERING_CONCLUSION=conclusion, + ) + + set_trigger_from_payload = wrapper_set_trigger_from_payload + + def add_pull_request( + self, + *, + pr_number: int, + head_sha: str | None = None, + author: str = "dana", + labels: list[str] | None = None, + requested_reviewers: list[str] | None = None, + status_code: int = 200, + ) -> None: + payload = pull_request_payload( + pr_number, + head_sha=head_sha or "", + author=author, + ) + if head_sha is None: + payload.pop("head", None) + payload["labels"] = [{"name": label} for label in (labels or [])] + if requested_reviewers is not None: + payload["requested_reviewers"] = [ + {"login": reviewer} for reviewer in requested_reviewers + ] + self.github.add_request("GET", f"pulls/{pr_number}", status_code=status_code, payload=payload) + + def add_review( + self, + *, + pr_number: int, + review_id: int, + submitted_at: str, + state: str, + commit_id: str, + author: str, + status_code: int = 200, + ) -> None: + self.github.add_request( + "GET", + f"pulls/{pr_number}/reviews/{review_id}", + status_code=status_code, + payload=review_payload( + review_id, + state=state, + submitted_at=submitted_at, + commit_id=commit_id, + author=author, + ), + ) + + def add_reviews_page(self, *, pr_number: int, reviews: list[dict], page: int = 1) -> None: + self.github.add_request( + "GET", + f"pulls/{pr_number}/reviews?per_page=100&page={page}", + status_code=200, + payload=reviews, + ) + + def add_issue_comment( + self, + *, + comment_id: int, + body: str, + author: str, + author_type: str, + author_association: str, + performed_via_github_app=None, + status_code: int = 200, + ) -> None: + self.github.add_request( + "GET", + f"issues/comments/{comment_id}", + status_code=status_code, + payload={ + "body": body, + "user": {"login": author, "type": author_type}, + "author_association": author_association, + "performed_via_github_app": performed_via_github_app, + }, + ) + + def add_review_comment( + self, + *, + comment_id: int, + body: str, + author: str, + author_type: str, + author_association: str, + performed_via_github_app=None, + status_code: int = 200, + ) -> None: + self.github.add_request( + "GET", + f"pulls/comments/{comment_id}", + status_code=status_code, + payload={ + "body": body, + "user": {"login": author, "type": author_type}, + "author_association": author_association, + "performed_via_github_app": performed_via_github_app, + }, + ) + + def add_request_failure( + self, + *, + endpoint: str, + status_code: int, + payload: dict, + retry_attempts: int = 1, + failure_kind: str | None = None, + ) -> None: + self.github.add_request( + "GET", + endpoint, + result=github_result( + status_code, + payload, + retry_attempts=retry_attempts, + failure_kind=failure_kind, + ), + ) + + def stub_head_repair(self, *, changed: bool = False, outcome: str = "unchanged") -> None: + self.runtime.maybe_record_head_observation_repair = lambda issue_number, review_data: lifecycle.HeadObservationRepairResult( + changed=changed, + outcome=outcome, + ) + + def stub_review_rebuild(self, *, changed: bool = False) -> None: + self.monkeypatch.setattr( + reconcile, + "_record_review_rebuild", + lambda bot, state_obj, issue_number, review_data: changed, + ) + + def stub_comment_classification(self, classified: dict | None = None, *, func=None) -> None: + if func is None: + def default_func(bot, body): + return classified + + func = default_func + self.monkeypatch.setattr(reconcile, "classify_comment_payload", func) + + def stub_apply_comment_command(self, result: bool | None = None, *, func=None) -> None: + if func is None: + def default_func(*args, **kwargs): + return result + + func = default_func + self.monkeypatch.setattr(comment_application, "apply_comment_command", func) + + def run(self, state: dict) -> bool: + return self.handle_workflow_run_event_result(state).state_changed diff --git a/tests/fixtures/recording_logger.py b/tests/fixtures/recording_logger.py new file mode 100644 index 000000000..31aeebec1 --- /dev/null +++ b/tests/fixtures/recording_logger.py @@ -0,0 +1,15 @@ +from __future__ import annotations + + +class RecordingLogger: + def __init__(self): + self.records: list[dict[str, object]] = [] + + def event(self, level: str, message: str, **fields) -> None: + self.records.append( + { + "level": level, + "message": message, + "fields": dict(fields), + } + ) diff --git a/tests/fixtures/reviewer_bot.py b/tests/fixtures/reviewer_bot.py new file mode 100644 index 000000000..8243822b4 --- /dev/null +++ b/tests/fixtures/reviewer_bot.py @@ -0,0 +1,145 @@ +import io +import json +import zipfile + +from scripts.reviewer_bot_lib.config import ( + FRESHNESS_RUNTIME_EPOCH_V18, + STATE_SCHEMA_VERSION, + STATUS_PROJECTION_EPOCH, +) + +from .reviewer_bot_builders import ( + accept_contributor_comment as accept_contributor_comment, +) +from .reviewer_bot_builders import ( + accept_contributor_revision as accept_contributor_revision, +) +from .reviewer_bot_builders import ( + accept_reviewer_comment as accept_reviewer_comment, +) +from .reviewer_bot_builders import ( + accept_reviewer_review as accept_reviewer_review, +) +from .reviewer_bot_builders import ( + accepted_record as accepted_record, +) +from .reviewer_bot_builders import ( + issue_snapshot as issue_snapshot, +) +from .reviewer_bot_builders import ( + make_tracked_review_state as make_tracked_review_state, +) +from .reviewer_bot_builders import ( + pull_request_payload as pull_request_payload, +) +from .reviewer_bot_builders import ( + review_payload as review_payload, +) + +__all__ = [ + "accept_contributor_comment", + "accept_contributor_revision", + "accept_reviewer_comment", + "accept_reviewer_review", + "accepted_record", + "issue_snapshot", + "iso_z", + "make_state", + "make_tracked_review_state", + "make_zip_payload", + "pull_request_payload", + "review_payload", + "valid_reviewer_board_metadata", +] + + +def make_state(epoch: str | None = None): + state = { + "schema_version": STATE_SCHEMA_VERSION, + "freshness_runtime_epoch": epoch or FRESHNESS_RUNTIME_EPOCH_V18, + "status_projection_epoch": STATUS_PROJECTION_EPOCH, + "last_updated": None, + "current_index": 0, + "queue": [ + {"github": "alice", "name": "Alice"}, + {"github": "bob", "name": "Bob"}, + {"github": "carol", "name": "Carol"}, + ], + "pass_until": [], + "recent_assignments": [], + "active_reviews": {}, + } + if epoch == "freshness_v15": + state["freshness_runtime_epoch"] = epoch + state.pop("status_projection_epoch", None) + return state + + +def valid_reviewer_board_metadata(): + return { + "data": { + "organization": { + "projectV2": { + "id": "PVT_kwDOB", + "title": "Reviewer Board", + "fields": { + "nodes": [ + { + "__typename": "ProjectV2SingleSelectField", + "id": "field-review-state", + "name": "Review State", + "options": [ + {"id": "opt-ar", "name": "Awaiting Reviewer"}, + {"id": "opt-ac", "name": "Awaiting Contributor"}, + {"id": "opt-aw", "name": "Awaiting Write Approval"}, + {"id": "opt-done", "name": "Done"}, + {"id": "opt-unassigned", "name": "Unassigned"}, + ], + }, + { + "__typename": "ProjectV2Field", + "dataType": "TEXT", + "id": "field-reviewer", + "name": "Reviewer", + }, + { + "__typename": "ProjectV2Field", + "dataType": "DATE", + "id": "field-assigned-at", + "name": "Assigned At", + }, + { + "__typename": "ProjectV2Field", + "dataType": "DATE", + "id": "field-waiting-since", + "name": "Waiting Since", + }, + { + "__typename": "ProjectV2SingleSelectField", + "id": "field-needs-attention", + "name": "Needs Attention", + "options": [ + {"id": "opt-no", "name": "No"}, + {"id": "opt-warning", "name": "Warning Sent"}, + {"id": "opt-notice", "name": "Transition Notice Sent"}, + {"id": "opt-triage", "name": "Triage Approval Required"}, + {"id": "opt-repair", "name": "Projection Repair Required"}, + ], + }, + ] + }, + } + } + } + } + + +def iso_z(dt): + return dt.isoformat().replace("+00:00", "Z") + + +def make_zip_payload(file_name: str, payload: dict) -> bytes: + buffer = io.BytesIO() + with zipfile.ZipFile(buffer, "w") as archive: + archive.writestr(file_name, json.dumps(payload)) + return buffer.getvalue() diff --git a/tests/fixtures/reviewer_bot_builders.py b/tests/fixtures/reviewer_bot_builders.py new file mode 100644 index 000000000..692f66932 --- /dev/null +++ b/tests/fixtures/reviewer_bot_builders.py @@ -0,0 +1,271 @@ +from scripts.reviewer_bot_core.comment_routing_policy import PrCommentRouterOutcome +from scripts.reviewer_bot_lib import repair_records, review_state +from scripts.reviewer_bot_lib.context import ( + AssignmentRequest, + CommentEventRequest, + PrCommentAdmission, + PrivilegedCommandRequest, +) + + +def build_assignment_request( + *, + issue_number: int, + issue_author: str = "", + is_pull_request: bool = False, + issue_labels: tuple[str, ...] = (), + repo_owner: str = "", + repo_name: str = "", +) -> AssignmentRequest: + return AssignmentRequest( + issue_number=issue_number, + issue_author=issue_author, + is_pull_request=is_pull_request, + issue_labels=issue_labels, + repo_owner=repo_owner, + repo_name=repo_name, + ) + + +def build_privileged_command_request( + *, + issue_number: int, + actor: str = "", + command_name: str = "", + is_pull_request: bool = False, + issue_labels: tuple[str, ...] = (), +) -> PrivilegedCommandRequest: + return PrivilegedCommandRequest( + issue_number=issue_number, + actor=actor, + command_name=command_name, + is_pull_request=is_pull_request, + issue_labels=issue_labels, + ) + + +def build_comment_event_request( + *, + issue_number: int, + is_pull_request: bool, + issue_state: str = "", + issue_author: str = "", + issue_labels: tuple[str, ...] = (), + comment_id: int = 0, + comment_author: str = "", + comment_author_id: int = 0, + comment_body: str = "", + comment_created_at: str = "", + comment_source_event_key: str = "", + comment_user_type: str = "", + comment_sender_type: str = "", + comment_installation_id: str = "", + comment_performed_via_github_app: bool = False, +) -> CommentEventRequest: + return CommentEventRequest( + issue_number=issue_number, + is_pull_request=is_pull_request, + issue_state=issue_state, + issue_author=issue_author, + issue_labels=issue_labels, + comment_id=comment_id, + comment_author=comment_author, + comment_author_id=comment_author_id, + comment_body=comment_body, + comment_created_at=comment_created_at, + comment_source_event_key=comment_source_event_key, + comment_user_type=comment_user_type, + comment_sender_type=comment_sender_type, + comment_installation_id=comment_installation_id, + comment_performed_via_github_app=comment_performed_via_github_app, + ) + + +def build_pr_comment_admission( + *, + route_outcome: PrCommentRouterOutcome = PrCommentRouterOutcome.TRUSTED_DIRECT, + declared_trust_class: str = "pr_trusted_direct", + github_repository: str = "", + pr_head_full_name: str = "", + pr_author: str = "", + issue_state: str = "open", + issue_labels: tuple[str, ...] = (), + comment_author_id: int = 200, + github_run_id: int = 0, + github_run_attempt: int = 0, +) -> PrCommentAdmission: + return PrCommentAdmission( + route_outcome=route_outcome, + declared_trust_class=declared_trust_class, + github_repository=github_repository, + pr_head_full_name=pr_head_full_name, + pr_author=pr_author, + issue_state=issue_state, + issue_labels=issue_labels, + comment_author_id=comment_author_id, + github_run_id=github_run_id, + github_run_attempt=github_run_attempt, + ) + + +def make_tracked_review_state( + state: dict, + issue_number: int, + *, + reviewer: str | None = None, + assigned_at: str | None = None, + active_cycle_started_at: str | None = None, + repair_needed: dict | None = None, +): + review = review_state.ensure_review_entry(state, issue_number, create=True) + if review is None: + raise AssertionError(f"Unable to create review entry for #{issue_number}") + if reviewer is not None: + review["current_reviewer"] = reviewer + if assigned_at is not None: + review["assigned_at"] = assigned_at + if active_cycle_started_at is not None: + review["active_cycle_started_at"] = active_cycle_started_at + if repair_needed is not None: + repair_records.store_repair_marker(review, "status_label_projection", repair_needed) + return review + + +def issue_snapshot( + issue_number: int, + *, + state: str = "open", + is_pull_request: bool = False, + labels: list[dict] | list[str] | None = None, +) -> dict: + return { + "number": issue_number, + "state": state, + "pull_request": {} if is_pull_request else None, + "labels": labels or [], + } + + +def pull_request_payload( + issue_number: int, + *, + head_sha: str, + author: str = "alice", + head_repo_full_name: str | None = None, +) -> dict: + payload = { + "number": issue_number, + "state": "open", + "head": {"sha": head_sha}, + "user": {"login": author}, + } + if head_repo_full_name is not None: + payload["head"]["repo"] = {"full_name": head_repo_full_name} + return payload + + +def review_payload( + review_id: int, + *, + state: str, + submitted_at: str, + commit_id: str, + author: str, +) -> dict: + return { + "id": review_id, + "state": state, + "submitted_at": submitted_at, + "commit_id": commit_id, + "user": {"login": author}, + } + + +def accept_reviewer_comment( + review_data: dict, + *, + semantic_key: str, + timestamp: str, + actor: str, +) -> bool: + return review_state.accept_channel_event( + review_data, + "reviewer_comment", + semantic_key=semantic_key, + timestamp=timestamp, + actor=actor, + ) + + +def accept_reviewer_review( + review_data: dict, + *, + semantic_key: str, + timestamp: str, + actor: str, + reviewed_head_sha: str, + source_precedence: int = 1, +) -> bool: + return review_state.accept_channel_event( + review_data, + "reviewer_review", + semantic_key=semantic_key, + timestamp=timestamp, + actor=actor, + reviewed_head_sha=reviewed_head_sha, + source_precedence=source_precedence, + ) + + +def accept_contributor_comment( + review_data: dict, + *, + semantic_key: str, + timestamp: str, + actor: str, +) -> bool: + return review_state.accept_channel_event( + review_data, + "contributor_comment", + semantic_key=semantic_key, + timestamp=timestamp, + actor=actor, + ) + + +def accept_contributor_revision( + review_data: dict, + *, + semantic_key: str, + timestamp: str, + actor: str, + head_sha: str, +) -> bool: + return review_state.accept_channel_event( + review_data, + "contributor_revision", + semantic_key=semantic_key, + timestamp=timestamp, + actor=actor, + reviewed_head_sha=head_sha, + ) + + +def accepted_record( + *, + semantic_key: str, + timestamp: str, + actor: str, + reviewed_head_sha: str | None = None, + head_sha: str | None = None, +) -> dict: + record = { + "semantic_key": semantic_key, + "timestamp": timestamp, + "actor": actor, + } + if reviewed_head_sha is not None: + record["reviewed_head_sha"] = reviewed_head_sha + if head_sha is not None: + record["head_sha"] = head_sha + return record diff --git a/tests/fixtures/reviewer_bot_env.py b/tests/fixtures/reviewer_bot_env.py new file mode 100644 index 000000000..fe1d51568 --- /dev/null +++ b/tests/fixtures/reviewer_bot_env.py @@ -0,0 +1,70 @@ +from __future__ import annotations + +import json +import tempfile +from pathlib import Path + +from scripts.reviewer_bot_lib.config import LeaseContext + +CLEAR_REVIEWER_BOT_ENV_VARS = { + "ALLOW_EMPTY_ACTIVE_REVIEWS_WRITE", + "COMMENT_AUTHOR", + "COMMENT_BODY", + "COMMENT_ID", + "COMMENT_SOURCE_EVENT_KEY", + "EVENT_ACTION", + "EVENT_NAME", + "GITHUB_EVENT_PATH", + "IS_PULL_REQUEST", + "ISSUE_AUTHOR", + "ISSUE_LABELS", + "ISSUE_NUMBER", + "LABEL_NAME", + "MANUAL_ACTION", + "PR_IS_CROSS_REPOSITORY", + "REPO_NAME", + "REPO_OWNER", + "REVIEW_AUTHOR", + "REVIEW_STATE", + "WORKFLOW_JOB_NAME", + "WORKFLOW_NAME", + "WORKFLOW_RUN_EVENT", + "WORKFLOW_RUN_EVENT_ACTION", + "WORKFLOW_RUN_HEAD_SHA", + "WORKFLOW_RUN_ID", + "WORKFLOW_RUN_RECONCILE_HEAD_SHA", + "WORKFLOW_RUN_RECONCILE_PR_NUMBER", +} + + +def build_test_lease_context(): + return LeaseContext( + lock_token="test-lock-token", + lock_owner_run_id="test-run", + lock_owner_workflow="test-workflow", + lock_owner_job="test-job", + state_issue_url="https://example.com/state", + ) + + +def clear_reviewer_bot_env(monkeypatch) -> None: + for name in CLEAR_REVIEWER_BOT_ENV_VARS: + monkeypatch.delenv(name, raising=False) + + +def set_env_values(config, **values) -> None: + for name, value in values.items(): + config.set(name, value) + + +def set_process_env_values(monkeypatch, **values) -> None: + for name, value in values.items(): + monkeypatch.setenv(name, str(value)) + + +def set_workflow_run_event_payload(config, workflow_name: str) -> str: + with tempfile.NamedTemporaryFile("w", encoding="utf-8", suffix=".json", delete=False) as handle: + json.dump({"workflow_run": {"name": workflow_name}}, handle) + path = Path(handle.name) + config.set("GITHUB_EVENT_PATH", str(path)) + return str(path) diff --git a/tests/fixtures/reviewer_bot_fakes.py b/tests/fixtures/reviewer_bot_fakes.py new file mode 100644 index 000000000..5e680a3ed --- /dev/null +++ b/tests/fixtures/reviewer_bot_fakes.py @@ -0,0 +1,187 @@ +from __future__ import annotations + +from dataclasses import dataclass +from typing import Any + +from scripts.reviewer_bot_lib.config import GitHubApiResult + + +def github_result( + status_code: int, + payload: Any = None, + *, + headers: dict[str, str] | None = None, + text: str | None = None, + ok: bool | None = None, + failure_kind: str | None = None, + retry_attempts: int = 0, + transport_error: str | None = None, +) -> GitHubApiResult: + normalized_headers = { + key.lower(): value for key, value in (headers or {}).items() + } + resolved_ok = status_code < 400 if ok is None else ok + resolved_failure_kind = failure_kind + if resolved_failure_kind is None and not resolved_ok: + if status_code == 404: + resolved_failure_kind = "not_found" + elif status_code == 429: + resolved_failure_kind = "rate_limited" + elif status_code is not None and status_code >= 500: + resolved_failure_kind = "server_error" + else: + resolved_failure_kind = "http_error" + resolved_text = text + if resolved_text is None: + if isinstance(payload, dict) and isinstance(payload.get("message"), str): + resolved_text = payload["message"] + elif payload is None: + resolved_text = "" + else: + resolved_text = "ok" if resolved_ok else "error" + return GitHubApiResult( + status_code=status_code, + payload=payload, + headers=normalized_headers, + text=resolved_text, + ok=resolved_ok, + failure_kind=resolved_failure_kind, + retry_attempts=retry_attempts, + transport_error=transport_error, + ) + + +@dataclass(frozen=True) +class GitHubCall: + method: str + endpoint: str + data: dict | None + extra_headers: dict[str, str] | None = None + kwargs: dict[str, Any] | None = None + + +class RouteGitHubApi: + def __init__(self): + self._request_routes: dict[tuple[str, str], list[GitHubApiResult | BaseException]] = {} + self._api_routes: dict[tuple[str, str], list[Any | BaseException]] = {} + self._raise_system_exit_on_request = False + self.request_calls: list[GitHubCall] = [] + self.api_calls: list[GitHubCall] = [] + + @staticmethod + def _normalize_route_sequence(value: Any) -> list[Any]: + if isinstance(value, list): + if not value: + raise ValueError("Route sequence cannot be empty") + return list(value) + return [value] + + @staticmethod + def _consume_route(routes: dict[tuple[str, str], list[Any]], key: tuple[str, str], *, kind: str) -> Any: + route = routes.get(key) + if route is None: + raise AssertionError(f"Unexpected GitHub {kind} route: {key[0]} {key[1]}") + current = route[0] + if len(route) > 1: + del route[0] + if isinstance(current, BaseException): + raise current + return current + + def add_request( + self, + method: str, + endpoint: str, + result: GitHubApiResult | None = None, + **result_kwargs, + ) -> "RouteGitHubApi": + return self.add_request_sequence( + method, + endpoint, + [result or github_result(**result_kwargs)], + ) + + def add_request_sequence( + self, + method: str, + endpoint: str, + results: list[GitHubApiResult | BaseException], + ) -> "RouteGitHubApi": + self._request_routes[(method, endpoint)] = self._normalize_route_sequence(results) + return self + + def add_api(self, method: str, endpoint: str, payload: Any) -> "RouteGitHubApi": + return self.add_api_sequence(method, endpoint, [payload]) + + def add_api_sequence( + self, + method: str, + endpoint: str, + payloads: list[Any | BaseException], + ) -> "RouteGitHubApi": + self._api_routes[(method, endpoint)] = self._normalize_route_sequence(payloads) + return self + + def add_pull_request_snapshot(self, issue_number: int, payload: Any) -> "RouteGitHubApi": + return self.add_api("GET", f"pulls/{issue_number}", payload).add_request( + "GET", + f"pulls/{issue_number}", + status_code=200, + payload=payload, + ) + + def add_pull_request_reviews(self, issue_number: int, reviews: Any, *, page: int = 1) -> "RouteGitHubApi": + return self.add_request( + "GET", + f"pulls/{issue_number}/reviews?per_page=100&page={page}", + status_code=200, + payload=reviews, + ) + + def raise_system_exit_on_request(self) -> "RouteGitHubApi": + self._raise_system_exit_on_request = True + return self + + def requested_endpoints(self) -> list[str]: + return [call.endpoint for call in self.request_calls] + + def api_endpoints(self) -> list[str]: + return [call.endpoint for call in self.api_calls] + + def github_api_request( + self, + method: str, + endpoint: str, + data: dict | None = None, + extra_headers: dict[str, str] | None = None, + **kwargs, + ) -> GitHubApiResult: + self.request_calls.append( + GitHubCall( + method=method, + endpoint=endpoint, + data=data, + extra_headers=extra_headers, + kwargs=kwargs, + ) + ) + if self._raise_system_exit_on_request: + raise SystemExit(1) + return self._consume_route(self._request_routes, (method, endpoint), kind="request") + + def github_api(self, method: str, endpoint: str, data: dict | None = None): + self.api_calls.append( + GitHubCall(method=method, endpoint=endpoint, data=data) + ) + key = (method, endpoint) + if key in self._api_routes: + return self._consume_route(self._api_routes, key, kind="api") + route = self._request_routes.get(key) + if route is None: + raise AssertionError(f"Unexpected GitHub API route: {method} {endpoint}") + route = self._consume_route(self._request_routes, key, kind="api") + if not route.ok: + return None + if route.payload is None: + return {} + return route.payload diff --git a/tests/fixtures/reviewer_bot_recorders.py b/tests/fixtures/reviewer_bot_recorders.py new file mode 100644 index 000000000..9a4a187cc --- /dev/null +++ b/tests/fixtures/reviewer_bot_recorders.py @@ -0,0 +1,42 @@ +from __future__ import annotations + +from dataclasses import dataclass + + +@dataclass +class CommentSideEffects: + comments: list[tuple[int, str]] + reactions: list[tuple[int, str]] + + +def record_comments(target): + comments: list[tuple[int, str]] = [] + target.post_comment = lambda issue_number, body: comments.append((issue_number, body)) or True + return comments + + +def record_comment_dicts(target): + comments = [] + target.post_comment = lambda issue_number, body: comments.append({"issue_number": issue_number, "body": body}) or True + return comments + + +def record_reactions(target): + reactions: list[tuple[int, str]] = [] + target.add_reaction = lambda comment_id, reaction: reactions.append((comment_id, reaction)) or True + return reactions + + +def record_comment_side_effects(target) -> CommentSideEffects: + return CommentSideEffects( + comments=record_comments(target), + reactions=record_reactions(target), + ) + + +def record_status_label_ops(target): + operations = [] + target.add_label_with_status = lambda issue_number, label: operations.append(("add", issue_number, label)) or True + target.remove_label_with_status = lambda issue_number, label: operations.append(("remove", issue_number, label)) or True + target.ensure_label_exists = lambda *args, **kwargs: True + return operations diff --git a/tests/fixtures/reviewer_bot_sweeper_builders.py b/tests/fixtures/reviewer_bot_sweeper_builders.py new file mode 100644 index 000000000..89959ed52 --- /dev/null +++ b/tests/fixtures/reviewer_bot_sweeper_builders.py @@ -0,0 +1,80 @@ +def issue_comment_event(comment_id: int, *, created_at: str, login: str = "alice", user_type: str = "User") -> dict: + return { + "id": comment_id, + "created_at": created_at, + "user": {"login": login, "type": user_type}, + } + + +def review_comment_event(comment_id: int, *, created_at: str, login: str = "dana", user_type: str = "User") -> dict: + return { + "id": comment_id, + "created_at": created_at, + "user": {"login": login, "type": user_type}, + } + + +def pull_request_review_event( + review_id: int, + *, + submitted_at: str, + state: str, + commit_id: str | None = None, + login: str = "alice", + updated_at: str | None = None, +) -> dict: + payload = { + "id": review_id, + "submitted_at": submitted_at, + "state": state, + "user": {"login": login}, + } + if commit_id is not None: + payload["commit_id"] = commit_id + if updated_at is not None: + payload["updated_at"] = updated_at + return payload + + +def workflow_run( + run_id: int, + *, + event: str, + path: str, + created_at: str, + repo_full_name: str = "rustfoundation/safety-critical-rust-coding-guidelines", + pr_number: int | None = 42, + status: str | None = None, + conclusion: str | None = None, + name: str | None = None, +) -> dict: + payload = { + "id": run_id, + "event": event, + "path": path, + "created_at": created_at, + "repository": {"full_name": repo_full_name}, + "pull_requests": [] if pr_number is None else [{"number": pr_number}], + } + if status is not None: + payload["status"] = status + if conclusion is not None: + payload["conclusion"] = conclusion + if name is not None: + payload["name"] = name + return payload + + +def artifact_payload( + *, + source_event_key: str, + source_run_id: int, + pr_number: int = 42, + source_run_attempt: int = 1, +) -> dict: + return { + "source_event_key": source_event_key, + "source_run_id": source_run_id, + "source_run_attempt": source_run_attempt, + "pr_number": pr_number, + } diff --git a/tests/fixtures/state_contracts/review_state_contract_table.md b/tests/fixtures/state_contracts/review_state_contract_table.md new file mode 100644 index 000000000..b375474f3 --- /dev/null +++ b/tests/fixtures/state_contracts/review_state_contract_table.md @@ -0,0 +1,52 @@ +Top-Level Persisted Keys +- active_reviews: required +- schema_version: required +- freshness_runtime_epoch: required +- projection epoch markers: intentionally not frozen +- status_projection_epoch: lazily materialized +- last_updated: intentionally not frozen +- current_index: required +- queue: required +- pass_until: required +- recent_assignments: required + +Per-Review-Entry Keys +- skipped: tolerated legacy shape +- current_reviewer: lazily materialized +- cycle_started_at: lazily materialized +- active_cycle_started_at: lazily materialized +- assigned_at: lazily materialized +- active_head_sha: lazily materialized +- last_reviewer_activity: lazily materialized +- transition_warning_sent: lazily materialized +- transition_notice_sent_at: lazily materialized +- assignment_method: lazily materialized +- review_completed_at: lazily materialized +- review_completed_by: lazily materialized +- review_completion_source: lazily materialized +- mandatory_approver_required: lazily materialized +- mandatory_approver_label_applied_at: lazily materialized +- mandatory_approver_pinged_at: lazily materialized +- mandatory_approver_satisfied_by: lazily materialized +- mandatory_approver_satisfied_at: lazily materialized +- sidecars: lazily materialized +- repair_markers: nested under sidecars with fixed owner keys +- overdue_anchor: lazily materialized +- deferred_gaps: nested under sidecars +- observer_discovery_watermarks: nested under sidecars +- pending_privileged_commands: nested under sidecars +- current_cycle_completion: lazily materialized +- current_cycle_write_approval: lazily materialized +- reconciled_source_events: nested under sidecars as a map; tolerated legacy list shape + +Per-Channel Keys +- accepted: lazily materialized +- seen_keys: tolerated legacy shape + +Lazy-Upgrade Cases +- missing active_reviews: tolerated legacy shape +- list-form review entry converted to {"skipped": ...}: tolerated legacy shape +- missing channel maps: lazily materialized +- missing sidecar-backed fields: lazily materialized +- non-list reconciled_source_events: tolerated legacy shape +- non-list skipped: tolerated legacy shape diff --git a/tests/fixtures/workflow_contracts/observer_payload_contract_matrix.json b/tests/fixtures/workflow_contracts/observer_payload_contract_matrix.json new file mode 100644 index 000000000..8f8251511 --- /dev/null +++ b/tests/fixtures/workflow_contracts/observer_payload_contract_matrix.json @@ -0,0 +1,144 @@ +{ + "behavior_questions": [ + { + "question_id": "pr_comment_route_outcome", + "owner": ".github/workflows/reviewer-bot-pr-comment-router.yml", + "source_event_name": "issue_comment", + "source_event_action": "created", + "payload_kind": "deferred_comment" + }, + { + "question_id": "pr_review_submitted_replay", + "owner": ".github/workflows/reviewer-bot-pr-review-submitted-observer.yml", + "source_event_name": "pull_request_review", + "source_event_action": "submitted", + "payload_kind": "deferred_review_submitted" + }, + { + "question_id": "pr_review_dismissed_replay", + "owner": ".github/workflows/reviewer-bot-pr-review-dismissed-observer.yml", + "source_event_name": "pull_request_review", + "source_event_action": "dismissed", + "payload_kind": "deferred_review_dismissed" + }, + { + "question_id": "pr_review_comment_replay", + "owner": ".github/workflows/reviewer-bot-pr-review-comment-observer.yml", + "source_event_name": "pull_request_review_comment", + "source_event_action": "created", + "payload_kind": "deferred_review_comment" + } + ], + "payload_contracts": [ + { + "payload_kind": "deferred_comment", + "owner": ".github/workflows/reviewer-bot-pr-comment-router.yml", + "schema_version": 3, + "carried_edge_fields": [ + "source_run_id", + "source_run_attempt", + "source_event_name", + "source_event_action", + "source_event_key", + "pr_number", + "comment_id", + "comment_body", + "comment_created_at", + "comment_author", + "comment_author_id", + "comment_user_type", + "comment_sender_type", + "comment_installation_id", + "comment_performed_via_github_app", + "issue_author", + "issue_state", + "issue_labels" + ], + "debug_only_fields": [ + "source_workflow_name", + "source_workflow_file", + "source_artifact_name", + "payload_filename" + ] + }, + { + "payload_kind": "deferred_review_submitted", + "owner": ".github/workflows/reviewer-bot-pr-review-submitted-observer.yml", + "schema_version": 3, + "carried_edge_fields": [ + "source_run_id", + "source_run_attempt", + "source_event_name", + "source_event_action", + "source_event_key", + "pr_number", + "review_id", + "source_submitted_at", + "source_review_state", + "source_commit_id", + "actor_login", + "actor_id" + ], + "debug_only_fields": [ + "source_workflow_name", + "source_workflow_file", + "source_artifact_name", + "payload_filename" + ] + }, + { + "payload_kind": "deferred_review_dismissed", + "owner": ".github/workflows/reviewer-bot-pr-review-dismissed-observer.yml", + "schema_version": 3, + "carried_edge_fields": [ + "source_run_id", + "source_run_attempt", + "source_event_name", + "source_event_action", + "source_event_key", + "pr_number", + "review_id", + "source_commit_id", + "actor_login", + "actor_id" + ], + "debug_only_fields": [ + "source_workflow_name", + "source_workflow_file", + "source_artifact_name", + "payload_filename" + ] + }, + { + "payload_kind": "deferred_review_comment", + "owner": ".github/workflows/reviewer-bot-pr-review-comment-observer.yml", + "schema_version": 3, + "carried_edge_fields": [ + "source_run_id", + "source_run_attempt", + "source_event_name", + "source_event_action", + "source_event_key", + "pr_number", + "comment_id", + "comment_body", + "comment_created_at", + "comment_author", + "comment_author_id", + "comment_user_type", + "comment_sender_type", + "comment_installation_id", + "comment_performed_via_github_app", + "issue_author", + "issue_state", + "issue_labels" + ], + "debug_only_fields": [ + "source_workflow_name", + "source_workflow_file", + "source_artifact_name", + "payload_filename" + ] + } + ] +} diff --git a/tests/integration/reviewer_bot/test_accept_no_fls_changes.py b/tests/integration/reviewer_bot/test_accept_no_fls_changes.py new file mode 100644 index 000000000..bab0854ed --- /dev/null +++ b/tests/integration/reviewer_bot/test_accept_no_fls_changes.py @@ -0,0 +1,295 @@ +from datetime import datetime, timezone +from pathlib import Path + +import pytest + +pytestmark = pytest.mark.integration + +from builder import build_cli +from scripts.reviewer_bot_core import privileged_command_policy +from scripts.reviewer_bot_lib import automation +from scripts.reviewer_bot_lib.config import FLS_AUDIT_LABEL +from tests.fixtures.commands_harness import CommandHarness + + +def test_list_changed_files_ignores_untracked_bootstrap_noise(monkeypatch, tmp_path): + harness = CommandHarness(monkeypatch) + runner = harness.automation_runner() + runner.when(["git", "diff", "--name-only"], stdout="") + runner.when(["git", "diff", "--cached", "--name-only"], stdout="") + + assert automation.list_changed_files(tmp_path) == [] + assert [command for command, _cwd, _check in runner.calls] == [ + ["git", "diff", "--name-only"], + ["git", "diff", "--cached", "--name-only"], + ] + +def test_list_changed_files_reports_tracked_changes_only(monkeypatch, tmp_path): + harness = CommandHarness(monkeypatch) + runner = harness.automation_runner() + runner.when(["git", "diff", "--name-only"], stdout="README.md\nsrc/spec.lock\n") + runner.when(["git", "diff", "--cached", "--name-only"], stdout="src/spec.lock\n") + + assert automation.list_changed_files(tmp_path) == ["README.md", "src/spec.lock"] + +def test_accept_no_fls_changes_honors_explicit_target_repo_root(monkeypatch, tmp_path): + harness = CommandHarness(monkeypatch) + harness.set_privileged_context(labels=[FLS_AUDIT_LABEL], target_repo_root=str(tmp_path)) + request = harness.typed_privileged_request( + issue_number=42, + actor="alice", + command_name="accept-no-fls-changes", + is_pull_request=False, + issue_labels=(FLS_AUDIT_LABEL,), + ) + harness.stub_permission("granted") + observed = {"cwd": None} + + def fake_list_changed_files(repo_root): + observed["cwd"] = repo_root + return ["README.md"] + + harness.runtime.list_changed_files = fake_list_changed_files + + message, success = harness.handle_accept_no_fls_changes(42, "alice", request=request) + + assert (message, success) == ("❌ Working tree is not clean; refusing to update spec.lock.", False) + assert observed["cwd"] == tmp_path + +def test_accept_no_fls_changes_uses_locked_nested_uv_commands(monkeypatch, tmp_path): + harness = CommandHarness(monkeypatch) + harness.set_privileged_context(labels=[FLS_AUDIT_LABEL], target_repo_root=str(tmp_path)) + request = harness.typed_privileged_request( + issue_number=42, + actor="alice", + command_name="accept-no-fls-changes", + is_pull_request=False, + issue_labels=(FLS_AUDIT_LABEL,), + ) + harness.stub_permission("granted") + list_calls = {"count": 0} + + def fake_list_changed_files(repo_root): + list_calls["count"] += 1 + assert repo_root == tmp_path + return [] + + runner = harness.automation_runner() + runner.when(["uv", "run", "--locked", "python", "scripts/fls_audit.py", "--summary-only", "--fail-on-impact"]) + runner.when(["uv", "run", "--locked", "python", "./make.py", "--update-spec-lock-file"]) + + harness.runtime.list_changed_files = fake_list_changed_files + + message, success = harness.handle_accept_no_fls_changes(42, "alice", request=request) + + assert (message, success) == ("✅ `src/spec.lock` is already up to date; no PR needed.", True) + assert list_calls["count"] == 2 + assert runner.calls == [ + (["uv", "run", "--locked", "python", "scripts/fls_audit.py", "--summary-only", "--fail-on-impact"], tmp_path, False), + (["uv", "run", "--locked", "python", "./make.py", "--update-spec-lock-file"], tmp_path, False), + ] + +def test_accept_no_fls_changes_surfaces_locked_uv_failure_details(monkeypatch, tmp_path): + harness = CommandHarness(monkeypatch) + harness.set_privileged_context(labels=[FLS_AUDIT_LABEL], target_repo_root=str(tmp_path)) + request = harness.typed_privileged_request( + issue_number=42, + actor="alice", + command_name="accept-no-fls-changes", + is_pull_request=False, + issue_labels=(FLS_AUDIT_LABEL,), + ) + harness.stub_permission("granted") + harness.runtime.list_changed_files = lambda repo_root: [] + runner = harness.automation_runner() + runner.when( + ["uv", "run", "--locked", "python", "scripts/fls_audit.py", "--summary-only", "--fail-on-impact"], + returncode=1, + stderr="error: lockfile at uv.lock needs to be updated, but --locked was provided", + ) + + message, success = harness.handle_accept_no_fls_changes(42, "alice", request=request) + + assert success is False + assert "Audit command failed." in message + assert "--locked was provided" in message + +def test_update_spec_lock_file_mode_exits_before_build_docs(monkeypatch, tmp_path): + monkeypatch.setattr( + build_cli.argparse.ArgumentParser, + "parse_args", + lambda self: type( + "Args", + (), + { + "clear": False, + "offline": False, + "ignore_spec_lock_diff": False, + "update_spec_lock_file": True, + "validate_urls": False, + "serve": False, + "check_links": False, + "xml": False, + "verbose": False, + "debug": False, + }, + )(), + ) + called = {"update": 0, "build": 0} + monkeypatch.setattr(build_cli, "update_spec_lockfile", lambda url, path: called.__setitem__("update", called["update"] + 1) or True) + monkeypatch.setattr(build_cli, "build_docs", lambda *args, **kwargs: called.__setitem__("build", called["build"] + 1)) + + with pytest.raises(SystemExit) as exc_info: + build_cli.main(tmp_path) + + assert exc_info.value.code == 0 + assert called == {"update": 1, "build": 0} + + +def test_accept_no_fls_changes_freezes_ordered_execution_plan_branch_name_and_pr_metadata(monkeypatch, tmp_path): + harness = CommandHarness(monkeypatch) + harness.set_privileged_context(labels=[FLS_AUDIT_LABEL], target_repo_root=str(tmp_path)) + request = harness.typed_privileged_request( + issue_number=42, + actor="alice", + command_name="accept-no-fls-changes", + is_pull_request=False, + issue_labels=(FLS_AUDIT_LABEL,), + ) + harness.stub_permission("granted") + list_calls = {"count": 0} + observed = {} + + class FrozenDateTime: + @classmethod + def now(cls, tz=None): + return datetime(2026, 4, 6, 12, 34, 56, tzinfo=timezone.utc) + + def fake_list_changed_files(repo_root): + list_calls["count"] += 1 + return [] if list_calls["count"] == 1 else ["src/spec.lock"] + + runner = harness.automation_runner() + runner.when(["uv", "run", "--locked", "python", "scripts/fls_audit.py", "--summary-only", "--fail-on-impact"]) + runner.when(["uv", "run", "--locked", "python", "./make.py", "--update-spec-lock-file"]) + runner.when(["git", "rev-parse", "--verify", "chore/spec-lock-2026-04-06-issue-42"], returncode=1) + runner.when(["git", "checkout", "-b", "chore/spec-lock-2026-04-06-issue-42"]) + runner.when(["git", "add", "src/spec.lock"]) + runner.when([ + "git", + "-c", + "user.name=guidelines-bot", + "-c", + "user.email=guidelines-bot@users.noreply.github.com", + "commit", + "-m", + "chore: update spec.lock; no affected guidelines", + ]) + runner.when(["git", "push", "origin", "chore/spec-lock-2026-04-06-issue-42"]) + + monkeypatch.setattr(automation, "datetime", FrozenDateTime) + harness.runtime.list_changed_files = fake_list_changed_files + harness.runtime.get_default_branch = lambda: "main" + monkeypatch.setattr( + automation, + "create_pull_request", + lambda bot, branch, base, issue_number, title=None, body=None: observed.update( + {"branch": branch, "base": base, "issue_number": issue_number, "title": title, "body": body} + ) or {"html_url": "https://example.invalid/pr/1"}, + ) + + message, success = harness.handle_accept_no_fls_changes(42, "alice", request=request) + + assert (message, success) == ("✅ Opened PR https://example.invalid/pr/1", True) + assert observed == { + "branch": "chore/spec-lock-2026-04-06-issue-42", + "base": "main", + "issue_number": 42, + "title": "chore: update spec.lock (no guideline impact)", + "body": "Updates `src/spec.lock` after confirming the audit reported no affected guidelines.\n\nCloses #42", + } + assert [command for command, _cwd, _check in runner.calls] == [ + ["uv", "run", "--locked", "python", "scripts/fls_audit.py", "--summary-only", "--fail-on-impact"], + ["uv", "run", "--locked", "python", "./make.py", "--update-spec-lock-file"], + ["git", "rev-parse", "--verify", "chore/spec-lock-2026-04-06-issue-42"], + ["git", "checkout", "-b", "chore/spec-lock-2026-04-06-issue-42"], + ["git", "add", "src/spec.lock"], + [ + "git", + "-c", + "user.name=guidelines-bot", + "-c", + "user.email=guidelines-bot@users.noreply.github.com", + "commit", + "-m", + "chore: update spec.lock; no affected guidelines", + ], + ["git", "push", "origin", "chore/spec-lock-2026-04-06-issue-42"], + ] + + +def test_j1_executor_consumes_richer_plan_command_lists_without_rederiving_git_steps(monkeypatch, tmp_path): + harness = CommandHarness(monkeypatch) + plan = privileged_command_policy.AcceptNoFlsChangesPlan( + ordered_steps=list(privileged_command_policy.ORDERED_EXECUTION_STEPS), + revalidation_checkpoints=list(privileged_command_policy.REVALIDATION_CHECKPOINTS), + expected_changed_files=["src/spec.lock"], + branch_probe_name="chore/spec-lock-2026-04-06-issue-42", + branch_name="chore/spec-lock-2026-04-06-issue-42", + base_branch="main", + add_paths=["src/spec.lock"], + git_checkout_args=["git", "checkout", "-b", "chore/spec-lock-2026-04-06-issue-42"], + git_commit_args=[ + "git", + "-c", + "user.name=guidelines-bot", + "-c", + "user.email=guidelines-bot@users.noreply.github.com", + "commit", + "-m", + "chore: update spec.lock; no affected guidelines", + ], + git_push_args=["git", "push", "origin", "chore/spec-lock-2026-04-06-issue-42"], + commit_message="chore: update spec.lock; no affected guidelines", + pull_request_title="chore: update spec.lock (no guideline impact)", + pull_request_body="Updates `src/spec.lock` after confirming the audit reported no affected guidelines.\n\nCloses #42", + ) + runner = harness.automation_runner() + runner.when(plan.git_checkout_args) + runner.when(["git", "add", "src/spec.lock"]) + runner.when(plan.git_commit_args) + runner.when(plan.git_push_args) + monkeypatch.setattr( + automation, + "create_pull_request", + lambda bot, branch, base, issue_number, title=None, body=None: {"html_url": "https://example.invalid/pr/2"}, + ) + + result = automation._execute_accept_no_fls_changes_plan(harness.runtime, tmp_path, 42, plan) + + assert result == privileged_command_policy.CompletePrivilegedExecution( + status="executed", + result_code="opened_pull_request", + result_message="✅ Opened PR https://example.invalid/pr/2", + opened_pr_url="https://example.invalid/pr/2", + ) + assert [command for command, _cwd, _check in runner.calls] == [ + plan.git_checkout_args, + ["git", "add", "src/spec.lock"], + plan.git_commit_args, + plan.git_push_args, + ] + + +def test_automation_executor_phase_checklist_and_plan_execution_helper_are_explicit(): + module_text = Path("scripts/reviewer_bot_lib/automation.py").read_text(encoding="utf-8") + + assert "EXECUTOR_PHASE_CHECKLIST = [" in module_text + assert '"audit_command_execution"' in module_text + assert '"spec_lock_update_command_execution"' in module_text + assert '"changed_file_validation_execution"' in module_text + assert '"branch_existence_check"' in module_text + assert '"pull_request_create"' in module_text + assert "def _resolve_accept_no_fls_changes_plan(" in module_text + assert "def _execute_accept_no_fls_changes_plan(" in module_text + assert "return _execute_accept_no_fls_changes_plan(bot, repo_root, execution_plan.record.issue_number, planning)" in module_text diff --git a/tests/integration/reviewer_bot/test_app_closed_issue_cleanup.py b/tests/integration/reviewer_bot/test_app_closed_issue_cleanup.py new file mode 100644 index 000000000..7f01413cd --- /dev/null +++ b/tests/integration/reviewer_bot/test_app_closed_issue_cleanup.py @@ -0,0 +1,93 @@ +import pytest + +from scripts.reviewer_bot_lib import review_state +from tests.fixtures.app_harness import AppHarness +from tests.fixtures.reviewer_bot import make_state + +pytestmark = pytest.mark.integration + + +def test_execute_run_closed_issue_comment_cleanup_persists_removed_review_entry(monkeypatch): + harness = AppHarness(monkeypatch) + harness.set_event( + EVENT_NAME="issue_comment", + EVENT_ACTION="created", + ISSUE_NUMBER=42, + IS_PULL_REQUEST="false", + ISSUE_STATE="closed", + ISSUE_AUTHOR="dana", + COMMENT_USER_TYPE="User", + COMMENT_SENDER_TYPE="User", + COMMENT_AUTHOR="dana", + COMMENT_AUTHOR_ID=101, + COMMENT_ID=100, + COMMENT_CREATED_AT="2026-03-17T10:00:00Z", + COMMENT_BODY="reviewer-bot validation close-path comment", + COMMENT_PERFORMED_VIA_GITHUB_APP="false", + ) + + initial_state = make_state() + review = review_state.ensure_review_entry(initial_state, 42, create=True) + assert review is not None + review["current_reviewer"] = "alice" + reloaded_state = make_state() + load_calls = {"count": 0} + save_calls = [] + sync_calls = [] + + def fake_load_state(*, fail_on_unavailable=False): + load_calls["count"] += 1 + if load_calls["count"] == 1: + return initial_state + return reloaded_state + + harness.stub_lock(acquire=lambda: None, release=lambda: True) + harness.stub_load_state(fake_load_state) + harness.stub_pass_until(lambda state: (state, [])) + harness.stub_sync_members(lambda state: (state, [])) + harness.stub_save_state(lambda state: save_calls.append("42" in state["active_reviews"]) or True) + harness.stub_sync_status_labels(lambda state, issue_numbers: sync_calls.append((state, list(issue_numbers))) or True) + + result = harness.run_execute() + + assert result.exit_code == 0 + assert save_calls == [False] + assert len(sync_calls) == 1 + assert sync_calls[0][0] is reloaded_state + assert sync_calls[0][1] == [42] + +def test_execute_run_closed_issue_comment_without_entry_skips_save(monkeypatch): + harness = AppHarness(monkeypatch) + harness.set_event( + EVENT_NAME="issue_comment", + EVENT_ACTION="created", + ISSUE_NUMBER=42, + IS_PULL_REQUEST="false", + ISSUE_STATE="closed", + ISSUE_AUTHOR="dana", + COMMENT_USER_TYPE="User", + COMMENT_SENDER_TYPE="User", + COMMENT_AUTHOR="dana", + COMMENT_AUTHOR_ID=101, + COMMENT_ID=100, + COMMENT_CREATED_AT="2026-03-17T10:00:00Z", + COMMENT_BODY="reviewer-bot validation close-path comment", + COMMENT_PERFORMED_VIA_GITHUB_APP="false", + ) + + state = make_state() + save_called = {"value": False} + sync_calls = [] + + harness.stub_lock(acquire=lambda: None, release=lambda: True) + harness.stub_load_state(lambda *, fail_on_unavailable=False: state) + harness.stub_pass_until(lambda current: (current, [])) + harness.stub_sync_members(lambda current: (current, [])) + harness.stub_save_state(lambda current: save_called.__setitem__("value", True) or True) + harness.stub_sync_status_labels(lambda current, issue_numbers: sync_calls.append(list(issue_numbers)) or False) + + result = harness.run_execute() + + assert result.exit_code == 0 + assert save_called["value"] is False + assert sync_calls == [[42]] diff --git a/tests/integration/reviewer_bot/test_app_execution.py b/tests/integration/reviewer_bot/test_app_execution.py new file mode 100644 index 000000000..64c7f974b --- /dev/null +++ b/tests/integration/reviewer_bot/test_app_execution.py @@ -0,0 +1,224 @@ +import json +from pathlib import Path + +import pytest + +from scripts.reviewer_bot_lib import reconcile +from tests.fixtures.app_harness import AppHarness +from tests.fixtures.reviewer_bot import make_state, make_tracked_review_state + +pytestmark = pytest.mark.integration + + +def test_app_harness_exposes_focused_runtime_services(monkeypatch): + harness = AppHarness(monkeypatch) + + assert harness.state_store is harness.runtime.state_store + assert harness.locks is harness.runtime.locks + assert harness.handlers is harness.runtime.handlers + assert harness.touch_tracker is harness.runtime.touch_tracker + + +def test_execute_run_reloads_state_before_syncing_status_labels(monkeypatch): + harness = AppHarness(monkeypatch) + harness.set_event(EVENT_NAME="issue_comment", EVENT_ACTION="created") + + initial_state = make_state() + reloaded_state = make_state() + load_calls = {"count": 0} + save_completed = {"value": False} + sync_inputs = {} + release_calls = [] + + def fake_load_state(*, fail_on_unavailable=False): + load_calls["count"] += 1 + if load_calls["count"] == 1: + return initial_state + return reloaded_state + + def fake_handle_comment_event(state): + assert state is initial_state + harness.runtime.collect_touched_item(42) + return True + + def fake_save_state(state): + assert state is initial_state + save_completed["value"] = True + return True + + def fake_sync_status_labels_for_items(state, issue_numbers): + sync_inputs["save_completed"] = save_completed["value"] + sync_inputs["state"] = state + sync_inputs["issue_numbers"] = list(issue_numbers) + assert state is reloaded_state + assert list(issue_numbers) == [42] + return True + + harness.stub_lock(acquire=lambda: None, release=lambda: release_calls.append("released") or True) + harness.stub_load_state(fake_load_state) + harness.stub_pass_until(lambda state: (state, [])) + harness.stub_sync_members(lambda state: (state, [])) + harness.stub_handler("handle_comment_event", fake_handle_comment_event) + harness.stub_save_state(fake_save_state) + harness.stub_sync_status_labels(fake_sync_status_labels_for_items) + + result = harness.run_execute() + + assert result.exit_code == 0 + assert result.state_changed is True + assert load_calls["count"] >= 2 + assert sync_inputs == { + "save_completed": True, + "state": reloaded_state, + "issue_numbers": [42], + } + assert release_calls == ["released"] + +def test_execute_run_returns_failure_when_save_state_fails(monkeypatch): + harness = AppHarness(monkeypatch) + harness.set_event(EVENT_NAME="issue_comment", EVENT_ACTION="created") + harness.stub_lock(acquire=lambda: None, release=lambda: True) + harness.stub_load_state(lambda *, fail_on_unavailable=False: make_state()) + harness.stub_pass_until(lambda state: (state, [])) + harness.stub_sync_members(lambda state: (state, [])) + harness.stub_handler("handle_comment_event", lambda state: True) + harness.stub_save_state(lambda state: False) + + result = harness.run_execute() + + assert result.exit_code == 1 + assert result.state_changed is True + + +def test_execute_run_releases_lock_after_save_failure(monkeypatch): + harness = AppHarness(monkeypatch) + harness.set_event(EVENT_NAME="issue_comment", EVENT_ACTION="created") + release_calls = [] + harness.stub_lock(acquire=lambda: None, release=lambda: release_calls.append("released") or True) + harness.stub_load_state(lambda *, fail_on_unavailable=False: make_state()) + harness.stub_pass_until(lambda state: (state, [])) + harness.stub_sync_members(lambda state: (state, [])) + harness.stub_handler("handle_comment_event", lambda state: True) + harness.stub_save_state(lambda state: False) + + result = harness.run_execute() + + assert result.exit_code == 1 + assert release_calls == ["released"] + + +def test_execute_run_persists_projection_repair_marker_after_projection_failure(monkeypatch): + harness = AppHarness(monkeypatch) + harness.set_event(EVENT_NAME="issue_comment", EVENT_ACTION="created") + initial_state = make_state() + reloaded_state = make_state() + make_tracked_review_state(reloaded_state, 42, reviewer="alice") + load_count = {"value": 0} + saved_states = [] + + def fake_load_state(*, fail_on_unavailable=False): + load_count["value"] += 1 + if load_count["value"] == 1: + return initial_state + return reloaded_state + + def fake_handle_comment_event(state): + harness.runtime.collect_touched_item(42) + return True + + def fake_save_state(state): + saved_states.append(state.copy()) + return True + + harness.stub_lock(acquire=lambda: None, release=lambda: True) + harness.stub_load_state(fake_load_state) + harness.stub_pass_until(lambda state: (state, [])) + harness.stub_sync_members(lambda state: (state, [])) + harness.stub_handler("handle_comment_event", fake_handle_comment_event) + harness.stub_save_state(fake_save_state) + harness.stub_sync_status_labels(lambda state, issue_numbers: (_ for _ in ()).throw(RuntimeError("projection failed"))) + + result = harness.run_execute() + + assert result.exit_code == 0 + assert result.state_changed is True + assert len(saved_states) == 2 + assert saved_states[-1]["active_reviews"]["42"]["sidecars"]["repair_markers"]["status_label_projection"]["kind"] == "projection_failure" + + +def test_execute_run_returns_failure_when_lock_release_fails(monkeypatch): + harness = AppHarness(monkeypatch) + harness.set_event(EVENT_NAME="issue_comment", EVENT_ACTION="created") + harness.stub_lock(acquire=lambda: None, release=lambda: False) + harness.stub_load_state(lambda *, fail_on_unavailable=False: make_state()) + harness.stub_pass_until(lambda state: (state, [])) + harness.stub_sync_members(lambda state: (state, [])) + harness.stub_handler("handle_comment_event", lambda state: False) + + result = harness.run_execute() + + assert result.exit_code == 1 + assert result.release_failed is True + +def test_execute_run_returns_failure_for_invalid_workflow_run_context(monkeypatch): + harness = AppHarness(monkeypatch) + harness.set_event( + EVENT_NAME="workflow_run", + EVENT_ACTION="completed", + REVIEWER_BOT_WORKFLOW_KIND="reconcile", + WORKFLOW_RUN_TRIGGERING_CONCLUSION="success", + ) + harness.stub_lock(acquire=lambda: None, release=lambda: True) + harness.stub_load_state(lambda *, fail_on_unavailable=False: make_state()) + harness.stub_pass_until(lambda state: (state, [])) + harness.stub_sync_members(lambda state: (state, [])) + monkeypatch.setattr( + reconcile, + "handle_workflow_run_event_result", + lambda bot, state: (_ for _ in ()).throw(RuntimeError("invalid deferred context")), + ) + + result = harness.run_execute() + + assert result.exit_code == 1 + assert result.state_changed is False + + +def test_d4a_app_branch_to_phase_map_is_frozen_pre_edit(): + app_text = Path("scripts/reviewer_bot_lib/app.py").read_text(encoding="utf-8") + + assert "if lock_required:" in app_text + assert "state = bot.state_store.load_state(fail_on_unavailable=lock_required)" in app_text + assert "state, restored = bot.adapters.workflow.process_pass_until_expirations(state)" in app_text + assert "state, sync_changes = bot.adapters.workflow.sync_members_with_queue(state)" in app_text + assert "if workflow_run_result is not None:" in app_text + assert "touched_items = workflow_run_result.touched_items" in app_text + assert "elif schedule_result is not None:" in app_text + assert "touched_items = schedule_result.touched_items" in app_text + assert "touched_items = bot.drain_touched_items()" in app_text + assert '_revalidate_epoch(bot, loaded_epoch, "authoritative save")' in app_text + assert "if not bot.state_store.save_state(state):" in app_text + assert "state = bot.state_store.load_state(fail_on_unavailable=True)" in app_text + assert '_revalidate_epoch(bot, loaded_epoch, "status-label projection")' in app_text + assert "if _mark_projection_repair_needed(bot, state, touched_items, str(exc)):" in app_text + assert "if not bot.locks.release():" in app_text + + +def test_d4b_post_edit_phase_map_matches_pre_edit_transaction_shape(): + pre_map = json.loads(Path("tests/fixtures/equivalence/app/transaction_phase_map.json").read_text(encoding="utf-8")) + post_map = json.loads(Path("tests/fixtures/equivalence/app/post_edit_transaction_phase_map.json").read_text(encoding="utf-8")) + + assert post_map["harness_id"] == "D4b app post-edit transaction phase map" + assert post_map["branch_to_phase"] == pre_map["branch_to_phase"] + + +def test_m1_app_consumes_typed_workflow_run_result_instead_of_boolean_only_signal(): + app_text = Path("scripts/reviewer_bot_lib/app.py").read_text(encoding="utf-8") + + assert "workflow_run_result: reconcile.WorkflowRunHandlerResult | None = None" in app_text + assert "workflow_run_result = reconcile.handle_workflow_run_event_result(bot, state)" in app_text + assert "state_changed = workflow_run_result.state_changed" in app_text + assert "if workflow_run_result is not None:" in app_text + assert "touched_items = workflow_run_result.touched_items" in app_text + assert "elif schedule_result is not None:" in app_text + assert "touched_items = schedule_result.touched_items" in app_text diff --git a/tests/integration/reviewer_bot/test_app_reviewer_board_preview.py b/tests/integration/reviewer_bot/test_app_reviewer_board_preview.py new file mode 100644 index 000000000..ab336da76 --- /dev/null +++ b/tests/integration/reviewer_bot/test_app_reviewer_board_preview.py @@ -0,0 +1,128 @@ +import pytest + +from scripts.reviewer_bot_lib import review_state +from tests.fixtures.app_harness import AppHarness +from tests.fixtures.reviewer_bot import make_state, valid_reviewer_board_metadata + +pytestmark = pytest.mark.integration + + +def test_execute_run_preview_reviewer_board_disabled_is_clean_noop(monkeypatch, capsys): + harness = AppHarness(monkeypatch) + harness.set_event( + EVENT_NAME="workflow_dispatch", + EVENT_ACTION="", + MANUAL_ACTION="preview-reviewer-board", + REVIEWER_BOARD_ENABLED="false", + ) + + harness.stub_load_state(lambda *, fail_on_unavailable=False: make_state()) + harness.stub_lock(acquire=lambda: (_ for _ in ()).throw(AssertionError("preview should not acquire lock"))) + harness.stub_pass_until(lambda state: (_ for _ in ()).throw(AssertionError("preview should skip pass-until processing"))) + harness.stub_sync_members(lambda state: (_ for _ in ()).throw(AssertionError("preview should skip member sync"))) + harness.stub_save_state(lambda state: (_ for _ in ()).throw(AssertionError("preview should not save state"))) + harness.stub_sync_status_labels(lambda state, issue_numbers: (_ for _ in ()).throw(AssertionError("preview should not sync labels"))) + + result = harness.run_execute() + + assert result.exit_code == 0 + output = capsys.readouterr().out + assert "Reviewer board preview skipped: reviewer board is disabled." in output + +def test_execute_run_preview_reviewer_board_missing_token_fails_clearly(monkeypatch, capsys): + harness = AppHarness(monkeypatch) + harness.set_event( + EVENT_NAME="workflow_dispatch", + EVENT_ACTION="", + MANUAL_ACTION="preview-reviewer-board", + REVIEWER_BOARD_ENABLED="true", + ) + monkeypatch.setattr(harness.runtime, "_reviewer_board_project_metadata", None, raising=False) + + harness.stub_load_state(lambda *, fail_on_unavailable=False: make_state()) + harness.stub_lock(acquire=lambda: (_ for _ in ()).throw(AssertionError("preview should not acquire lock"))) + harness.stub_pass_until(lambda state: (_ for _ in ()).throw(AssertionError("preview should skip pass-until processing"))) + harness.stub_sync_members(lambda state: (_ for _ in ()).throw(AssertionError("preview should skip member sync"))) + + result = harness.run_execute() + + assert result.exit_code == 1 + captured = capsys.readouterr().err + assert "REVIEWER_BOARD_TOKEN not set" in captured or any( + "REVIEWER_BOARD_TOKEN not set" in record["message"] + for record in harness.runtime.logger.records + ) + +def test_execute_run_preview_reviewer_board_invalid_manifest_fails_clearly(monkeypatch, capsys): + harness = AppHarness(monkeypatch) + harness.set_event( + EVENT_NAME="workflow_dispatch", + EVENT_ACTION="", + MANUAL_ACTION="preview-reviewer-board", + REVIEWER_BOARD_ENABLED="true", + REVIEWER_BOARD_TOKEN="board-token", + ) + monkeypatch.setattr(harness.runtime, "_reviewer_board_project_metadata", None, raising=False) + + harness.stub_load_state(lambda *, fail_on_unavailable=False: make_state()) + harness.stub_lock(acquire=lambda: (_ for _ in ()).throw(AssertionError("preview should not acquire lock"))) + monkeypatch.setattr( + harness.runtime, + "github_graphql", + lambda query, variables=None, *, token=None: { + "data": { + "organization": { + "projectV2": { + "id": "PVT_kwDOB", + "title": "Reviewer Board", + "fields": {"nodes": []}, + } + } + } + }, + ) + + result = harness.run_execute() + + assert result.exit_code == 1 + captured = capsys.readouterr().err + assert "Missing reviewer board field: Review State" in captured or any( + "Missing reviewer board field: Review State" in record["message"] + for record in harness.runtime.logger.records + ) + +def test_execute_run_preview_reviewer_board_is_read_only(monkeypatch, capsys): + harness = AppHarness(monkeypatch) + harness.set_event( + EVENT_NAME="workflow_dispatch", + EVENT_ACTION="", + MANUAL_ACTION="preview-reviewer-board", + REVIEWER_BOARD_ENABLED="true", + REVIEWER_BOARD_TOKEN="board-token", + ISSUE_NUMBER=42, + ) + monkeypatch.setattr(harness.runtime, "_reviewer_board_project_metadata", None, raising=False) + + state = make_state() + state["status_projection_epoch"] = "status_projection_v1" + review = review_state.ensure_review_entry(state, 42, create=True) + assert review is not None + review["current_reviewer"] = "alice" + review["assigned_at"] = "2026-03-20T12:34:56Z" + review["active_cycle_started_at"] = "2026-03-20T12:34:56Z" + + harness.stub_load_state(lambda *, fail_on_unavailable=False: state) + harness.stub_lock(acquire=lambda: (_ for _ in ()).throw(AssertionError("preview should not acquire lock"))) + harness.stub_pass_until(lambda current: (_ for _ in ()).throw(AssertionError("preview should skip pass-until processing"))) + harness.stub_sync_members(lambda current: (_ for _ in ()).throw(AssertionError("preview should skip member sync"))) + harness.stub_save_state(lambda current: (_ for _ in ()).throw(AssertionError("preview should not save state"))) + harness.stub_sync_status_labels(lambda current, issue_numbers: (_ for _ in ()).throw(AssertionError("preview should not sync labels"))) + monkeypatch.setattr(harness.runtime, "github_graphql", lambda query, variables=None, *, token=None: valid_reviewer_board_metadata()) + harness.runtime.get_issue_or_pr_snapshot = lambda issue_number: {"number": issue_number, "state": "open", "pull_request": None, "labels": []} + + result = harness.run_execute() + + assert result.exit_code == 0 + output = capsys.readouterr().out + assert "classification: open_tracked_assigned" in output + assert "ensure_membership: true" in output diff --git a/tests/integration/reviewer_bot/test_app_schedule_bookkeeping.py b/tests/integration/reviewer_bot/test_app_schedule_bookkeeping.py new file mode 100644 index 000000000..c3c248c7d --- /dev/null +++ b/tests/integration/reviewer_bot/test_app_schedule_bookkeeping.py @@ -0,0 +1,147 @@ +import pytest + +from scripts.reviewer_bot_lib import maintenance, maintenance_schedule, review_state +from scripts.reviewer_bot_lib.config import GitHubApiResult +from tests.fixtures.app_harness import AppHarness +from tests.fixtures.reviewer_bot import make_state + +pytestmark = pytest.mark.integration + +def test_execute_run_schedule_sweeper_bookkeeping_only_mutation_still_saves_state(monkeypatch): + harness = AppHarness(monkeypatch) + harness.set_event(EVENT_NAME="schedule", EVENT_ACTION="") + state = make_state() + review = review_state.ensure_review_entry(state, 42, create=True) + assert review is not None + review["current_reviewer"] = "alice" + + save_calls = [] + + def fake_sweep(bot, current): + current["active_reviews"]["42"]["sidecars"]["reconciled_source_events"]["pull_request_review:500"] = { + "reconciled_at": None + } + return True + + harness.stub_lock(acquire=lambda: None, release=lambda: True) + harness.stub_load_state(lambda *, fail_on_unavailable=False: state) + harness.stub_pass_until(lambda current: (current, [])) + harness.stub_sync_members(lambda current: (current, [])) + + def fake_schedule_result(bot, current): + fake_sweep(bot, current) + return maintenance.ScheduleHandlerResult(True, []) + + monkeypatch.setattr(maintenance, "handle_scheduled_check_result", fake_schedule_result) + harness.stub_save_state( + lambda current: save_calls.append( + list(current["active_reviews"]["42"]["sidecars"]["reconciled_source_events"]) + ) or True + ) + harness.stub_sync_status_labels(lambda current, issue_numbers: True) + + result = harness.run_execute() + + assert result.exit_code == 0 + assert save_calls == [["pull_request_review:500"]] + +def test_execute_run_schedule_reviewer_review_activity_only_repair_still_saves_state(monkeypatch): + harness = AppHarness(monkeypatch) + harness.set_event(EVENT_NAME="schedule", EVENT_ACTION="") + state = make_state() + review = review_state.ensure_review_entry(state, 42, create=True) + assert review is not None + review["current_reviewer"] = "alice" + review["active_cycle_started_at"] = "2026-03-17T09:00:00Z" + review["reviewer_review"] = { + "accepted": { + "semantic_key": "pull_request_review:10", + "timestamp": "2026-03-17T10:01:00Z", + "actor": "alice", + "reviewed_head_sha": "head-1", + "source_precedence": 1, + "payload": {}, + }, + "seen_keys": ["pull_request_review:10"], + } + review["last_reviewer_activity"] = "2026-03-17T09:00:00Z" + review["transition_warning_sent"] = "2026-03-18T00:00:00Z" + review["transition_notice_sent_at"] = "2026-03-25T00:00:00Z" + + save_calls = [] + + def fake_github_api_request(method, endpoint, data=None, extra_headers=None, **kwargs): + if endpoint == "pulls/42": + return GitHubApiResult( + 200, + {"state": "open", "head": {"sha": "head-1"}}, + {}, + "ok", + True, + None, + 0, + None, + ) + if endpoint.startswith("pulls/42/reviews"): + return GitHubApiResult( + 200, + [ + { + "id": 10, + "state": "COMMENTED", + "submitted_at": "2026-03-17T10:01:00Z", + "commit_id": "head-1", + "user": {"login": "alice"}, + } + ], + {}, + "ok", + True, + None, + 0, + None, + ) + raise AssertionError(endpoint) + + harness.stub_lock(acquire=lambda: None, release=lambda: True) + harness.stub_load_state(lambda *, fail_on_unavailable=False: state) + harness.stub_pass_until(lambda current: (current, [])) + harness.stub_sync_members(lambda current: (current, [])) + + def fake_schedule_result(bot, current): + current_review = current["active_reviews"]["42"] + current_review["last_reviewer_activity"] = "2026-03-17T10:01:00Z" + current_review["transition_warning_sent"] = None + current_review["transition_notice_sent_at"] = None + return maintenance.ScheduleHandlerResult(True, []) + + monkeypatch.setattr(maintenance, "handle_scheduled_check_result", fake_schedule_result) + harness.stub_save_state( + lambda current: save_calls.append( + { + "last_reviewer_activity": current["active_reviews"]["42"]["last_reviewer_activity"], + "transition_warning_sent": current["active_reviews"]["42"]["transition_warning_sent"], + "transition_notice_sent_at": current["active_reviews"]["42"]["transition_notice_sent_at"], + } + ) + or True + ) + harness.stub_sync_status_labels(lambda current, issue_numbers: True) + + result = harness.run_execute() + + assert result.exit_code == 0 + assert save_calls == [ + { + "last_reviewer_activity": "2026-03-17T10:01:00Z", + "transition_warning_sent": None, + "transition_notice_sent_at": None, + } + ] + + +def test_m2_schedule_handler_exposes_typed_result_shape(): + assert maintenance.ScheduleHandlerResult is maintenance_schedule.ScheduleHandlerResult + + fields = maintenance_schedule.ScheduleHandlerResult.__dataclass_fields__ + assert list(fields) == ["state_changed", "touched_items"] diff --git a/tests/integration/reviewer_bot/test_app_schedule_repairs.py b/tests/integration/reviewer_bot/test_app_schedule_repairs.py new file mode 100644 index 000000000..977fc8002 --- /dev/null +++ b/tests/integration/reviewer_bot/test_app_schedule_repairs.py @@ -0,0 +1,191 @@ +import json +from pathlib import Path + +import pytest + +pytestmark = pytest.mark.integration + +from scripts.reviewer_bot_lib import maintenance, review_state +from scripts.reviewer_bot_lib.config import STATUS_PROJECTION_EPOCH +from tests.fixtures.app_harness import AppHarness +from tests.fixtures.reviewer_bot import make_state + + +def test_execute_run_schedule_status_projection_epoch_mismatch_triggers_label_repair_sweep(monkeypatch): + harness = AppHarness(monkeypatch) + harness.set_event(EVENT_NAME="schedule", EVENT_ACTION="") + state = make_state() + state["status_projection_epoch"] = "status_projection_v1" + review = review_state.ensure_review_entry(state, 42, create=True) + assert review is not None + review["current_reviewer"] = "alice" + synced_issue_numbers = [] + saved_epochs = [] + + harness.stub_lock(acquire=lambda: None, release=lambda: True) + harness.stub_load_state(lambda *, fail_on_unavailable=False: state) + harness.stub_pass_until(lambda current: (current, [])) + harness.stub_sync_members(lambda current: (current, [])) + monkeypatch.setattr( + maintenance, + "handle_scheduled_check_result", + lambda bot, current: maintenance.ScheduleHandlerResult(False, []), + ) + monkeypatch.setattr(maintenance.reviews, "list_open_items_with_status_labels", lambda bot: [99]) + harness.stub_sync_status_labels(lambda current, issue_numbers: synced_issue_numbers.extend(issue_numbers) or True) + harness.stub_save_state(lambda current: saved_epochs.append(current.get("status_projection_epoch")) or True) + + result = harness.run_execute() + + assert result.exit_code == 0 + assert synced_issue_numbers == [42, 99] + assert saved_epochs[-1] == STATUS_PROJECTION_EPOCH + +def test_execute_run_schedule_status_projection_epoch_not_advanced_on_label_sync_failure(monkeypatch): + harness = AppHarness(monkeypatch) + harness.set_event(EVENT_NAME="schedule", EVENT_ACTION="") + state = make_state() + state["status_projection_epoch"] = "status_projection_v1" + review = review_state.ensure_review_entry(state, 42, create=True) + assert review is not None + review["current_reviewer"] = "alice" + saved_epochs = [] + + harness.stub_lock(acquire=lambda: None, release=lambda: True) + harness.stub_load_state(lambda *, fail_on_unavailable=False: state) + harness.stub_pass_until(lambda current: (current, [])) + harness.stub_sync_members(lambda current: (current, [])) + monkeypatch.setattr( + maintenance, + "handle_scheduled_check_result", + lambda bot, current: maintenance.ScheduleHandlerResult(False, []), + ) + monkeypatch.setattr(maintenance.reviews, "list_open_items_with_status_labels", lambda bot: [42]) + harness.stub_sync_status_labels(lambda current, issue_numbers: (_ for _ in ()).throw(RuntimeError("projection exploded"))) + harness.stub_save_state(lambda current: saved_epochs.append(current.get("status_projection_epoch")) or True) + + result = harness.run_execute() + + assert result.exit_code == 0 + assert all(epoch != STATUS_PROJECTION_EPOCH for epoch in saved_epochs) + +def test_execute_run_records_repair_needed_when_projection_fails(monkeypatch, tmp_path): + harness = AppHarness(monkeypatch) + state = make_state() + review = review_state.ensure_review_entry(state, 42, create=True) + assert review is not None + review["current_reviewer"] = "alice" + harness.set_event( + EVENT_NAME="issue_comment", + EVENT_ACTION="created", + IS_PULL_REQUEST="false", + ISSUE_NUMBER="42", + ISSUE_AUTHOR="dana", + ISSUE_STATE="open", + COMMENT_USER_TYPE="User", + COMMENT_SENDER_TYPE="User", + COMMENT_AUTHOR="dana", + COMMENT_AUTHOR_ID="101", + COMMENT_ID="100", + COMMENT_CREATED_AT="2026-03-17T10:00:00Z", + COMMENT_BODY="plain text", + COMMENT_PERFORMED_VIA_GITHUB_APP="false", + ) + harness.stub_lock(acquire=lambda: None, release=lambda: True) + saved_states = [] + + def fake_load_state(*, fail_on_unavailable=False): + return json.loads(json.dumps(state)) + + def fake_save_state(updated_state): + saved_states.append(json.loads(json.dumps(updated_state))) + state.clear() + state.update(json.loads(json.dumps(updated_state))) + return True + + harness.stub_load_state(fake_load_state) + harness.stub_save_state(fake_save_state) + harness.stub_pass_until(lambda current_state: (current_state, [])) + harness.stub_sync_members(lambda current_state: (current_state, [])) + harness.runtime.get_issue_or_pr_snapshot = lambda issue_number: {"number": issue_number, "state": "open", "labels": [], "pull_request": None} + harness.stub_sync_status_labels(lambda current_state, issue_numbers: (_ for _ in ()).throw(RuntimeError("projection failed"))) + output_path = tmp_path / "github-output.txt" + monkeypatch.setenv("GITHUB_OUTPUT", str(output_path)) + + result = harness.run_execute() + + assert result.exit_code == 0 + assert state["active_reviews"]["42"]["sidecars"]["repair_markers"]["status_label_projection"]["kind"] == "projection_failure" + assert len(saved_states) >= 2 + +def test_schedule_overdue_check_does_not_repeat_warning_after_stale_review_repair(monkeypatch): + harness = AppHarness(monkeypatch) + harness.set_event(EVENT_NAME="schedule", EVENT_ACTION="") + state = make_state() + review = review_state.ensure_review_entry(state, 42, create=True) + assert review is not None + review["current_reviewer"] = "iglesias" + review["assigned_at"] = "2026-02-26T04:58:03Z" + review["active_cycle_started_at"] = "2026-02-26T04:58:03Z" + review["last_reviewer_activity"] = "2026-03-18T01:09:05Z" + review["transition_warning_sent"] = "2026-04-01T12:12:04Z" + + saved_warning_values = [] + posted_comments = [] + + def fake_load_state(*args, **kwargs): + return state + + def fake_sweep(bot, current): + review_state.record_reviewer_activity( + current["active_reviews"]["42"], + "2026-03-18T01:09:05Z", + ) + return False + + harness.stub_lock(acquire=lambda: None, release=lambda: True) + harness.stub_load_state(lambda *, fail_on_unavailable=False: fake_load_state()) + harness.stub_pass_until(lambda current: (current, [])) + harness.stub_sync_members(lambda current: (current, [])) + monkeypatch.setattr(maintenance, "sweep_deferred_gaps", fake_sweep) + monkeypatch.setattr( + maintenance, + "handle_scheduled_check_result", + lambda bot, current: maintenance.ScheduleHandlerResult(False, []), + ) + harness.runtime.post_comment = lambda issue_number, body: posted_comments.append((issue_number, body)) or True + harness.stub_save_state( + lambda current: saved_warning_values.append(current["active_reviews"]["42"]["transition_warning_sent"]) or True + ) + harness.stub_sync_status_labels(lambda current, issue_numbers: False) + + first = harness.run_execute() + second = harness.run_execute() + + assert first.exit_code == 0 + assert second.exit_code == 0 + assert posted_comments == [] + assert review["transition_warning_sent"] == "2026-04-01T12:12:04Z" + assert saved_warning_values == [] + + +def test_c5c_sweeper_cleanup_keeps_mutation_apply_but_removes_embedded_diagnosis_defs(): + module_text = Path("scripts/reviewer_bot_lib/sweeper.py").read_text(encoding="utf-8") + + assert "def observer_run_reason_from_details(" not in module_text + assert "def can_mark_observer_run_missing(" not in module_text + assert "def classify_artifact_gap_reason(" not in module_text + assert "def correlate_candidate_observer_runs(" not in module_text + assert "def correlate_run_artifacts_exact(" not in module_text + assert "def evaluate_deferred_gap_state(" not in module_text + assert "def _repair_visible_review_gap(" in module_text + assert "deferred_gap_diagnosis.evaluate_deferred_gap_state(" in module_text + + +def test_h4a_review_submission_gap_fixture_keeps_only_one_repair_flow(): + matrix = json.loads( + Path("tests/fixtures/equivalence/review_submission_gap_repair/scenario_matrix.json").read_text(encoding="utf-8") + ) + + assert len(matrix["scenarios"]) == 1 + assert matrix["scenarios"][0]["expected_repair_category"] == "review_submission_repair" diff --git a/tests/integration/reviewer_bot/test_app_state_unavailable.py b/tests/integration/reviewer_bot/test_app_state_unavailable.py new file mode 100644 index 000000000..3bf060217 --- /dev/null +++ b/tests/integration/reviewer_bot/test_app_state_unavailable.py @@ -0,0 +1,61 @@ +import pytest + +from tests.fixtures.app_harness import AppHarness + +pytestmark = pytest.mark.integration + + +def test_execute_run_mutating_event_fails_closed_when_state_unavailable(monkeypatch): + harness = AppHarness(monkeypatch) + harness.set_event(EVENT_NAME="issue_comment", EVENT_ACTION="created") + harness.stub_lock(acquire=lambda: None, release=lambda: True) + harness.runtime.stub_state_unavailable("state unavailable") + + result = harness.run_execute() + + assert result.exit_code == 1 + assert result.state_changed is False + +def test_execute_run_mutating_event_does_not_sync_or_save_when_state_unavailable(monkeypatch): + harness = AppHarness(monkeypatch) + harness.set_event(EVENT_NAME="issue_comment", EVENT_ACTION="created") + harness.stub_lock(acquire=lambda: None, release=lambda: True) + + called = { + "pass_until": False, + "sync": False, + "handler": False, + "save": False, + } + + def track_pass_until(state): + called["pass_until"] = True + return state, [] + + def track_sync(state): + called["sync"] = True + return state, [] + + def track_handler(state): + called["handler"] = True + return True + + def track_save(state): + called["save"] = True + return True + + harness.runtime.stub_state_unavailable("state unavailable") + harness.stub_pass_until(track_pass_until) + harness.stub_sync_members(track_sync) + harness.stub_handler("handle_comment_event", track_handler) + harness.stub_save_state(track_save) + + result = harness.run_execute() + + assert result.exit_code == 1 + assert called == { + "pass_until": False, + "sync": False, + "handler": False, + "save": False, + } diff --git a/tests/integration/reviewer_bot/test_app_workflow_run_bookkeeping.py b/tests/integration/reviewer_bot/test_app_workflow_run_bookkeeping.py new file mode 100644 index 000000000..64171cef7 --- /dev/null +++ b/tests/integration/reviewer_bot/test_app_workflow_run_bookkeeping.py @@ -0,0 +1,253 @@ +import json +from pathlib import Path + +import pytest + +pytestmark = pytest.mark.integration + +from scripts.reviewer_bot_lib import reconcile, review_state +from tests.fixtures.app_harness import AppHarness +from tests.fixtures.reviewer_bot import make_state + + +def test_execute_run_workflow_run_bookkeeping_only_reconcile_still_saves_state(tmp_path, monkeypatch): + harness = AppHarness(monkeypatch) + harness.set_workflow_run_name("Reviewer Bot PR Review Submitted Observer") + harness.set_event( + EVENT_NAME="workflow_run", + EVENT_ACTION="completed", + REVIEWER_BOT_WORKFLOW_KIND="reconcile", + WORKFLOW_RUN_TRIGGERING_CONCLUSION="success", + ) + + state = make_state() + review = review_state.ensure_review_entry(state, 42, create=True) + assert review is not None + review["current_reviewer"] = "bob" + review["sidecars"]["deferred_gaps"]["pull_request_review:11"] = {"reason": "artifact_missing"} + + payload_path = tmp_path / "deferred-review.json" + payload_path.write_text( + json.dumps( + { + "schema_version": 2, + "source_workflow_name": "Reviewer Bot PR Review Submitted Observer", + "source_workflow_file": ".github/workflows/reviewer-bot-pr-review-submitted-observer.yml", + "source_run_id": 700, + "source_run_attempt": 1, + "source_event_name": "pull_request_review", + "source_event_action": "submitted", + "source_event_key": "pull_request_review:11", + "pr_number": 42, + "review_id": 11, + "source_submitted_at": "2026-03-17T10:00:00Z", + "source_review_state": "COMMENTED", + "source_commit_id": "head-1", + "actor_login": "alice", + } + ), + encoding="utf-8", + ) + harness.set_event( + DEFERRED_CONTEXT_PATH=str(payload_path), + WORKFLOW_RUN_TRIGGERING_ID="700", + WORKFLOW_RUN_TRIGGERING_ATTEMPT="1", + WORKFLOW_RUN_TRIGGERING_CONCLUSION="success", + ) + + save_snapshots = [] + synced_issue_numbers = [] + + harness.stub_lock(acquire=lambda: None, release=lambda: True) + harness.stub_load_state(lambda *, fail_on_unavailable=False: state) + harness.stub_pass_until(lambda current: (current, [])) + harness.stub_sync_members(lambda current: (current, [])) + def fake_workflow_run_result(bot, current): + harness.runtime.collect_touched_item(42) + current["active_reviews"]["42"]["sidecars"]["reconciled_source_events"]["pull_request_review:11"] = {"reconciled_at": None} + current["active_reviews"]["42"]["sidecars"]["deferred_gaps"].pop("pull_request_review:11", None) + return reconcile.WorkflowRunHandlerResult(True, [42]) + + monkeypatch.setattr(reconcile, "handle_workflow_run_event_result", fake_workflow_run_result) + harness.stub_save_state( + lambda current: save_snapshots.append( + { + "reconciled": list(current["active_reviews"]["42"]["sidecars"]["reconciled_source_events"]), + "gap_present": "pull_request_review:11" in current["active_reviews"]["42"]["sidecars"]["deferred_gaps"], + } + ) + or True + ) + harness.stub_sync_status_labels(lambda current, issue_numbers: synced_issue_numbers.extend(issue_numbers) or True) + + result = harness.run_execute() + + assert result.exit_code == 0 + assert save_snapshots == [{"reconciled": ["pull_request_review:11"], "gap_present": False}] + assert synced_issue_numbers == [42] + +def test_execute_run_workflow_run_deferred_comment_bookkeeping_only_reconcile_still_saves_state( + tmp_path, monkeypatch +): + harness = AppHarness(monkeypatch) + harness.set_workflow_run_name("Reviewer Bot PR Comment Observer") + harness.set_event( + EVENT_NAME="workflow_run", + EVENT_ACTION="completed", + REVIEWER_BOT_WORKFLOW_KIND="reconcile", + WORKFLOW_RUN_TRIGGERING_CONCLUSION="success", + ) + + state = make_state() + review = review_state.ensure_review_entry(state, 42, create=True) + assert review is not None + review["current_reviewer"] = "alice" + review["sidecars"]["deferred_gaps"]["issue_comment:210"] = {"reason": "artifact_missing"} + + payload_path = tmp_path / "deferred-comment.json" + payload_path.write_text( + json.dumps( + { + "schema_version": 2, + "source_workflow_name": "Reviewer Bot PR Comment Observer", + "source_workflow_file": ".github/workflows/reviewer-bot-pr-comment-observer.yml", + "source_run_id": 710, + "source_run_attempt": 1, + "source_event_name": "issue_comment", + "source_event_action": "created", + "source_event_key": "issue_comment:210", + "pr_number": 42, + "comment_id": 210, + "comment_class": "command_only", + "has_non_command_text": False, + "source_body_digest": "digest", + "source_created_at": "2026-03-17T10:00:00Z", + "actor_login": "bob", + } + ), + encoding="utf-8", + ) + harness.set_event( + DEFERRED_CONTEXT_PATH=str(payload_path), + WORKFLOW_RUN_TRIGGERING_ID="710", + WORKFLOW_RUN_TRIGGERING_ATTEMPT="1", + WORKFLOW_RUN_TRIGGERING_CONCLUSION="success", + ) + + save_snapshots = [] + + harness.stub_lock(acquire=lambda: None, release=lambda: True) + harness.stub_load_state(lambda *, fail_on_unavailable=False: state) + harness.stub_pass_until(lambda current: (current, [])) + harness.stub_sync_members(lambda current: (current, [])) + def fake_comment_workflow_run_result(bot, current): + harness.runtime.collect_touched_item(42) + current["active_reviews"]["42"]["sidecars"]["reconciled_source_events"]["issue_comment:210"] = {"reconciled_at": None} + current["active_reviews"]["42"]["sidecars"]["deferred_gaps"].pop("issue_comment:210", None) + return reconcile.WorkflowRunHandlerResult(True, [42]) + + monkeypatch.setattr(reconcile, "handle_workflow_run_event_result", fake_comment_workflow_run_result) + harness.stub_save_state( + lambda current: save_snapshots.append( + { + "reconciled": list(current["active_reviews"]["42"]["sidecars"]["reconciled_source_events"]), + "gap_present": "issue_comment:210" in current["active_reviews"]["42"]["sidecars"]["deferred_gaps"], + } + ) + or True + ) + harness.stub_sync_status_labels(lambda current, issue_numbers: True) + + result = harness.run_execute() + + assert result.exit_code == 0 + assert save_snapshots == [{"reconciled": ["issue_comment:210"], "gap_present": False}] + +def test_execute_run_workflow_run_deferred_review_comment_bookkeeping_only_reconcile_still_saves_state( + tmp_path, monkeypatch +): + harness = AppHarness(monkeypatch) + harness.set_workflow_run_name("Reviewer Bot PR Review Comment Observer") + harness.set_event( + EVENT_NAME="workflow_run", + EVENT_ACTION="completed", + REVIEWER_BOT_WORKFLOW_KIND="reconcile", + WORKFLOW_RUN_TRIGGERING_CONCLUSION="success", + ) + + state = make_state() + review = review_state.ensure_review_entry(state, 42, create=True) + assert review is not None + review["current_reviewer"] = "alice" + review["sidecars"]["deferred_gaps"]["pull_request_review_comment:310"] = {"reason": "artifact_missing"} + + payload_path = tmp_path / "deferred-review-comment.json" + payload_path.write_text( + json.dumps( + { + "schema_version": 2, + "source_workflow_name": "Reviewer Bot PR Review Comment Observer", + "source_workflow_file": ".github/workflows/reviewer-bot-pr-review-comment-observer.yml", + "source_run_id": 711, + "source_run_attempt": 1, + "source_event_name": "pull_request_review_comment", + "source_event_action": "created", + "source_event_key": "pull_request_review_comment:310", + "pr_number": 42, + "comment_id": 310, + "comment_class": "plain_text", + "has_non_command_text": True, + "source_body_digest": "digest", + "source_created_at": "2026-03-17T10:00:00Z", + "actor_login": "alice", + } + ), + encoding="utf-8", + ) + harness.set_event( + DEFERRED_CONTEXT_PATH=str(payload_path), + WORKFLOW_RUN_TRIGGERING_ID="711", + WORKFLOW_RUN_TRIGGERING_ATTEMPT="1", + WORKFLOW_RUN_TRIGGERING_CONCLUSION="success", + ) + + save_snapshots = [] + + harness.stub_lock(acquire=lambda: None, release=lambda: True) + harness.stub_load_state(lambda *, fail_on_unavailable=False: state) + harness.stub_pass_until(lambda current: (current, [])) + harness.stub_sync_members(lambda current: (current, [])) + + def fake_review_comment_workflow_run_result(bot, current): + harness.runtime.collect_touched_item(42) + current["active_reviews"]["42"]["sidecars"]["reconciled_source_events"]["pull_request_review_comment:310"] = {"reconciled_at": None} + current["active_reviews"]["42"]["sidecars"]["deferred_gaps"].pop("pull_request_review_comment:310", None) + return reconcile.WorkflowRunHandlerResult(True, [42]) + + monkeypatch.setattr(reconcile, "handle_workflow_run_event_result", fake_review_comment_workflow_run_result) + harness.stub_save_state( + lambda current: save_snapshots.append( + { + "reconciled": list(current["active_reviews"]["42"]["sidecars"]["reconciled_source_events"]), + "gap_present": "pull_request_review_comment:310" + in current["active_reviews"]["42"]["sidecars"]["deferred_gaps"], + } + ) + or True + ) + harness.stub_sync_status_labels(lambda current, issue_numbers: True) + + result = harness.run_execute() + + assert result.exit_code == 0 + assert save_snapshots == [ + {"reconciled": ["pull_request_review_comment:310"], "gap_present": False} + ] + + +def test_m1_reconcile_exposes_typed_workflow_run_result_shape(): + reconcile_text = Path("scripts/reviewer_bot_lib/reconcile.py").read_text(encoding="utf-8") + + assert "class WorkflowRunHandlerResult:" in reconcile_text + for field in ["state_changed", "touched_items"]: + assert field in reconcile_text diff --git a/tests/integration/reviewer_bot/test_app_workflow_run_paths.py b/tests/integration/reviewer_bot/test_app_workflow_run_paths.py new file mode 100644 index 000000000..507214c9b --- /dev/null +++ b/tests/integration/reviewer_bot/test_app_workflow_run_paths.py @@ -0,0 +1,158 @@ +from pathlib import Path + +import pytest + +from scripts.reviewer_bot_lib import reconcile +from scripts.reviewer_bot_lib.config import LeaseContext +from tests.fixtures.app_harness import AppHarness +from tests.fixtures.reviewer_bot import make_state + +pytestmark = pytest.mark.integration + + +D4C_DELETION_MANIFEST = [] + + +def test_execute_run_cross_repo_review_stays_read_only(monkeypatch): + harness = AppHarness(monkeypatch) + harness.set_event(EVENT_NAME="pull_request_review", EVENT_ACTION="submitted", PR_IS_CROSS_REPOSITORY="true") + + acquire_called = {"value": False} + + def fail_if_called(): + acquire_called["value"] = True + raise AssertionError("acquire_state_issue_lease_lock should not be called") + + harness.stub_lock(acquire=fail_if_called) + harness.stub_load_state(lambda *, fail_on_unavailable=False: make_state()) + + harness.run_execute() + + assert acquire_called["value"] is False + +def test_execute_run_same_repo_review_stays_read_only(monkeypatch): + harness = AppHarness(monkeypatch) + harness.set_event(EVENT_NAME="pull_request_review", EVENT_ACTION="submitted") + + acquire_called = {"value": False} + + def fail_if_called(): + acquire_called["value"] = True + raise AssertionError("acquire_state_issue_lease_lock should not be called") + + harness.stub_lock(acquire=fail_if_called) + harness.stub_load_state(lambda *, fail_on_unavailable=False: make_state()) + harness.stub_pass_until(lambda state: (state, [])) + harness.stub_sync_members(lambda state: (state, [])) + + harness.run_execute() + + assert acquire_called["value"] is False + +@pytest.mark.parametrize( + ("workflow_run_event", "workflow_run_event_action"), + [ + ("issue_comment", "created"), + ("pull_request_review", "submitted"), + ("pull_request_review", "dismissed"), + ("pull_request_review_comment", "created"), + ], +) +def test_execute_run_supported_workflow_run_source_action_pairs_acquire_lock( + monkeypatch, workflow_run_event, workflow_run_event_action +): + harness = AppHarness(monkeypatch) + harness.set_workflow_run_name("Reviewer Bot PR Review Submitted Observer") + harness.set_event( + EVENT_NAME="workflow_run", + EVENT_ACTION="completed", + REVIEWER_BOT_WORKFLOW_KIND="reconcile", + WORKFLOW_RUN_TRIGGERING_CONCLUSION="success", + ) + + acquire_called = {"value": False} + + def fake_acquire(): + acquire_called["value"] = True + return LeaseContext( + lock_token="token", + lock_owner_run_id="run", + lock_owner_workflow="workflow", + lock_owner_job="job", + state_issue_url="https://example.com/issues/314", + lock_ref="refs/heads/reviewer-bot-state-lock", + lock_expires_at="2999-01-01T00:00:00+00:00", + ) + + harness.stub_lock(acquire=fake_acquire, release=lambda: True) + harness.stub_load_state(lambda *, fail_on_unavailable=False: make_state()) + harness.stub_pass_until(lambda state: (state, [])) + harness.stub_sync_members(lambda state: (state, [])) + monkeypatch.setattr( + reconcile, + "handle_workflow_run_event_result", + lambda bot, state: reconcile.WorkflowRunHandlerResult(False, []), + ) + + result = harness.run_execute() + + assert acquire_called["value"] is True + assert result.exit_code == 0 + + +def test_execute_run_unsupported_workflow_run_source_action_pair_stays_read_only(monkeypatch): + harness = AppHarness(monkeypatch) + harness.set_event( + EVENT_NAME="workflow_run", + EVENT_ACTION="completed", + REVIEWER_BOT_WORKFLOW_KIND="pull_request_review", + WORKFLOW_RUN_TRIGGERING_CONCLUSION="edited", + ) + + acquire_called = {"value": False} + + def fail_if_called(): + acquire_called["value"] = True + raise AssertionError("unsupported workflow_run source-action pairs must stay read-only") + + harness.stub_lock(acquire=fail_if_called) + harness.stub_load_state(lambda *, fail_on_unavailable=False: make_state()) + monkeypatch.setattr( + reconcile, + "handle_workflow_run_event_result", + lambda bot, state: pytest.fail("unsupported workflow_run source-action pairs must not reach reconcile"), + ) + + result = harness.run_execute() + + assert acquire_called["value"] is False + assert result.exit_code == 0 + assert result.state_changed is False + + +def test_d4a_phase_map_records_workflow_run_and_non_mutating_branches_explicitly(): + with open("tests/fixtures/equivalence/app/transaction_phase_map.json", encoding="utf-8") as handle: + phase_map = __import__("json").load(handle) + + assert phase_map["branch_to_phase"]["issues/pull_request_target/issue_comment/workflow_dispatch/schedule/workflow_run dispatch"] == "event handling" + assert phase_map["branch_to_phase"]["locks.release"] == "lock release" + + +def test_d4c_deletion_manifest_is_explicit_and_remaining_app_branches_stay_transactional(): + app_text = Path("scripts/reviewer_bot_lib/app.py").read_text(encoding="utf-8") + + assert D4C_DELETION_MANIFEST == [] + assert 'if event_name == "issues":' in app_text + assert 'elif event_name == "pull_request_target":' in app_text + assert 'elif event_name == "workflow_run":' in app_text + assert 'if state_changed or sync_changes or restored:' in app_text + assert 'if touched_items:' in app_text + assert 'if projection_failure is not None:' in app_text + + +def test_m1_typed_workflow_run_result_is_the_only_retained_entrypoint(): + reconcile_text = Path("scripts/reviewer_bot_lib/reconcile.py").read_text(encoding="utf-8") + + assert "def handle_workflow_run_event_result(" in reconcile_text + assert "def handle_workflow_run_event(bot: ReconcileWorkflowRuntimeContext, state: dict) -> bool:" not in reconcile_text + assert "return handle_workflow_run_event_result(bot, state).state_changed" not in reconcile_text diff --git a/tests/integration/reviewer_bot/test_cli_entrypoint.py b/tests/integration/reviewer_bot/test_cli_entrypoint.py new file mode 100644 index 000000000..cbe322903 --- /dev/null +++ b/tests/integration/reviewer_bot/test_cli_entrypoint.py @@ -0,0 +1,112 @@ +import pytest + +from scripts import reviewer_bot +from tests.fixtures.app_harness import AppHarness +from tests.fixtures.reviewer_bot import make_state + +pytestmark = pytest.mark.integration + + +def test_main_show_state_uses_direct_yaml_import(monkeypatch, capsys): + harness = AppHarness(monkeypatch) + harness.set_event( + EVENT_NAME="workflow_dispatch", + EVENT_ACTION="", + MANUAL_ACTION="show-state", + ) + harness.stub_load_state(lambda *, fail_on_unavailable=False: make_state()) + + run = harness.run_main() + + assert run.exit_code is None + output = capsys.readouterr().out + assert "Current state:" in output + assert "freshness_runtime_epoch" in output + + +def test_main_builds_event_context_for_preview_wrapper(monkeypatch): + harness = AppHarness(monkeypatch) + harness.set_event( + EVENT_NAME="workflow_dispatch", + EVENT_ACTION="", + MANUAL_ACTION="preview-reviewer-board", + ) + captured = harness.stub_execute_run( + reviewer_bot.ExecutionResult(exit_code=0, state_changed=False) + ) + + run = harness.run_main() + + assert run.exit_code is None + assert captured.context is not None + assert captured.context.event_name == "workflow_dispatch" + assert captured.context.event_action == "" + assert captured.context.manual_action == "preview-reviewer-board" + + +def test_main_builds_workflow_run_context_before_execution(monkeypatch): + harness = AppHarness(monkeypatch) + harness.set_workflow_run_name("Reviewer Bot PR Review Dismissed Observer") + harness.set_event( + EVENT_NAME="workflow_run", + EVENT_ACTION="completed", + REVIEWER_BOT_WORKFLOW_KIND="reconcile", + WORKFLOW_RUN_TRIGGERING_CONCLUSION="success", + ) + captured = harness.stub_execute_run( + reviewer_bot.ExecutionResult(exit_code=0, state_changed=False) + ) + + run = harness.run_main() + + assert run.exit_code is None + assert captured.context is not None + assert captured.context.event_name == "workflow_run" + assert captured.context.event_action == "completed" + assert captured.context.workflow_kind == "reconcile" + assert captured.context.workflow_run_triggering_conclusion == "success" + assert captured.context.workflow_artifact_contract == "artifact_required" + + +def test_main_exits_with_nonzero_execution_result(monkeypatch): + harness = AppHarness(monkeypatch) + harness.set_event( + EVENT_NAME="issue_comment", + EVENT_ACTION="created", + ) + captured = harness.stub_execute_run( + reviewer_bot.ExecutionResult(exit_code=1, state_changed=False) + ) + + run = harness.run_main() + + assert run.exit_code == 1 + assert captured.context is not None + assert captured.context.event_name == "issue_comment" + assert captured.context.event_action == "created" + + +def test_main_accepts_explicit_runtime_argument(monkeypatch): + harness = AppHarness(monkeypatch) + harness.set_event( + EVENT_NAME="issue_comment", + EVENT_ACTION="created", + ) + captured = harness.stub_execute_run( + reviewer_bot.ExecutionResult(exit_code=0, state_changed=False) + ) + + try: + reviewer_bot.main(harness.runtime) + except SystemExit as exc: # pragma: no cover - defensive + pytest.fail(f"unexpected SystemExit: {exc.code}") + + assert captured.context is not None + assert captured.context.event_name == "issue_comment" + assert captured.context.event_action == "created" + + +def test_app_harness_no_longer_requires_singleton_runtime_patch(monkeypatch): + AppHarness(monkeypatch) + + assert hasattr(reviewer_bot, "RUNTIME") is False diff --git a/tests/integration/reviewer_bot/test_comment_routing_integration.py b/tests/integration/reviewer_bot/test_comment_routing_integration.py new file mode 100644 index 000000000..528b955a7 --- /dev/null +++ b/tests/integration/reviewer_bot/test_comment_routing_integration.py @@ -0,0 +1,172 @@ +import pytest + +pytestmark = pytest.mark.integration + +from scripts.reviewer_bot_lib import comment_routing, reconcile, review_state +from scripts.reviewer_bot_lib.config import FLS_AUDIT_LABEL +from tests.fixtures.comment_routing_harness import CommentRoutingHarness +from tests.fixtures.reviewer_bot import make_state + + +def test_handle_non_pr_issue_comment_creates_pending_privileged_command(monkeypatch): + harness = CommentRoutingHarness(monkeypatch) + state = make_state() + entry = review_state.ensure_review_entry(state, 42, create=True) + assert entry is not None + entry["current_reviewer"] = "alice" + request = harness.request( + issue_number=42, + is_pull_request=False, + issue_author="dana", + issue_labels=(FLS_AUDIT_LABEL,), + comment_author="dana", + comment_body="@guidelines-bot /accept-no-fls-changes", + ) + effects = harness.capture_comment_side_effects() + harness.runtime.get_user_permission_status = lambda username, required_permission="triage": "granted" + + assert comment_routing.handle_comment_event(harness.runtime, state, request) is True + pending = state["active_reviews"]["42"]["sidecars"]["pending_privileged_commands"] + assert pending["issue_comment:100"]["command_name"] == "accept-no-fls-changes" + assert effects.comments == [ + ( + 42, + "✅ Recorded pending privileged command `accept-no-fls-changes` from trusted live validation. " + "Use the isolated privileged workflow to execute it from issue `#314` state.", + ) + ] + assert effects.reactions == [] + +def test_closed_non_pr_plain_text_comment_does_not_create_review_entry(monkeypatch): + harness = CommentRoutingHarness(monkeypatch) + state = make_state() + request = harness.request( + issue_number=42, + is_pull_request=False, + issue_state="closed", + issue_author="dana", + comment_author="dana", + comment_body="reviewer-bot validation: close comment", + ) + + assert comment_routing.handle_comment_event(harness.runtime, state, request) is False + assert state["active_reviews"] == {} + +def test_closed_non_pr_command_comment_does_not_create_pending_privileged_command(monkeypatch): + harness = CommentRoutingHarness(monkeypatch) + state = make_state() + request = harness.request( + issue_number=42, + is_pull_request=False, + issue_state="closed", + issue_author="dana", + comment_author="dana", + comment_body="@guidelines-bot /accept-no-fls-changes", + ) + effects = harness.capture_comment_side_effects() + harness.runtime.set_config_value("ISSUE_LABELS", f'["{FLS_AUDIT_LABEL}"]') + harness.runtime.check_user_permission = lambda username, required_permission="triage": True + + assert comment_routing.handle_comment_event(harness.runtime, state, request) is False + assert state["active_reviews"] == {} + assert effects.comments == [] + +def test_closed_non_pr_comment_removes_stale_review_entry(monkeypatch): + harness = CommentRoutingHarness(monkeypatch) + state = make_state() + review = review_state.ensure_review_entry(state, 42, create=True) + assert review is not None + review["current_reviewer"] = "alice" + request = harness.request( + issue_number=42, + is_pull_request=False, + issue_state="closed", + issue_author="dana", + comment_author="dana", + comment_body="reviewer-bot validation: close comment", + ) + + assert comment_routing.handle_comment_event(harness.runtime, state, request) is True + assert "42" not in state["active_reviews"] + +def test_closed_non_pr_comment_without_entry_returns_false(monkeypatch): + harness = CommentRoutingHarness(monkeypatch) + state = make_state() + request = harness.request( + issue_number=42, + is_pull_request=False, + issue_state="closed", + issue_author="dana", + comment_author="dana", + comment_body="reviewer-bot validation: close comment", + ) + + assert comment_routing.handle_comment_event(harness.runtime, state, request) is False + assert state["active_reviews"] == {} + +def test_open_non_pr_plain_text_comment_still_updates_freshness(monkeypatch): + harness = CommentRoutingHarness(monkeypatch) + state = make_state() + review = review_state.ensure_review_entry(state, 42, create=True) + assert review is not None + review["current_reviewer"] = "alice" + request = harness.request( + issue_number=42, + is_pull_request=False, + issue_state="open", + issue_author="dana", + comment_author="dana", + comment_body="reviewer-bot validation: contributor plain text comment", + ) + + assert harness.handle_comment_event(state, request=request) is True + accepted = state["active_reviews"]["42"]["contributor_comment"]["accepted"] + assert accepted["semantic_key"] == "issue_comment:100" + +def test_observer_noop_payload_is_safe_noop(tmp_path, monkeypatch): + state = make_state() + harness = CommentRoutingHarness(monkeypatch) + review_state.ensure_review_entry(state, 42, create=True) + harness.runtime.stub_deferred_payload( + { + "schema_version": 1, + "kind": "observer_noop", + "reason": "ignored_non_human_automation", + "source_workflow_name": "Reviewer Bot PR Comment Observer", + "source_workflow_file": ".github/workflows/reviewer-bot-pr-comment-observer.yml", + "source_run_id": 777, + "source_run_attempt": 1, + "source_event_name": "issue_comment", + "source_event_action": "created", + "source_event_key": "issue_comment:111", + "pr_number": 42, + } + ) + harness.config.set("WORKFLOW_RUN_TRIGGERING_NAME", "Reviewer Bot PR Comment Observer") + harness.config.set("WORKFLOW_RUN_TRIGGERING_ID", "777") + harness.config.set("WORKFLOW_RUN_TRIGGERING_ATTEMPT", "1") + harness.config.set("WORKFLOW_RUN_TRIGGERING_CONCLUSION", "success") + + assert reconcile.handle_workflow_run_event_result(harness.runtime, state).state_changed is False + assert state["active_reviews"]["42"]["sidecars"]["deferred_gaps"] == {} + + +def test_cross_repo_pr_comment_route_remains_deferred_not_direct(monkeypatch): + harness = CommentRoutingHarness(monkeypatch) + state = make_state() + request = harness.request( + issue_number=42, + is_pull_request=True, + issue_author="dana", + comment_author="alice", + comment_body="hello", + ) + trust_context = harness.trust_context( + route_outcome=comment_routing.comment_routing_policy.PrCommentRouterOutcome.DEFERRED_RECONCILE, + github_repository="rustfoundation/safety-critical-rust-coding-guidelines", + pr_head_full_name="fork/example", + pr_author="dana", + ) + + with pytest.raises(RuntimeError, match="Deferred PR comment events must not mutate directly"): + comment_routing.handle_comment_event(harness.runtime, state, request, trust_context) diff --git a/tests/integration/reviewer_bot/test_lifecycle_integration.py b/tests/integration/reviewer_bot/test_lifecycle_integration.py new file mode 100644 index 000000000..c355d2daa --- /dev/null +++ b/tests/integration/reviewer_bot/test_lifecycle_integration.py @@ -0,0 +1,56 @@ +import pytest + +from scripts.reviewer_bot_lib import review_state +from tests.fixtures.reconcile_harness import ReconcileHarness, review_submitted_payload +from tests.fixtures.reviewer_bot import make_state + +pytestmark = pytest.mark.integration + + +def test_workflow_run_review_submission_clears_warning_and_transition_notice_markers(monkeypatch): + state = make_state() + review = review_state.ensure_review_entry(state, 42, create=True) + assert review is not None + review["current_reviewer"] = "alice" + review["active_cycle_started_at"] = "2026-03-17T09:00:00Z" + review["transition_warning_sent"] = "2026-03-18T00:00:00Z" + review["transition_notice_sent_at"] = "2026-03-25T00:00:00Z" + harness = ReconcileHarness( + monkeypatch, + review_submitted_payload( + pr_number=42, + review_id=11, + source_event_key="pull_request_review:11", + source_submitted_at="2026-03-17T10:00:00Z", + source_review_state="COMMENTED", + source_commit_id="head-1", + actor_login="alice", + source_run_id=500, + source_run_attempt=2, + ), + ) + harness.add_pull_request(pr_number=42, head_sha="head-2", author="dana") + harness.add_review( + pr_number=42, + review_id=11, + submitted_at="2026-03-17T10:00:00Z", + state="COMMENTED", + commit_id="head-1", + author="alice", + ) + harness.add_reviews_page( + pr_number=42, + reviews=[ + { + "id": 11, + "submitted_at": "2026-03-17T10:00:00Z", + "state": "COMMENTED", + "commit_id": "head-1", + "user": {"login": "alice"}, + } + ], + ) + + assert harness.run(state) is True + assert review["transition_warning_sent"] is None + assert review["transition_notice_sent_at"] is None diff --git a/tests/integration/reviewer_bot/test_reconcile_privileged_command.py b/tests/integration/reviewer_bot/test_reconcile_privileged_command.py new file mode 100644 index 000000000..ffe158187 --- /dev/null +++ b/tests/integration/reviewer_bot/test_reconcile_privileged_command.py @@ -0,0 +1,173 @@ +import os + +import pytest + +pytestmark = pytest.mark.integration + +from scripts.reviewer_bot_lib import automation, maintenance, review_state +from scripts.reviewer_bot_lib.config import FLS_AUDIT_LABEL +from tests.fixtures.commands_harness import CommandHarness +from tests.fixtures.reviewer_bot import make_state + + +def test_execute_pending_privileged_command_revalidates_live_state(monkeypatch): + harness = CommandHarness(monkeypatch) + state = make_state() + review = review_state.ensure_review_entry(state, 42, create=True) + assert review is not None + review["sidecars"]["pending_privileged_commands"]["issue_comment:100"] = { + "source_event_key": "issue_comment:100", + "command_name": "accept-no-fls-changes", + "issue_number": 42, + "actor": "alice", + "authorization_required_permission": "triage", + "authorization_authorized": True, + "target_kind": "issue", + "target_number": 42, + "target_labels_snapshot": [FLS_AUDIT_LABEL], + "status": "pending", + "created_at": "2026-03-17T10:00:00Z", + } + harness.set_manual_dispatch(source_event_key="issue_comment:100") + harness.runtime.get_issue_or_pr_snapshot = lambda issue_number: {"number": issue_number, "labels": [{"name": FLS_AUDIT_LABEL}]} + harness.runtime.get_user_permission_status = lambda username, required_permission="triage": "granted" + monkeypatch.setattr( + automation, + "handle_accept_no_fls_changes_command", + lambda bot, issue_number, actor, execution_plan=None, request=None: automation.privileged_command_policy.CompletePrivilegedExecution( + status="executed", + result_code="opened_pull_request", + result_message="ok", + ), + ) + + assert maintenance.handle_manual_dispatch(harness.runtime, state) is True + assert review["sidecars"]["pending_privileged_commands"]["issue_comment:100"]["status"] == "executed" + +def test_execute_pending_privileged_command_passes_revalidated_typed_request(monkeypatch): + harness = CommandHarness(monkeypatch) + state = make_state() + review = review_state.ensure_review_entry(state, 42, create=True) + assert review is not None + review["sidecars"]["pending_privileged_commands"]["issue_comment:100"] = { + "source_event_key": "issue_comment:100", + "command_name": "accept-no-fls-changes", + "issue_number": 42, + "actor": "alice", + "authorization_required_permission": "triage", + "authorization_authorized": True, + "target_kind": "issue", + "target_number": 42, + "target_labels_snapshot": [FLS_AUDIT_LABEL], + "status": "pending", + "created_at": "2026-03-17T10:00:00Z", + } + harness.set_manual_dispatch(source_event_key="issue_comment:100") + monkeypatch.setenv("ISSUE_LABELS", '["stale-label"]') + harness.runtime.get_issue_or_pr_snapshot = lambda issue_number: {"number": issue_number, "labels": [{"name": FLS_AUDIT_LABEL}]} + harness.runtime.get_user_permission_status = lambda username, required_permission="triage": "granted" + + observed = {} + + def fake_handle(issue_number, actor, execution_plan=None): + observed["issue_number"] = issue_number + observed["actor"] = actor + observed["execution_plan"] = execution_plan + return automation.privileged_command_policy.CompletePrivilegedExecution( + status="executed", + result_code="opened_pull_request", + result_message="ok", + ) + + monkeypatch.setattr( + automation, + "handle_accept_no_fls_changes_command", + lambda bot, issue_number, actor, execution_plan=None, request=None: fake_handle(issue_number, actor, execution_plan), + ) + + assert maintenance.handle_manual_dispatch(harness.runtime, state) is True + execution_plan = observed["execution_plan"] + assert observed["issue_number"] == 42 + assert observed["actor"] == "alice" + assert execution_plan is not None + assert execution_plan.record.issue_number == 42 + assert execution_plan.record.actor == "alice" + assert execution_plan.record.command_name == "accept-no-fls-changes" + assert execution_plan.record.target_labels_snapshot == (FLS_AUDIT_LABEL,) + assert os.environ["ISSUE_LABELS"] == '["stale-label"]' + assert review["sidecars"]["pending_privileged_commands"]["issue_comment:100"]["status"] == "executed" + + +def test_execute_pending_privileged_command_does_not_leak_issue_labels_env(monkeypatch): + harness = CommandHarness(monkeypatch) + state = make_state() + review = review_state.ensure_review_entry(state, 42, create=True) + assert review is not None + review["sidecars"]["pending_privileged_commands"]["issue_comment:100"] = { + "source_event_key": "issue_comment:100", + "command_name": "accept-no-fls-changes", + "issue_number": 42, + "actor": "alice", + "authorization_required_permission": "triage", + "authorization_authorized": True, + "target_kind": "issue", + "target_number": 42, + "target_labels_snapshot": [FLS_AUDIT_LABEL], + "status": "pending", + "created_at": "2026-03-17T10:00:00Z", + } + harness.set_manual_dispatch(source_event_key="issue_comment:100") + monkeypatch.delenv("ISSUE_LABELS", raising=False) + harness.runtime.get_issue_or_pr_snapshot = lambda issue_number: {"number": issue_number, "labels": [{"name": FLS_AUDIT_LABEL}]} + harness.runtime.get_user_permission_status = lambda username, required_permission="triage": "granted" + monkeypatch.setattr( + automation, + "handle_accept_no_fls_changes_command", + lambda bot, issue_number, actor, execution_plan=None, request=None: automation.privileged_command_policy.CompletePrivilegedExecution( + status="executed", + result_code="opened_pull_request", + result_message="ok", + ), + ) + + assert maintenance.handle_manual_dispatch(harness.runtime, state) is True + assert "ISSUE_LABELS" not in os.environ + +def test_execute_pending_privileged_command_fails_closed_without_live_fls_audit_label(monkeypatch): + harness = CommandHarness(monkeypatch) + state = make_state() + review = review_state.ensure_review_entry(state, 42, create=True) + assert review is not None + review["sidecars"]["pending_privileged_commands"]["issue_comment:100"] = { + "source_event_key": "issue_comment:100", + "command_name": "accept-no-fls-changes", + "issue_number": 42, + "actor": "alice", + "authorization_required_permission": "triage", + "authorization_authorized": True, + "target_kind": "issue", + "target_number": 42, + "target_labels_snapshot": [FLS_AUDIT_LABEL], + "status": "pending", + "created_at": "2026-03-17T10:00:00Z", + } + harness.set_manual_dispatch(source_event_key="issue_comment:100") + harness.runtime.get_issue_or_pr_snapshot = lambda issue_number: {"number": issue_number, "labels": [{"name": "status: awaiting reviewer response"}]} + harness.runtime.get_user_permission_status = lambda username, required_permission="triage": "granted" + called = {"handle": 0} + monkeypatch.setattr( + automation, + "handle_accept_no_fls_changes_command", + lambda bot, issue_number, actor, execution_plan=None, request=None: called.__setitem__("handle", called["handle"] + 1) + or automation.privileged_command_policy.CompletePrivilegedExecution( + status="executed", + result_code="opened_pull_request", + result_message="ok", + ), + ) + + assert maintenance.handle_manual_dispatch(harness.runtime, state) is True + assert called["handle"] == 0 + pending = review["sidecars"]["pending_privileged_commands"]["issue_comment:100"] + assert pending["status"] == "failed_closed" + assert pending["result_code"] == "missing_fls_audit_label" diff --git a/tests/integration/reviewer_bot/test_reconcile_workflow_run.py b/tests/integration/reviewer_bot/test_reconcile_workflow_run.py new file mode 100644 index 000000000..c4511a98b --- /dev/null +++ b/tests/integration/reviewer_bot/test_reconcile_workflow_run.py @@ -0,0 +1,681 @@ +import pytest + +from scripts.reviewer_bot_lib import reconcile +from tests.fixtures.reconcile_harness import ( + ReconcileHarness, + issue_comment_payload, + review_comment_payload, + review_submitted_payload, +) +from tests.fixtures.reviewer_bot import ( + accept_reviewer_review, + make_state, + make_tracked_review_state, + review_payload, +) + +pytestmark = pytest.mark.integration + + +def _deferred_gaps(review: dict) -> dict: + return review["sidecars"]["deferred_gaps"] + + +def _reconciled_source_events(review: dict) -> dict: + return review["sidecars"]["reconciled_source_events"] + + +C4C_DELETION_MANIFEST = [ + "inline comment classification drift decision text in reconcile.py", + "inline non-command text drift decision text in reconcile.py", + "inline command-count replay decision text in reconcile.py", +] + + +def test_handle_workflow_run_event_returns_true_for_submitted_review_bookkeeping_only_mutations( + monkeypatch, +): + state = make_state() + review = make_tracked_review_state(state, 42, reviewer="bob") + _deferred_gaps(review)["pull_request_review:11"] = {"reason": "artifact_missing"} + harness = ReconcileHarness( + monkeypatch, + review_submitted_payload( + pr_number=42, + review_id=11, + source_event_key="pull_request_review:11", + source_submitted_at="2026-03-17T10:00:00Z", + source_review_state="COMMENTED", + source_commit_id="head-1", + actor_login="alice", + source_run_id=500, + source_run_attempt=2, + ), + ) + harness.stub_review_rebuild(changed=False) + harness.stub_head_repair(changed=False) + harness.add_pull_request(pr_number=42, head_sha="head-1", author="dana") + harness.add_review( + pr_number=42, + review_id=11, + submitted_at="2026-03-17T10:00:00Z", + state="COMMENTED", + commit_id="head-1", + author="alice", + ) + + assert harness.run(state) is True + assert "pull_request_review:11" in _reconciled_source_events(review) + assert "pull_request_review:11" not in _deferred_gaps(review) + + +def test_handle_workflow_run_event_persists_fail_closed_diagnostic_without_raising(monkeypatch): + state = make_state() + review = make_tracked_review_state(state, 42, reviewer="alice") + harness = ReconcileHarness( + monkeypatch, + review_submitted_payload( + pr_number=42, + review_id=12, + source_event_key="pull_request_review:12", + source_submitted_at="2026-03-17T10:00:00Z", + source_review_state="COMMENTED", + source_commit_id="head-1", + actor_login="alice", + source_run_id=501, + source_run_attempt=1, + ), + ) + harness.add_pull_request(pr_number=42, head_sha="head-1", author="dana") + harness.add_request_failure( + endpoint="pulls/42/reviews/12", + status_code=502, + payload={"message": "bad gateway"}, + failure_kind="server_error", + ) + + assert harness.run(state) is True + gap = _deferred_gaps(review)["pull_request_review:12"] + assert gap["reason"] == "reconcile_failed_closed" + assert gap["failure_kind"] == "server_error" + + +def test_handle_workflow_run_event_treats_observer_noop_payload_as_no_mutation(monkeypatch): + state = make_state(epoch="freshness_v15") + harness = ReconcileHarness( + monkeypatch, + { + "schema_version": 1, + "kind": "observer_noop", + "reason": "trusted_direct_same_repo_human_comment", + "source_workflow_name": "Reviewer Bot PR Comment Observer", + "source_workflow_file": ".github/workflows/reviewer-bot-pr-comment-observer.yml", + "source_run_id": 900, + "source_run_attempt": 1, + "source_event_name": "issue_comment", + "source_event_action": "created", + "source_event_key": "issue_comment:210", + "pr_number": 42, + }, + ) + + result = reconcile.handle_workflow_run_event_result(harness.runtime, state) + + assert result.state_changed is False + assert result.touched_items == [42] + assert harness.runtime.drain_touched_items() == [] + assert state["active_reviews"]["42"]["sidecars"]["reconciled_source_events"] == {} + + +def test_deferred_comment_reconcile_returns_true_for_bookkeeping_only_mutations(monkeypatch): + state = make_state() + review = make_tracked_review_state(state, 42, reviewer="alice") + _deferred_gaps(review)["issue_comment:210"] = {"reason": "artifact_missing"} + live_body = "@guidelines-bot /queue" + harness = ReconcileHarness( + monkeypatch, + issue_comment_payload( + pr_number=42, + comment_id=210, + source_event_key="issue_comment:210", + body=live_body, + comment_class="command_only", + has_non_command_text=False, + source_created_at="2026-03-17T10:00:00Z", + actor_login="bob", + source_run_id=610, + source_run_attempt=1, + ), + ) + harness.add_pull_request(pr_number=42, author="dana", labels=["coding guideline"]) + harness.add_issue_comment( + comment_id=210, + body=live_body, + author="bob", + author_type="User", + author_association="MEMBER", + ) + harness.stub_apply_comment_command(False) + + assert harness.run(state) is True + assert "issue_comment:210" in _reconciled_source_events(review) + assert "issue_comment:210" not in _deferred_gaps(review) + + +def test_deferred_comment_missing_live_object_preserves_source_time_freshness(monkeypatch): + state = make_state() + make_tracked_review_state(state, 42, reviewer="alice") + harness = ReconcileHarness( + monkeypatch, + issue_comment_payload( + pr_number=42, + comment_id=99, + source_event_key="issue_comment:99", + body="stale body", + comment_class="plain_text", + has_non_command_text=True, + source_created_at="2026-03-17T10:00:00Z", + actor_login="alice", + source_run_id=501, + source_run_attempt=1, + ), + ) + harness.add_pull_request(pr_number=42, author="dana") + harness.add_request_failure( + endpoint="issues/comments/99", + status_code=404, + payload={"message": "missing"}, + failure_kind="not_found", + ) + + assert harness.run(state) is True + assert state["active_reviews"]["42"]["reviewer_comment"]["accepted"]["semantic_key"] == "issue_comment:99" + assert state["active_reviews"]["42"]["sidecars"]["deferred_gaps"]["issue_comment:99"]["reason"] == "reconcile_failed_closed" + + +def test_handle_workflow_run_event_rebuilds_completion_from_live_review_commit_id(monkeypatch): + state = make_state() + review = make_tracked_review_state( + state, + 42, + reviewer="alice", + active_cycle_started_at="2026-03-17T09:00:00Z", + ) + harness = ReconcileHarness( + monkeypatch, + review_submitted_payload( + pr_number=42, + review_id=11, + source_event_key="pull_request_review:11", + source_submitted_at="2026-03-17T10:00:00Z", + source_review_state="APPROVED", + source_commit_id="head-1", + actor_login="alice", + source_run_id=500, + source_run_attempt=2, + ), + ) + harness.add_pull_request(pr_number=42, head_sha="head-2", author="dana") + harness.add_review( + pr_number=42, + review_id=11, + submitted_at="2026-03-17T10:00:00Z", + state="APPROVED", + commit_id="head-1", + author="alice", + ) + harness.add_reviews_page( + pr_number=42, + reviews=[ + review_payload( + 11, + state="APPROVED", + submitted_at="2026-03-17T10:00:00Z", + commit_id="head-1", + author="alice", + ) + ], + ) + harness.runtime.get_user_permission_status = lambda username, required_permission="push": "granted" + + assert harness.run(state) is True + assert review["current_cycle_completion"]["completed"] is False + + +def test_handle_workflow_run_event_refreshes_stale_stored_reviewer_review_to_current_head_preferred_review( + monkeypatch, +): + state = make_state() + review = make_tracked_review_state( + state, + 42, + reviewer="alice", + active_cycle_started_at="2026-03-17T09:00:00Z", + ) + accept_reviewer_review( + review, + semantic_key="pull_request_review:99", + timestamp="2026-03-17T11:00:00Z", + actor="alice", + reviewed_head_sha="head-0", + source_precedence=1, + ) + harness = ReconcileHarness( + monkeypatch, + review_submitted_payload( + pr_number=42, + review_id=99, + source_event_key="pull_request_review:99", + source_submitted_at="2026-03-17T11:00:00Z", + source_review_state="COMMENTED", + source_commit_id="head-0", + actor_login="alice", + source_run_id=500, + source_run_attempt=2, + ), + ) + harness.add_pull_request(pr_number=42, head_sha="head-1", author="dana") + harness.add_review( + pr_number=42, + review_id=99, + submitted_at="2026-03-17T11:00:00Z", + state="COMMENTED", + commit_id="head-0", + author="alice", + ) + harness.add_reviews_page( + pr_number=42, + reviews=[ + review_payload( + 10, + state="COMMENTED", + submitted_at="2026-03-17T10:00:00Z", + commit_id="head-1", + author="alice", + ), + review_payload( + 99, + state="COMMENTED", + submitted_at="2026-03-17T11:00:00Z", + commit_id="head-0", + author="alice", + ), + ], + ) + harness.runtime.get_user_permission_status = lambda username, required_permission="push": "granted" + + assert harness.run(state) is True + accepted = review["reviewer_review"]["accepted"] + assert accepted["semantic_key"] == "pull_request_review:10" + assert accepted["reviewed_head_sha"] == "head-1" + + +def test_deferred_review_comment_reconcile_records_contributor_freshness(monkeypatch): + state = make_state() + make_tracked_review_state(state, 42, reviewer="alice") + live_body = "author reply in review thread" + harness = ReconcileHarness( + monkeypatch, + review_comment_payload( + pr_number=42, + comment_id=301, + source_event_key="pull_request_review_comment:301", + body=live_body, + comment_class="plain_text", + has_non_command_text=True, + source_created_at="2026-03-17T10:00:00Z", + actor_login="dana", + actor_id=5, + actor_class="repo_user_principal", + pull_request_review_id=10, + in_reply_to_id=200, + source_run_id=701, + source_run_attempt=1, + ), + ) + harness.add_pull_request(pr_number=42, author="dana") + harness.add_review_comment( + comment_id=301, + body=live_body, + author="dana", + author_type="User", + author_association="CONTRIBUTOR", + ) + harness.runtime.get_user_permission_status = lambda username, required_permission="push": "granted" + + assert harness.run(state) is True + accepted = state["active_reviews"]["42"]["contributor_comment"]["accepted"] + assert accepted is not None + assert accepted["semantic_key"] == "pull_request_review_comment:301" + + +def test_deferred_review_comment_reconcile_records_reviewer_freshness(monkeypatch): + state = make_state() + review = make_tracked_review_state(state, 42, reviewer="alice") + live_body = "reviewer reply in thread" + harness = ReconcileHarness( + monkeypatch, + review_comment_payload( + pr_number=42, + comment_id=302, + source_event_key="pull_request_review_comment:302", + body=live_body, + comment_class="plain_text", + has_non_command_text=True, + source_created_at="2026-03-17T11:00:00Z", + actor_login="alice", + actor_id=6, + actor_class="repo_user_principal", + pull_request_review_id=10, + in_reply_to_id=200, + source_run_id=702, + source_run_attempt=1, + ), + ) + harness.add_pull_request(pr_number=42, author="dana") + harness.add_review_comment( + comment_id=302, + body=live_body, + author="alice", + author_type="User", + author_association="MEMBER", + ) + harness.runtime.get_user_permission_status = lambda username, required_permission="push": "granted" + + assert harness.run(state) is True + assert review["reviewer_comment"]["accepted"]["semantic_key"] == "pull_request_review_comment:302" + + +def test_deferred_review_comment_missing_live_object_preserves_source_time_freshness(monkeypatch): + state = make_state() + review = make_tracked_review_state(state, 42, reviewer="alice") + harness = ReconcileHarness( + monkeypatch, + review_comment_payload( + pr_number=42, + comment_id=303, + source_event_key="pull_request_review_comment:303", + body="stale body", + comment_class="plain_text", + has_non_command_text=True, + source_created_at="2026-03-17T10:00:00Z", + actor_login="alice", + actor_id=6, + actor_class="repo_user_principal", + pull_request_review_id=10, + in_reply_to_id=200, + source_run_id=703, + source_run_attempt=1, + ), + ) + harness.add_pull_request(pr_number=42, author="dana") + harness.add_request_failure( + endpoint="pulls/comments/303", + status_code=404, + payload={"message": "missing"}, + failure_kind="not_found", + ) + harness.runtime.get_user_permission_status = lambda username, required_permission="push": "granted" + + assert harness.run(state) is True + assert review["reviewer_comment"]["accepted"]["semantic_key"] == "pull_request_review_comment:303" + assert _deferred_gaps(review)["pull_request_review_comment:303"]["reason"] == "reconcile_failed_closed" + + +def test_deferred_comment_reconcile_fails_closed_when_command_replay_is_ambiguous(monkeypatch): + state = make_state() + make_tracked_review_state(state, 42, reviewer="alice") + live_body = "@guidelines-bot /claim" + harness = ReconcileHarness( + monkeypatch, + issue_comment_payload( + pr_number=42, + comment_id=201, + source_event_key="issue_comment:201", + body=live_body, + comment_class="command_only", + has_non_command_text=False, + source_created_at="2026-03-17T10:00:00Z", + actor_login="bob", + source_run_id=603, + source_run_attempt=1, + ), + ) + harness.add_pull_request(pr_number=42, author="dana") + harness.add_issue_comment( + comment_id=201, + body=live_body, + author="bob", + author_type="User", + author_association="MEMBER", + ) + harness.stub_comment_classification( + { + "comment_class": "command_only", + "has_non_command_text": False, + "command_count": 2, + "command": None, + "args": [], + "normalized_body": live_body, + } + ) + command_calls = [] + + def record_command_call(*args, **kwargs): + command_calls.append("called") + return True + + harness.stub_apply_comment_command(func=record_command_call) + + assert harness.run(state) is True + assert command_calls == [] + assert state["active_reviews"]["42"]["sidecars"]["deferred_gaps"]["issue_comment:201"]["reason"] == "reconcile_failed_closed" + assert "issue_comment:201" not in state["active_reviews"]["42"]["sidecars"]["reconciled_source_events"] + + +def test_deferred_comment_reconcile_hydrates_pr_author_context_for_contributor_freshness(monkeypatch): + state = make_state() + make_tracked_review_state(state, 42, reviewer="alice") + live_body = "reviewer-bot validation: contributor plain text comment" + harness = ReconcileHarness( + monkeypatch, + issue_comment_payload( + pr_number=42, + comment_id=199, + source_event_key="issue_comment:199", + body=live_body, + comment_class="plain_text", + has_non_command_text=True, + source_created_at="2026-03-17T10:00:00Z", + actor_login="dana", + source_run_id=601, + source_run_attempt=1, + ), + ) + harness.add_pull_request(pr_number=42, author="dana", labels=["coding guideline"]) + harness.add_issue_comment( + comment_id=199, + body=live_body, + author="dana", + author_type="User", + author_association="CONTRIBUTOR", + ) + harness.runtime.get_user_permission_status = lambda username, required_permission="push": "granted" + + assert harness.run(state) is True + assert state["active_reviews"]["42"]["contributor_comment"]["accepted"]["semantic_key"] == "issue_comment:199" + assert state["active_reviews"]["42"]["reviewer_comment"]["accepted"] is None + + +def test_deferred_comment_reconcile_uses_pr_assignment_semantics_for_claim(monkeypatch): + state = make_state() + state["queue"] = [{"github": "bob", "name": "Bob"}] + make_tracked_review_state(state, 42, reviewer="alice") + live_body = "@guidelines-bot /claim" + harness = ReconcileHarness( + monkeypatch, + issue_comment_payload( + pr_number=42, + comment_id=200, + source_event_key="issue_comment:200", + body=live_body, + comment_class="command_only", + has_non_command_text=False, + source_created_at="2026-03-17T10:00:00Z", + actor_login="bob", + source_run_id=602, + source_run_attempt=1, + ), + ) + harness.add_pull_request( + pr_number=42, + author="dana", + labels=["coding guideline"], + requested_reviewers=["alice"], + ) + harness.add_issue_comment( + comment_id=200, + body=live_body, + author="bob", + author_type="User", + author_association="MEMBER", + ) + harness.runtime.get_user_permission_status = lambda username, required_permission="push": "granted" + claim_contexts = [] + + def apply_claim_command(bot, state_obj, request, classified, classify_issue_comment_actor=None): + claim_contexts.append( + { + "issue_number": request.issue_number, + "username": request.comment_author, + "is_pull_request": request.is_pull_request, + "issue_author": request.issue_author, + } + ) + state_obj["active_reviews"][str(request.issue_number)]["current_reviewer"] = request.comment_author + return True + + harness.stub_apply_comment_command(func=apply_claim_command) + harness.runtime.add_reaction = lambda *args, **kwargs: True + + assert harness.run(state) is True + assert claim_contexts == [ + { + "issue_number": 42, + "username": "bob", + "is_pull_request": True, + "issue_author": "dana", + } + ] + assert state["active_reviews"]["42"]["current_reviewer"] == "bob" + + +def test_deferred_comment_reconcile_records_failure_kind_when_live_comment_unavailable(monkeypatch): + state = make_state() + make_tracked_review_state(state, 42, reviewer="alice") + live_body = "reviewer-bot validation: contributor plain text comment" + harness = ReconcileHarness( + monkeypatch, + issue_comment_payload( + pr_number=42, + comment_id=205, + source_event_key="issue_comment:205", + body=live_body, + comment_class="plain_text", + has_non_command_text=True, + source_created_at="2026-03-17T10:00:00Z", + actor_login="dana", + source_run_id=603, + source_run_attempt=1, + ), + ) + harness.add_pull_request(pr_number=42, author="dana") + harness.add_request_failure( + endpoint="issues/comments/205", + status_code=502, + payload={"message": "bad gateway"}, + failure_kind="server_error", + ) + + assert harness.run(state) is True + gap = state["active_reviews"]["42"]["sidecars"]["deferred_gaps"]["issue_comment:205"] + assert gap["reason"] == "reconcile_failed_closed" + assert gap["failure_kind"] == "server_error" + + +def test_deferred_comment_reconcile_fails_closed_when_comment_classification_drifts(monkeypatch): + state = make_state() + make_tracked_review_state(state, 42, reviewer="alice") + live_body = "reviewer-bot validation: contributor plain text comment" + harness = ReconcileHarness( + monkeypatch, + issue_comment_payload( + pr_number=42, + comment_id=202, + source_event_key="issue_comment:202", + body=live_body, + comment_class="plain_text", + has_non_command_text=True, + source_created_at="2026-03-17T10:00:00Z", + actor_login="dana", + source_run_id=604, + source_run_attempt=1, + ), + ) + harness.add_pull_request(pr_number=42, author="dana") + harness.add_issue_comment( + comment_id=202, + body=live_body, + author="dana", + author_type="User", + author_association="CONTRIBUTOR", + ) + harness.stub_comment_classification( + { + "comment_class": "command_plus_text", + "has_non_command_text": True, + "command_count": 1, + "command": "claim", + "args": [], + "normalized_body": live_body, + } + ) + harness.runtime.add_reaction = lambda *args, **kwargs: True + harness.runtime.post_comment = lambda *args, **kwargs: True + + assert harness.run(state) is True + assert state["active_reviews"]["42"]["contributor_comment"]["accepted"]["semantic_key"] == "issue_comment:202" + assert state["active_reviews"]["42"]["sidecars"]["deferred_gaps"]["issue_comment:202"]["reason"] == "reconcile_failed_closed" + assert state["active_reviews"]["42"]["sidecars"]["reconciled_source_events"] == {} + + +def test_c4c_reconcile_deletion_manifest_and_adapter_cleanup_are_explicit(): + with open("scripts/reviewer_bot_lib/reconcile.py", encoding="utf-8") as handle: + module_text = handle.read() + + assert C4C_DELETION_MANIFEST == [ + "inline comment classification drift decision text in reconcile.py", + "inline non-command text drift decision text in reconcile.py", + "inline command-count replay decision text in reconcile.py", + ] + assert "classification changed from" not in module_text + assert "non-command text classification drifted" not in module_text + assert "no longer resolves to exactly one command" not in module_text + assert "reconcile_replay_policy.decide_comment_replay(" in module_text + assert "reconcile_replay_policy.decide_review_submitted_replay(" in module_text + assert "reconcile_replay_policy.decide_review_dismissed_replay(" in module_text + + +def test_d2_reconcile_replay_path_stays_decode_read_apply_orchestration_only(): + with open("scripts/reviewer_bot_lib/reconcile.py", encoding="utf-8") as handle: + module_text = handle.read() + + assert "build_deferred_comment_replay_context(" in module_text + assert "_read_live_comment_replay_context(" in module_text + assert "process_comment_event(" in module_text + assert "record_conversation_freshness(" in module_text + assert "_mark_reconciled_source_event(" in module_text + assert "_clear_source_event_key(" in module_text + assert "reconcile_replay_policy.decide_comment_replay(" in module_text + assert "reconcile_replay_policy.decide_review_submitted_replay(" in module_text + assert "reconcile_replay_policy.decide_review_dismissed_replay(" in module_text diff --git a/tests/unit/reviewer_bot/test_app_event_intent.py b/tests/unit/reviewer_bot/test_app_event_intent.py new file mode 100644 index 000000000..56b26a705 --- /dev/null +++ b/tests/unit/reviewer_bot/test_app_event_intent.py @@ -0,0 +1,109 @@ +import json +from pathlib import Path + +import pytest + +from scripts.reviewer_bot_lib import app +from tests.fixtures.fake_runtime import FakeReviewerBotRuntime +from tests.fixtures.reviewer_bot_env import set_workflow_run_event_payload + + +def _load_phase_map() -> dict: + return json.loads( + Path("tests/fixtures/equivalence/app/transaction_phase_map.json").read_text( + encoding="utf-8" + ) + ) + + +def test_classify_event_intent_cross_repo_review_is_read_only(monkeypatch): + runtime = FakeReviewerBotRuntime(monkeypatch) + runtime.set_config_value("PR_IS_CROSS_REPOSITORY", "true") + intent = app.classify_event_intent(runtime, "pull_request_review", "submitted") + assert intent == runtime.EVENT_INTENT_NON_MUTATING_READONLY + + +def test_classify_event_intent_preview_reviewer_board_is_non_mutating(monkeypatch): + runtime = FakeReviewerBotRuntime(monkeypatch) + runtime.set_config_value("MANUAL_ACTION", "preview-reviewer-board") + intent = app.classify_event_intent(runtime, "workflow_dispatch", "") + assert intent == runtime.EVENT_INTENT_NON_MUTATING_READONLY + + +def test_classify_event_intent_same_repo_review_is_read_only(monkeypatch): + runtime = FakeReviewerBotRuntime(monkeypatch) + intent = app.classify_event_intent(runtime, "pull_request_review", "submitted") + assert intent == runtime.EVENT_INTENT_NON_MUTATING_READONLY + + +def test_classify_event_intent_same_repo_dismissed_review_is_read_only(monkeypatch): + runtime = FakeReviewerBotRuntime(monkeypatch) + intent = app.classify_event_intent(runtime, "pull_request_review", "dismissed") + assert intent == runtime.EVENT_INTENT_NON_MUTATING_READONLY + + +def test_classify_event_intent_review_comment_is_non_mutating_defer(monkeypatch): + runtime = FakeReviewerBotRuntime(monkeypatch) + intent = app.classify_event_intent(runtime, "pull_request_review_comment", "created") + assert intent == runtime.EVENT_INTENT_NON_MUTATING_DEFER + + +@pytest.mark.parametrize( + ("workflow_run_event", "workflow_run_event_action"), + [ + ("pull_request_review", "submitted"), + ("pull_request_review", "dismissed"), + ("issue_comment", "created"), + ("pull_request_review_comment", "created"), + ], +) +def test_classify_event_intent_supported_workflow_run_source_action_pairs_are_mutating( + monkeypatch, workflow_run_event, workflow_run_event_action +): + runtime = FakeReviewerBotRuntime(monkeypatch) + runtime.set_config_value("REVIEWER_BOT_WORKFLOW_KIND", "reconcile") + runtime.set_config_value("WORKFLOW_RUN_TRIGGERING_CONCLUSION", "success") + set_workflow_run_event_payload(runtime.config, "Reviewer Bot PR Review Submitted Observer") + intent = app.classify_event_intent(runtime, "workflow_run", "completed") + assert intent == runtime.EVENT_INTENT_MUTATING + + +@pytest.mark.parametrize( + ("workflow_run_event", "workflow_run_event_action"), + [ + ("pull_request_review", "edited"), + ("issue_comment", "deleted"), + ("pull_request_review_comment", "edited"), + ("pull_request_review", ""), + ("workflow_dispatch", "completed"), + ], +) +def test_classify_event_intent_unsupported_workflow_run_source_action_pairs_are_read_only( + monkeypatch, workflow_run_event, workflow_run_event_action +): + runtime = FakeReviewerBotRuntime(monkeypatch) + runtime.set_config_value("REVIEWER_BOT_WORKFLOW_KIND", workflow_run_event) + runtime.set_config_value("WORKFLOW_RUN_TRIGGERING_CONCLUSION", workflow_run_event_action) + + assert app.classify_event_intent(runtime, "workflow_run", "completed") == runtime.EVENT_INTENT_NON_MUTATING_READONLY + + +def test_d4a_transaction_phase_map_fixture_exists_and_lists_all_required_phases(): + phase_map = _load_phase_map() + + assert phase_map["harness_id"] == "D4a app transaction phase map" + assert phase_map["phases"] == [ + "lock acquisition", + "initial load", + "pass-until restoration", + "member sync", + "event handling", + "touched-item drain", + "schedule-only empty-active-reviews guard", + "authoritative-save epoch revalidation", + "authoritative save", + "reload before status sync", + "status-sync epoch revalidation", + "projection failure repair marker persistence", + "lock release", + ] diff --git a/tests/unit/reviewer_bot/test_approval_policy_equivalence.py b/tests/unit/reviewer_bot/test_approval_policy_equivalence.py new file mode 100644 index 000000000..df2b63923 --- /dev/null +++ b/tests/unit/reviewer_bot/test_approval_policy_equivalence.py @@ -0,0 +1,296 @@ +import json +from pathlib import Path +from types import SimpleNamespace + +from scripts.reviewer_bot_core import approval_policy, live_review_support +from scripts.reviewer_bot_lib import review_state, reviews +from scripts.reviewer_bot_lib.config import GitHubApiResult +from tests.fixtures.reviewer_bot import ( + make_state, + make_tracked_review_state, + pull_request_payload, + review_payload, +) + +C2C_DELETION_MANIFEST = [ + "compute_pr_approval_state_result", + "find_triage_approval_after", +] + + +def _read_table() -> str: + return Path("tests/fixtures/equivalence/approval_policy/function_classification_table.md").read_text( + encoding="utf-8" + ) + + +def _read_scope_fixture() -> dict: + return json.loads( + Path("tests/fixtures/equivalence/approval_policy/in_scope_functions.json").read_text( + encoding="utf-8" + ) + ) + + +def test_approval_policy_classification_artifacts_exist(): + assert Path("tests/fixtures/equivalence/approval_policy/function_classification_table.md").exists() + assert Path("tests/fixtures/equivalence/approval_policy/in_scope_functions.json").exists() + + +def test_approval_policy_classification_table_freezes_exact_categories_and_scope_boundary(): + table = _read_table() + + for heading in [ + "Stays in `reviews.py`", + "Moves to `approval_policy.py`", + "Moves to `live_review_support.py`", + "Out of scope", + ]: + assert heading in table + + for line in [ + "- `resolve_pr_approval_state`", + "- `rebuild_pr_approval_state`", + "- `rebuild_pr_approval_state_result`", + "- `pr_has_current_write_approval`", + "- `apply_pr_approval_state`", + "- `trigger_mandatory_approver_escalation`", + "- `satisfy_mandatory_approver_requirement`", + "- `handle_pr_approved_review`", + "- `compute_pr_approval_state_result`", + "- `find_triage_approval_after`", + "- `filter_current_head_reviews_for_cycle`", + "- `normalize_reviews_with_parsed_timestamps`", + "- `collect_permission_statuses`", + "- `compute_pr_approval_state_from_reviews`", + "- `desired_labels_from_response_state`", + "- `compute_reviewer_response_state`: out of scope for `C2a/C2b`", + ]: + assert line in table + + +def test_approval_policy_in_scope_fixture_names_exact_functions_for_c2b(): + fixture = _read_scope_fixture() + + assert fixture["harness_id"] == "C2b approval/completion derivation equivalence" + assert fixture["in_scope_move_functions"] == [ + "compute_pr_approval_state_result", + "find_triage_approval_after", + ] + assert fixture["out_of_scope_functions"] == ["compute_reviewer_response_state"] + + +def _legacy_compute_pr_approval_state_result( + bot, + issue_number: int, + review_data: dict, + *, + pull_request: dict | None = None, + reviews_data: list[dict] | None = None, +) -> dict[str, object]: + from scripts.reviewer_bot_core.approval_policy import ( + compute_pr_approval_state_from_reviews, + ) + from scripts.reviewer_bot_core.live_review_support import ( + collect_permission_statuses, + filter_current_head_reviews_for_cycle, + normalize_reviews_with_parsed_timestamps, + ) + + boundary = review_state.get_current_cycle_boundary(bot, review_data) + if boundary is None: + return live_review_support.projection_failure_result("pull_request_unavailable") + pull_request_result = live_review_support.read_pull_request_result(bot, issue_number, pull_request) + if not pull_request_result.get("ok"): + return pull_request_result + pull_request = pull_request_result["pull_request"] + head = pull_request.get("head") + current_head = head.get("sha") if isinstance(head, dict) else None + if not isinstance(current_head, str) or not current_head.strip(): + return live_review_support.projection_failure_result("pull_request_head_unavailable", "invalid_payload") + reviews_result = live_review_support.read_pull_request_reviews_result(bot, issue_number, reviews_data) + if not reviews_result.get("ok"): + return reviews_result + reviews_data = reviews_result["reviews"] + + normalized_reviews = normalize_reviews_with_parsed_timestamps( + reviews_data, + parse_timestamp=live_review_support.parse_github_timestamp, + ) + survivors = filter_current_head_reviews_for_cycle( + normalized_reviews, + boundary=boundary, + current_head=current_head, + ) + permission_cache = collect_permission_statuses( + survivors, + permission_status=lambda author: live_review_support.permission_status(bot, author, "push"), + ) + result = compute_pr_approval_state_from_reviews( + survivors, + current_head=current_head, + permission_statuses=permission_cache, + ) + if not result.get("ok"): + return live_review_support.projection_failure_result(str(result.get("reason"))) + return result + + +def _legacy_find_triage_approval_after(bot, reviews_data: list[dict], since): + permission_cache: dict[str, bool] = {} + approvals = [] + for review in reviews_data: + state = str(review.get("state", "")).upper() + if state != "APPROVED": + continue + author = review.get("user", {}).get("login") + if not isinstance(author, str) or not author: + continue + submitted_at = bot.parse_github_timestamp(review.get("submitted_at")) + if submitted_at is None: + continue + if since is not None and submitted_at <= since: + continue + approvals.append((submitted_at, str(review.get("id", "")), author)) + approvals.sort(key=lambda item: (item[0], item[1])) + for submitted_at, _, author in approvals: + cache_key = author.lower() + if cache_key not in permission_cache: + permission_cache[cache_key] = bot.is_triage_or_higher(author) + if permission_cache[cache_key]: + return author, submitted_at + return None + + +def test_approval_policy_equivalence_matches_legacy_approval_derivation_surface(): + state = make_state() + review = make_tracked_review_state( + state, + 42, + reviewer="alice", + active_cycle_started_at="2026-03-17T09:00:00Z", + ) + bot = type( + "Bot", + (), + { + "github_api_request": staticmethod( + lambda method, endpoint, data=None, extra_headers=None, **kwargs: GitHubApiResult( + 200, + pull_request_payload(42, head_sha="head-1") + if endpoint == "pulls/42" + else [ + review_payload( + 10, + state="APPROVED", + submitted_at="2026-03-17T10:01:00Z", + commit_id="head-1", + author="alice", + ) + ], + {}, + "ok", + True, + None, + 0, + None, + ) + ), + "github_api": staticmethod(lambda method, endpoint, data=None: {}), + "github": SimpleNamespace(get_user_permission_status=lambda username, permission="push": "granted"), + "parse_github_timestamp": staticmethod(reviews.parse_github_timestamp), + "parse_iso8601_timestamp": staticmethod(reviews.parse_github_timestamp), + "get_user_permission_status": staticmethod(lambda username, required_permission="push": "granted"), + "is_triage_or_higher": staticmethod(lambda username: username == "alice"), + }, + )() + + assert approval_policy.compute_pr_approval_state_result(bot, 42, review) == _legacy_compute_pr_approval_state_result( + bot, + 42, + review, + ) + triage_reviews = [ + review_payload( + 11, + state="APPROVED", + submitted_at="2026-03-17T10:02:00Z", + commit_id="head-1", + author="alice", + ) + ] + since = reviews.parse_github_timestamp("2026-03-17T10:00:00Z") + assert approval_policy.find_triage_approval_after(bot, triage_reviews, since) == _legacy_find_triage_approval_after( + bot, + triage_reviews, + since, + ) + + +def test_reviews_module_keeps_only_post_f1c_approval_surfaces(): + reviews_text = Path("scripts/reviewer_bot_lib/reviews.py").read_text(encoding="utf-8") + + assert "def compute_pr_approval_state_result(" not in reviews_text + assert "def find_triage_approval_after(" not in reviews_text + assert "def rebuild_pr_approval_state_result(" in reviews_text + assert "def rebuild_pr_approval_state(" in reviews_text + + +def test_reviewer_response_policy_derives_approval_state_from_core_owner_without_mutating_reviews_wrapper(): + reviewer_response_text = Path("scripts/reviewer_bot_core/reviewer_response_policy.py").read_text( + encoding="utf-8" + ) + reviews_text = Path("scripts/reviewer_bot_lib/reviews.py").read_text(encoding="utf-8") + + assert "approval_policy.compute_pr_approval_state_result(" in reviewer_response_text + assert "legacy_reviews.resolve_pr_approval_state(" not in reviewer_response_text + assert "def rebuild_pr_approval_state_result(" in reviews_text + assert "def rebuild_pr_approval_state(" in reviews_text + + +def test_c2c_deletion_manifest_names_in_scope_legacy_derivation_paths_now_reduced_to_delegation(): + reviews_text = Path("scripts/reviewer_bot_lib/reviews.py").read_text(encoding="utf-8") + + assert C2C_DELETION_MANIFEST == [ + "compute_pr_approval_state_result", + "find_triage_approval_after", + ] + assert "def compute_pr_approval_state_result(" not in reviews_text + assert "def find_triage_approval_after(" not in reviews_text + + +def test_c2c_caller_migration_moves_production_callers_off_reviews_derivation_path(): + reconcile_text = Path("scripts/reviewer_bot_lib/reconcile.py").read_text(encoding="utf-8") + + assert "find_triage_approval_after," not in reconcile_text + assert "approval_policy.find_triage_approval_after(" in reconcile_text + + +def test_c2c_repo_wide_caller_inventory_for_reviews_derivation_path_is_zero_outside_reviews_module(): + root = Path("scripts/reviewer_bot_lib") + callers = [] + + for path in root.glob("*.py"): + if path.name == "reviews.py": + continue + text = path.read_text(encoding="utf-8") + for symbol in C2C_DELETION_MANIFEST: + if f"reviews.{symbol}(" in text or f"from .reviews import {symbol}" in text: + callers.append((path.as_posix(), symbol)) + + assert callers == [] + + +def test_f1c_deleted_legacy_derivation_paths_are_no_longer_imported_by_unit_tests(): + projection_text = Path("tests/unit/reviewer_bot/test_reviews_projection.py").read_text(encoding="utf-8") + + assert "reviews.compute_pr_approval_state_result(" not in projection_text + assert "approval_policy.compute_pr_approval_state_result(" in projection_text + + +def test_g1_approval_policy_owns_cycle_boundary_without_review_state_wrapper_dependency(): + approval_policy_text = Path("scripts/reviewer_bot_core/approval_policy.py").read_text(encoding="utf-8") + + assert "from scripts.reviewer_bot_lib import review_state" not in approval_policy_text + assert "review_state.get_current_cycle_boundary" not in approval_policy_text + assert "live_review_support.get_current_cycle_boundary" in approval_policy_text diff --git a/tests/unit/reviewer_bot/test_commands.py b/tests/unit/reviewer_bot/test_commands.py new file mode 100644 index 000000000..b9240e988 --- /dev/null +++ b/tests/unit/reviewer_bot/test_commands.py @@ -0,0 +1,578 @@ + +from pathlib import Path + +import pytest + +from scripts.reviewer_bot_core import privileged_command_policy +from scripts.reviewer_bot_lib import ( + automation, + commands, + comment_application, + guidance, + reconcile, + review_state, +) +from scripts.reviewer_bot_lib.config import FLS_AUDIT_LABEL +from tests.fixtures.commands_harness import CommandHarness +from tests.fixtures.comment_routing_harness import CommentRoutingHarness +from tests.fixtures.reviewer_bot import make_state + + +def test_label_signoff_create_pr_marks_issue_review_complete_without_inline_status_sync(monkeypatch): + harness = CommandHarness(monkeypatch) + assert harness.github is harness.runtime.github + assert harness.handlers is harness.runtime.handlers + state = make_state() + review = review_state.ensure_review_entry(state, 42, create=True) + assert review is not None + review["current_reviewer"] = "alice" + request = harness.typed_comment_request( + issue_number=42, + actor="alice", + body="@guidelines-bot /label +sign-off: create pr", + issue_author="dana", + is_pull_request=False, + ) + harness.runtime.get_repo_labels = lambda: ["sign-off: create pr"] + harness.runtime.add_label = lambda issue_number, label: True + harness.runtime.sync_status_labels_for_items = lambda *args, **kwargs: pytest.fail( + "status sync should run only from app orchestration after save" + ) + harness.runtime.add_reaction = lambda *args, **kwargs: True + posted = harness.capture_posted_comments() + + assert harness.handle_comment_event(state, request=request) is True + assert review["review_completion_source"] == "issue_label: sign-off: create pr" + assert review["current_cycle_completion"]["completed"] is True + assert posted == [(42, "✅ Added label `sign-off: create pr`")] + + +def test_label_signoff_create_pr_on_pr_does_not_mark_issue_complete(monkeypatch): + harness = CommandHarness(monkeypatch) + state = make_state() + review = review_state.ensure_review_entry(state, 42, create=True) + assert review is not None + review["current_reviewer"] = "alice" + request = harness.typed_comment_request( + issue_number=42, + actor="alice", + body="@guidelines-bot /label +sign-off: create pr", + issue_author="dana", + is_pull_request=True, + ) + trust_context = harness.typed_trust_context( + author_association="MEMBER", + workflow_file=".github/workflows/reviewer-bot-pr-comment-trusted.yml", + repository="rustfoundation/safety-critical-rust-coding-guidelines", + ref="refs/heads/main", + ) + harness.runtime.github_api = lambda method, endpoint, data=None: { + "head": {"repo": {"full_name": "rustfoundation/safety-critical-rust-coding-guidelines"}}, + "user": {"login": "dana"}, + "pull_request": {}, + } + harness.runtime.get_repo_labels = lambda: ["sign-off: create pr"] + harness.runtime.add_label = lambda issue_number, label: True + harness.runtime.sync_status_labels_for_items = lambda *args, **kwargs: pytest.fail( + "status sync should not run for PR sign-off label command" + ) + harness.runtime.add_reaction = lambda *args, **kwargs: True + harness.runtime.post_comment = lambda *args, **kwargs: True + + assert harness.handle_comment_event(state, request=request, trust_context=trust_context) is False + assert review["review_completion_source"] is None + + +def test_create_pull_request_fails_closed_when_open_pr_lookup_unavailable(monkeypatch): + harness = CommandHarness(monkeypatch) + called = {"post": 0} + harness.runtime.set_config_value("REPO_OWNER", "rustfoundation") + harness.runtime.github_api_request = lambda method, endpoint, data=None, extra_headers=None, **kwargs: harness.runtime.GitHubApiResult( + 502, {"message": "bad gateway"}, {}, "bad gateway", False, "server_error", 1, None + ) + harness.runtime.github_api = lambda method, endpoint, data=None: called.__setitem__("post", called["post"] + 1) or None + + with pytest.raises(RuntimeError, match="Unable to determine whether branch 'feature-branch' already has an open PR"): + automation.create_pull_request(harness.runtime, "feature-branch", "main", 42) + + assert called["post"] == 0 + + +def test_assign_command_fails_closed_when_assignees_unavailable(monkeypatch): + harness = CommandHarness(monkeypatch) + state = make_state() + state["queue"] = [{"github": "felix91gr", "name": "Félix Fischer"}] + harness.runtime.get_issue_assignees = lambda issue_number: None + + response, success = harness.handle_assign(state, 42, "@felix91gr") + + assert success is False + assert "Unable to determine current assignees/reviewers" in response + + +def test_assign_command_posts_pr_guidance_on_success(monkeypatch): + harness = CommandHarness(monkeypatch) + state = make_state() + state["queue"] = [{"github": "felix91gr", "name": "Félix Fischer"}] + request = harness.typed_assignment_request(issue_number=42, issue_author="PLeVasseur", is_pull_request=True) + harness.stub_assignees([]) + harness.stub_assignment() + posted = [] + harness.runtime.post_comment = lambda issue_number, body: posted.append(body) or True + + response, success = harness.handle_assign(state, 42, "@felix91gr", request=request) + + assert success is True + assert response == "✅ @felix91gr has been assigned as reviewer." + assert posted == [guidance.get_pr_guidance("felix91gr", "PLeVasseur")] + + +def test_claim_command_posts_pr_guidance_on_success(monkeypatch): + harness = CommandHarness(monkeypatch) + state = make_state() + state["queue"] = [{"github": "felix91gr", "name": "Félix Fischer"}] + request = harness.typed_assignment_request(issue_number=42, issue_author="PLeVasseur", is_pull_request=True) + harness.stub_assignees([]) + harness.stub_assignment() + posted = [] + harness.runtime.post_comment = lambda issue_number, body: posted.append(body) or True + + response, success = harness.handle_claim(state, 42, "felix91gr", request=request) + + assert success is True + assert response == "✅ @felix91gr has claimed this review." + assert posted == [guidance.get_pr_guidance("felix91gr", "PLeVasseur")] + + +def test_pass_command_posts_pr_guidance_for_new_reviewer(monkeypatch): + harness = CommandHarness(monkeypatch) + state = make_state() + state["queue"] = [ + {"github": "alice", "name": "Alice"}, + {"github": "felix91gr", "name": "Félix Fischer"}, + ] + review = review_state.ensure_review_entry(state, 42, create=True) + assert review is not None + review["current_reviewer"] = "alice" + request = harness.typed_assignment_request(issue_number=42, issue_author="PLeVasseur", is_pull_request=True) + harness.stub_assignees(["alice"]) + harness.stub_assignment() + harness.runtime.unassign_reviewer = lambda issue_number, username: True + posted = [] + harness.runtime.post_comment = lambda issue_number, body: posted.append(body) or True + + response, success = harness.handle_pass(state, 42, "alice", None, request=request) + + assert success is True + assert "@felix91gr is now assigned as the reviewer." in response + assert posted == [guidance.get_pr_guidance("felix91gr", "PLeVasseur")] + + +def test_assign_from_queue_posts_guidance_only_once(monkeypatch): + harness = CommandHarness(monkeypatch) + state = make_state() + state["queue"] = [{"github": "felix91gr", "name": "Félix Fischer"}] + request = harness.typed_assignment_request(issue_number=42, issue_author="PLeVasseur", is_pull_request=True) + harness.stub_assignees([]) + harness.stub_assignment() + posted = [] + harness.runtime.post_comment = lambda issue_number, body: posted.append(body) or True + + response, success = harness.handle_assign_from_queue(state, 42, request=request) + + assert success is True + assert response == "✅ @felix91gr (next in queue) has been assigned as reviewer." + assert posted == [guidance.get_pr_guidance("felix91gr", "PLeVasseur")] + + +def test_handle_accept_no_fls_changes_command_fails_closed_when_permission_unavailable(monkeypatch): + harness = CommandHarness(monkeypatch) + request = harness.typed_privileged_request( + issue_number=42, + actor="alice", + command_name="accept-no-fls-changes", + is_pull_request=False, + issue_labels=(FLS_AUDIT_LABEL,), + ) + harness.stub_permission("unavailable") + + message, success = harness.handle_accept_no_fls_changes(42, "alice", request=request) + + assert success is False + assert "Unable to verify triage permissions right now" in message + + +def test_i1_comment_application_consumes_typed_ordinary_command_result(monkeypatch): + module_text = Path("scripts/reviewer_bot_lib/comment_application.py").read_text(encoding="utf-8") + + assert "ORDINARY_COMMAND_HANDLERS" in module_text + assert "decision.command_id" in module_text + assert "decision.needs_assignment_request" in module_text + assert "CommandExecutionResult" in module_text + + +def test_k2_comment_application_entrypoints_use_narrow_comment_runtime_protocol(): + module_text = Path("scripts/reviewer_bot_lib/comment_application.py").read_text(encoding="utf-8") + + assert "CommentApplicationRuntimeContext" in module_text + assert "bot: CommentApplicationRuntimeContext" in module_text + + +def test_pass_command_fails_closed_when_assignees_unavailable(monkeypatch): + harness = CommandHarness(monkeypatch) + state = make_state() + review = review_state.ensure_review_entry(state, 42, create=True) + assert review is not None + harness.runtime.get_issue_assignees = lambda issue_number: None + + response, success = harness.handle_pass(state, 42, "alice", None) + + assert success is False + assert "Unable to determine current assignees/reviewers" in response + + +def test_away_command_fails_closed_when_assignees_unavailable(monkeypatch): + harness = CommandHarness(monkeypatch) + state = make_state() + state["queue"] = [{"github": "alice", "name": "Alice"}, {"github": "bob", "name": "Bob"}] + review = review_state.ensure_review_entry(state, 42, create=True) + assert review is not None + review["current_reviewer"] = "alice" + harness.runtime.get_issue_assignees = lambda issue_number: None + + response, success = harness.handle_pass_until(state, 42, "alice", "2099-01-01", None) + + assert success is False + assert "Unable to determine current assignees/reviewers" in response + + +def test_claim_command_fails_closed_when_assignees_unavailable(monkeypatch): + harness = CommandHarness(monkeypatch) + state = make_state() + state["queue"] = [{"github": "alice", "name": "Alice"}] + harness.runtime.get_issue_assignees = lambda issue_number: None + + response, success = harness.handle_claim(state, 42, "alice") + + assert success is False + assert "Unable to determine current assignees/reviewers" in response + + +def test_release_command_fails_closed_when_permission_unavailable(monkeypatch): + harness = CommandHarness(monkeypatch) + state = make_state() + harness.runtime.get_user_permission_status = lambda username, required_permission="triage": "unavailable" + + response, success = harness.handle_release(state, 42, "alice", ["@bob"]) + + assert success is False + assert "Unable to verify triage permissions right now" in response + + +def test_release_command_fails_closed_when_assignees_unavailable(monkeypatch): + harness = CommandHarness(monkeypatch) + state = make_state() + harness.runtime.get_issue_assignees = lambda issue_number: None + + response, success = harness.handle_release(state, 42, "alice") + + assert success is False + assert "Unable to determine current assignees/reviewers" in response + + +def test_assign_from_queue_command_fails_closed_when_assignees_unavailable(monkeypatch): + harness = CommandHarness(monkeypatch) + state = make_state() + state["queue"] = [{"github": "alice", "name": "Alice"}] + harness.runtime.get_issue_assignees = lambda issue_number: None + + response, success = harness.handle_assign_from_queue(state, 42) + + assert success is False + assert "Unable to determine current assignees/reviewers" in response + + +def test_handle_rectify_command_reports_permission_unavailable(monkeypatch): + state = make_state() + harness = CommandHarness(monkeypatch) + monkeypatch.setattr(reconcile, "ensure_review_entry", lambda current, issue_number, create=False: None) + harness.runtime.get_user_permission_status = lambda username, required_permission="triage": "unavailable" + + message, success, changed = harness.handle_rectify(state, 42, "alice") + + assert success is False + assert changed is False + assert "Unable to verify triage permissions right now" in message + + +def test_handle_rectify_command_reports_permission_denied(monkeypatch): + state = make_state() + harness = CommandHarness(monkeypatch) + monkeypatch.setattr(reconcile, "ensure_review_entry", lambda current, issue_number, create=False: None) + harness.runtime.get_user_permission_status = lambda username, required_permission="triage": "denied" + + message, success, changed = harness.handle_rectify(state, 42, "alice") + + assert success is False + assert changed is False + assert "Only maintainers with triage+ permission" in message + + +def test_validate_accept_no_fls_changes_handoff_distinguishes_permission_unavailable(monkeypatch): + harness = CommentRoutingHarness(monkeypatch) + harness.runtime.set_config_value("ISSUE_LABELS", f'["{FLS_AUDIT_LABEL}"]') + harness.runtime.get_user_permission_status = lambda username, required_permission="triage": "unavailable" + request = harness.request( + issue_number=42, + is_pull_request=False, + issue_labels=(FLS_AUDIT_LABEL,), + comment_author="alice", + comment_body="@guidelines-bot /accept-no-fls-changes", + ) + + decision = privileged_command_policy.validate_accept_no_fls_changes_handoff( + request, + "unavailable", + source_event_key="issue_comment:100", + ) + + assert decision == privileged_command_policy.BlockedPrivilegedHandoff( + reason="authorization_unavailable", + response="❌ Unable to verify triage permissions right now; refusing to run this command.", + ) + + +@pytest.mark.parametrize( + ("is_pull_request", "labels", "permission", "expected_reason"), + [ + (True, (FLS_AUDIT_LABEL,), "granted", "pull_request_target_not_allowed"), + (False, (), "granted", "missing_fls_audit_label"), + (False, (FLS_AUDIT_LABEL,), "denied", "authorization_failed"), + ], +) +def test_validate_accept_no_fls_changes_handoff_freezes_fail_closed_reason_matrix( + monkeypatch, + is_pull_request, + labels, + permission, + expected_reason, +): + harness = CommentRoutingHarness(monkeypatch) + harness.runtime.set_config_value("ISSUE_LABELS", str(list(labels)).replace("'", '"')) + harness.runtime.get_user_permission_status = lambda username, required_permission="triage": permission + request = harness.request( + issue_number=42, + is_pull_request=is_pull_request, + issue_labels=labels, + comment_author="alice", + comment_body="@guidelines-bot /accept-no-fls-changes", + ) + + decision = privileged_command_policy.validate_accept_no_fls_changes_handoff( + request, + permission, + source_event_key="issue_comment:100", + ) + + assert decision.reason == expected_reason + + +def test_validate_accept_no_fls_changes_handoff_freezes_success_metadata_shape(monkeypatch): + harness = CommentRoutingHarness(monkeypatch) + harness.runtime.set_config_value("ISSUE_LABELS", f'["{FLS_AUDIT_LABEL}"]') + harness.runtime.get_user_permission_status = lambda username, required_permission="triage": "granted" + request = harness.request( + issue_number=42, + is_pull_request=False, + issue_labels=(FLS_AUDIT_LABEL,), + comment_author="alice", + comment_body="@guidelines-bot /accept-no-fls-changes", + ) + + decision = privileged_command_policy.validate_accept_no_fls_changes_handoff( + request, + "granted", + source_event_key="issue_comment:100", + ) + + assert decision == privileged_command_policy.AllowedPrivilegedHandoff( + source_event_key="issue_comment:100", + command_name=privileged_command_policy.PrivilegedCommandId.ACCEPT_NO_FLS_CHANGES.value, + issue_number=42, + actor="alice", + authorization_required_permission="triage", + authorization_authorized=True, + target_kind="issue", + target_number=42, + target_labels_snapshot=(FLS_AUDIT_LABEL,), + ) + + +def test_apply_comment_command_records_privileged_handoff_side_effects(monkeypatch): + harness = CommandHarness(monkeypatch) + state = make_state() + side_effects = harness.capture_comment_side_effects() + request = harness.typed_comment_request( + issue_number=42, + actor="alice", + body="@guidelines-bot /accept-no-fls-changes", + issue_author="dana", + is_pull_request=False, + issue_labels=(FLS_AUDIT_LABEL,), + ) + harness.stub_permission("granted") + + changed = comment_application.apply_comment_command( + harness.runtime, + state, + request, + {"command": "accept-no-fls-changes", "args": [], "command_count": 1}, + classify_issue_comment_actor=lambda current_request: "repo_user_principal", + ) + + pending = state["active_reviews"]["42"]["sidecars"]["pending_privileged_commands"]["issue_comment:100"] + assert changed is True + assert pending["command_name"] == "accept-no-fls-changes" + assert pending["status"] == "pending" + assert pending["authorization_authorized"] is True + assert side_effects.comments == [ + ( + 42, + "✅ Recorded pending privileged command `accept-no-fls-changes` from trusted live validation. Use the isolated privileged workflow to execute it from issue `#314` state.", + ) + ] + assert side_effects.reactions == [] + + +def test_apply_comment_command_adds_reactions_and_posts_normalized_queue_response(monkeypatch): + harness = CommandHarness(monkeypatch) + state = make_state() + side_effects = harness.capture_comment_side_effects() + request = harness.typed_comment_request( + issue_number=42, + actor="alice", + body="@guidelines-bot /queue", + issue_author="dana", + is_pull_request=False, + ) + monkeypatch.setattr(commands, "handle_queue_command", lambda bot, current_state: ("queue snapshot", True)) + + changed = comment_application.apply_comment_command( + harness.runtime, + state, + request, + {"command": "queue", "args": [], "command_count": 1}, + classify_issue_comment_actor=lambda current_request: "repo_user_principal", + ) + + assert changed is False + assert side_effects.comments == [(42, "queue snapshot")] + assert side_effects.reactions == [(100, "eyes"), (100, "+1")] + + +def test_manual_dispatch_marks_authorization_unavailable_for_pending_privileged_command(monkeypatch): + harness = CommandHarness(monkeypatch) + state = make_state() + review = review_state.ensure_review_entry(state, 42, create=True) + assert review is not None + review["sidecars"]["pending_privileged_commands"] = { + "issue_comment:100": { + "source_event_key": "issue_comment:100", + "command_name": "accept-no-fls-changes", + "issue_number": 42, + "actor": "alice", + "authorization_required_permission": "triage", + "authorization_authorized": True, + "target_kind": "issue", + "target_number": 42, + "target_labels_snapshot": [FLS_AUDIT_LABEL], + "status": "pending", + "created_at": "2026-03-17T10:00:00Z", + } + } + harness.set_manual_dispatch(source_event_key="issue_comment:100") + harness.runtime.get_issue_or_pr_snapshot = lambda issue_number: {"number": issue_number, "labels": [{"name": FLS_AUDIT_LABEL}]} + harness.runtime.get_user_permission_status = lambda username, required_permission="triage": "unavailable" + + assert harness.handle_manual_dispatch(state) is True + pending = review["sidecars"]["pending_privileged_commands"]["issue_comment:100"] + assert pending["status"] == "failed_closed" + assert pending["result_code"] == "authorization_unavailable" + + +@pytest.mark.parametrize( + ("comment_body", "expected"), + [ + ("@guidelines-bot /queue", ("queue", [])), + ("@guidelines-bot /r? producers", ("assign-from-queue", [])), + ("@guidelines-bot /r? @alice", ("r?-user", ["@alice"])), + ("@guidelines-bot queue", ("_malformed_known", ["queue"])), + ("@guidelines-bot /queue\n@guidelines-bot /pass", ("_multiple_commands", [])), + ("@guidelines-bot hello", None), + ], +) +def test_parse_command_preserves_known_command_classification(monkeypatch, comment_body, expected): + harness = CommandHarness(monkeypatch) + parser_bot = type( + "ParserBot", + (), + { + "BOT_MENTION": harness.runtime.BOT_MENTION, + "COMMANDS": { + "queue", + "pass", + "label", + "away", + "claim", + "release", + "rectify", + "sync-members", + "accept-no-fls-changes", + }, + }, + )() + + assert commands.parse_command(parser_bot, comment_body) == expected + + +def test_parse_command_preserves_quoted_args(monkeypatch): + harness = CommandHarness(monkeypatch) + + parser_bot = type("ParserBot", (), {"BOT_MENTION": harness.runtime.BOT_MENTION, "COMMANDS": {"label"}})() + + assert commands.parse_command(parser_bot, '@guidelines-bot /label +"needs decision"') == ("label", ["+needs decision"]) + + +def test_strip_code_blocks_removes_fenced_indented_and_inline_code(monkeypatch): + comment_body = """before +```bash +@guidelines-bot /queue +``` + @guidelines-bot /queue +inline `@guidelines-bot /queue` +after""" + + assert commands.strip_code_blocks(comment_body) == "before\n\n\ninline \nafter" + + +def test_comment_application_delegates_ordinary_command_decision_to_core_policy(): + module_text = Path("scripts/reviewer_bot_lib/comment_application.py").read_text(encoding="utf-8") + + assert "comment_command_policy" in module_text + assert "decision = comment_command_policy.decide_comment_command(" in module_text + + +def test_commands_module_exposes_rectify_handler_for_decision_adapter_surface(): + assert hasattr(commands, "handle_rectify_command") is False + + +def test_comment_application_and_automation_delegate_privileged_planning_to_core_policy(): + comment_application_text = Path("scripts/reviewer_bot_lib/comment_application.py").read_text(encoding="utf-8") + automation_text = Path("scripts/reviewer_bot_lib/automation.py").read_text(encoding="utf-8") + + assert "privileged_command_policy" in comment_application_text + assert "privileged_command_policy.validate_accept_no_fls_changes_handoff(" in comment_application_text + assert "privileged_command_policy.build_pending_privileged_command(" in comment_application_text + assert "from scripts.reviewer_bot_core import privileged_command_policy" in automation_text + assert "privileged_command_policy.prevalidate_accept_no_fls_changes_request(" in automation_text + assert "privileged_command_policy.plan_accept_no_fls_changes_execution(" in automation_text diff --git a/tests/unit/reviewer_bot/test_comment_command_equivalence.py b/tests/unit/reviewer_bot/test_comment_command_equivalence.py new file mode 100644 index 000000000..b4dbe83fd --- /dev/null +++ b/tests/unit/reviewer_bot/test_comment_command_equivalence.py @@ -0,0 +1,288 @@ +import json +from pathlib import Path + +from scripts.reviewer_bot_core import comment_command_policy, privileged_command_policy +from scripts.reviewer_bot_lib import commands, comment_application +from scripts.reviewer_bot_lib import config as config_module +from tests.fixtures.commands_harness import CommandHarness +from tests.fixtures.reviewer_bot import make_state +from tests.fixtures.reviewer_bot_recorders import record_comment_side_effects + +C3B2_DELETION_MANIFEST = [ + "pass", + "away", + "label", + "sync-members", + "queue", + "commands", + "claim", + "release", + "rectify", + "r?-user", + "assign-from-queue", + "r?", + "_multiple_commands", + "_malformed_known", + "_malformed_unknown", + "unknown_command", +] + + +def _load_fixture() -> dict: + return json.loads( + Path("tests/fixtures/equivalence/comment_command/decision_scope.json").read_text( + encoding="utf-8" + ) + ) + + +def _legacy_decide_comment_command(bot, request, classified, *, actor_class: str, commands_help: str) -> dict: + decision = comment_command_policy.decide_comment_command( + bot, + request, + classified, + actor_class=actor_class, + commands_help=commands_help, + ) + return _normalize_decision(decision) + + +def _legacy_apply_ordinary_decision(bot, state: dict, request, decision: dict) -> bool: + issue_number = request.issue_number + review_data = comment_application.ensure_review_entry(state, issue_number, create=True) + if review_data is None: + return False + assignment_request = comment_application._build_assignment_request_from_comment_request(request) + if decision["kind"] == "execute_ordinary_command": + typed_decision = comment_command_policy.ExecuteOrdinaryCommandDecision( + command_id=decision["command_id"], + issue_number=decision["issue_number"], + actor=decision["actor"], + raw_args=tuple(decision["raw_args"]), + needs_assignment_request=decision["needs_assignment_request"], + ) + execution = comment_application.ORDINARY_COMMAND_HANDLERS[decision["command_id"]](bot, state, typed_decision, assignment_request if decision["needs_assignment_request"] else None) + response = execution.response + success = execution.success + state_changed = execution.state_changed + else: + response = decision["response"] + success = decision["success"] + state_changed = bool(decision.get("state_changed", False)) + + if request.comment_id > 0 and decision.get("react", True): + bot.github.add_reaction(request.comment_id, "eyes") + if success: + bot.github.add_reaction(request.comment_id, "+1") + if response: + bot.github.post_comment(issue_number, response) + return state_changed + + +def _normalize_decision(decision): + if isinstance(decision, dict): + return {key: value for key, value in decision.items() if key != "raw_decision"} + if isinstance(decision, comment_command_policy.IgnoreDecision): + return {"kind": "ignore"} + if isinstance(decision, comment_command_policy.InlineResponseDecision): + return {"kind": "inline_response", "response": decision.response, "success": decision.success, "react": decision.react} + if isinstance(decision, comment_command_policy.DeferPrivilegedHandoffDecision): + return {"kind": "deferred_privileged_handoff", "command_id": decision.command_id, "raw_decision": decision} + return { + "kind": "execute_ordinary_command", + "command_id": decision.command_id, + "issue_number": decision.issue_number, + "actor": decision.actor, + "raw_args": decision.raw_args, + "needs_assignment_request": decision.needs_assignment_request, + } + + +def _stub_common_handlers(monkeypatch): + monkeypatch.setattr(commands, "handle_queue_command", lambda bot, state: ("queue snapshot", True)) + monkeypatch.setattr(commands, "handle_label_command", lambda bot, state, issue_number, labels, request=None: ("label updated", True, True)) + + +def test_comment_command_fixture_declares_ordinary_scope_and_deferred_privileged_path(): + fixture = _load_fixture() + + assert fixture["harness_id"] == "C3b1 command decision equivalence" + assert fixture["deferred_command_set"] == ["accept-no-fls-changes"] + assert fixture["equivalence_scenarios"] == [ + "queue_success", + "label_triplet_handler", + "away_missing_date", + "multiple_commands_warning", + "unknown_command_error", + ] + + +def test_command_policy_explicitly_defers_privileged_handoff_path(monkeypatch): + harness = CommandHarness(monkeypatch) + request = harness.typed_comment_request( + issue_number=42, + actor="alice", + body="@guidelines-bot /accept-no-fls-changes", + issue_author="dana", + is_pull_request=False, + ) + + decision = comment_command_policy.decide_comment_command( + harness.runtime, + request, + {"command": "accept-no-fls-changes", "args": [], "command_count": 1}, + actor_class="repo_user_principal", + commands_help="help text", + ) + + assert decision == comment_command_policy.DeferPrivilegedHandoffDecision( + privileged_command_policy.PrivilegedCommandId.ACCEPT_NO_FLS_CHANGES + ) + + +def test_i1_comment_policy_types_ordinary_command_output_shape(monkeypatch): + harness = CommandHarness(monkeypatch) + request = harness.typed_comment_request( + issue_number=42, + actor="alice", + body="@guidelines-bot /queue", + issue_author="dana", + is_pull_request=False, + ) + + decision = comment_command_policy.decide_comment_command( + harness.runtime, + request, + {"command": "queue", "args": [], "command_count": 1}, + actor_class="repo_user_principal", + commands_help="help text", + ) + + assert isinstance(decision, comment_command_policy.ExecuteOrdinaryCommandDecision) + assert list(decision.__dataclass_fields__) == [ + "command_id", + "issue_number", + "actor", + "raw_args", + "needs_assignment_request", + ] + + +def test_i2_comment_application_routing_does_not_reopen_command_semantics_in_adapter(): + module_text = Path("scripts/reviewer_bot_lib/comment_application.py").read_text(encoding="utf-8") + + assert 'if routing in {"freshness_only", "both"}:' in module_text + assert 'if routing in {"command_only", "both"}:' in module_text + assert "if command == " not in module_text + + +def test_c3b2_deletion_manifest_keeps_privileged_handoff_outside_ordinary_scope(): + fixture = _load_fixture() + + assert fixture["in_scope_command_set"] == C3B2_DELETION_MANIFEST + assert fixture["deferred_command_set"] == ["accept-no-fls-changes"] + + +def test_command_decision_equivalence_matches_legacy_for_ordinary_paths(monkeypatch): + fixture = _load_fixture() + + scenarios = [ + ( + "queue_success", + {"command": "queue", "args": [], "command_count": 1}, + CommandHarness(monkeypatch).typed_comment_request( + issue_number=42, + actor="alice", + body="@guidelines-bot /queue", + issue_author="dana", + is_pull_request=False, + ), + ), + ( + "label_triplet_handler", + {"command": "label", "args": ["+triage"], "command_count": 1}, + CommandHarness(monkeypatch).typed_comment_request( + issue_number=42, + actor="alice", + body="@guidelines-bot /label +triage", + issue_author="dana", + is_pull_request=False, + ), + ), + ( + "away_missing_date", + {"command": "away", "args": [], "command_count": 1}, + CommandHarness(monkeypatch).typed_comment_request( + issue_number=42, + actor="alice", + body="@guidelines-bot /away", + issue_author="dana", + is_pull_request=False, + ), + ), + ( + "multiple_commands_warning", + {"command": "_multiple_commands", "args": [], "command_count": 2}, + CommandHarness(monkeypatch).typed_comment_request( + issue_number=42, + actor="alice", + body="@guidelines-bot /queue\n@guidelines-bot /pass", + issue_author="dana", + is_pull_request=False, + ), + ), + ( + "unknown_command_error", + {"command": "does-not-exist", "args": [], "command_count": 1}, + CommandHarness(monkeypatch).typed_comment_request( + issue_number=42, + actor="alice", + body="@guidelines-bot /does-not-exist", + issue_author="dana", + is_pull_request=False, + ), + ), + ] + + assert fixture["equivalence_scenarios"] == [scenario[0] for scenario in scenarios] + + for scenario_name, classified, request in scenarios: + _stub_common_handlers(monkeypatch) + old_harness = CommandHarness(monkeypatch) + new_harness = CommandHarness(monkeypatch) + old_state = make_state() + new_state = make_state() + old_effects = record_comment_side_effects(old_harness.runtime) + old_decision = _legacy_decide_comment_command( + old_harness.runtime, + request, + classified, + actor_class="repo_user_principal", + commands_help=config_module.get_commands_help(), + ) + old_changed = _legacy_apply_ordinary_decision(old_harness.runtime, old_state, request, old_decision) + old_reactions = list(old_effects.reactions) + old_comments = list(old_effects.comments) + + new_effects = record_comment_side_effects(new_harness.runtime) + new_decision = comment_command_policy.decide_comment_command( + new_harness.runtime, + request, + classified, + actor_class="repo_user_principal", + commands_help=config_module.get_commands_help(), + ) + new_changed = comment_application.apply_comment_command( + new_harness.runtime, + new_state, + request, + dict(classified), + classify_issue_comment_actor=lambda current_request: "repo_user_principal", + ) + + assert _normalize_decision(new_decision) == _normalize_decision(old_decision), scenario_name + assert new_changed == old_changed, scenario_name + assert new_state == old_state, scenario_name + assert new_effects.comments == old_comments, scenario_name + assert new_effects.reactions == old_reactions, scenario_name + assert new_harness.runtime.drain_touched_items() == old_harness.runtime.drain_touched_items(), scenario_name diff --git a/tests/unit/reviewer_bot/test_comment_freshness_equivalence.py b/tests/unit/reviewer_bot/test_comment_freshness_equivalence.py new file mode 100644 index 000000000..a50c8b9ef --- /dev/null +++ b/tests/unit/reviewer_bot/test_comment_freshness_equivalence.py @@ -0,0 +1,177 @@ +import json +from pathlib import Path + +from scripts.reviewer_bot_core import comment_freshness_policy +from scripts.reviewer_bot_lib import comment_application, review_state +from tests.fixtures.comment_routing_harness import CommentRoutingHarness +from tests.fixtures.reviewer_bot import make_state + + +def _load_matrix() -> dict: + return json.loads( + Path("tests/fixtures/equivalence/comment_freshness/scenario_matrix.json").read_text( + encoding="utf-8" + ) + ) + + +def _legacy_record_conversation_freshness(state: dict, request) -> bool: + issue_number = request.issue_number + review_data = review_state.ensure_review_entry(state, issue_number, create=True) + if review_data is None: + return False + comment_author = request.comment_author + created_at = request.comment_created_at + semantic_key = request.comment_source_event_key or f"issue_comment:{request.comment_id}" + if request.issue_author and request.issue_author.lower() == comment_author.lower(): + return review_state.accept_channel_event( + review_data, + "contributor_comment", + semantic_key=semantic_key, + timestamp=created_at, + actor=comment_author, + ) + current_reviewer = review_data.get("current_reviewer") + if isinstance(current_reviewer, str) and current_reviewer.lower() == comment_author.lower(): + changed = review_state.accept_channel_event( + review_data, + "reviewer_comment", + semantic_key=semantic_key, + timestamp=created_at, + actor=comment_author, + ) + previous_activity = review_data.get("last_reviewer_activity") + previous_warning = review_data.get("transition_warning_sent") + previous_notice = review_data.get("transition_notice_sent_at") + review_state.record_reviewer_activity(review_data, created_at) + activity_changed = ( + previous_activity != review_data.get("last_reviewer_activity") + or previous_warning != review_data.get("transition_warning_sent") + or previous_notice != review_data.get("transition_notice_sent_at") + ) + return changed or activity_changed + return False + + +def test_comment_freshness_fixture_declares_exact_scenarios(): + matrix = _load_matrix() + + assert matrix["harness_id"] == "D1a comment freshness equivalence" + assert matrix["scenarios"] == [ + "contributor_plain_text_freshness", + "reviewer_plain_text_freshness", + "reviewer_activity_only_when_semantic_key_exists", + "non_contributor_non_reviewer_noop", + "command_plus_text_freshness", + ] + + +def test_comment_freshness_policy_decisions_match_frozen_branching_cases(monkeypatch): + harness = CommentRoutingHarness(monkeypatch) + review = review_state.ensure_review_entry(make_state(), 42, create=True) + assert review is not None + review["current_reviewer"] = "alice" + + contributor_request = harness.request( + issue_number=42, + is_pull_request=False, + issue_author="dana", + comment_author="dana", + comment_body="hello", + ) + reviewer_request = harness.request( + issue_number=42, + is_pull_request=False, + issue_author="dana", + comment_author="alice", + comment_body="hello", + ) + other_request = harness.request( + issue_number=42, + is_pull_request=False, + issue_author="dana", + comment_author="zoe", + comment_body="hello", + ) + + assert comment_freshness_policy.decide_comment_freshness(review, contributor_request).channel_name == "contributor_comment" + assert comment_freshness_policy.decide_comment_freshness(review, reviewer_request).channel_name == "reviewer_comment" + assert comment_freshness_policy.decide_comment_freshness(review, reviewer_request).update_reviewer_activity is True + assert comment_freshness_policy.decide_comment_freshness(review, other_request).kind == "noop" + + +def test_comment_freshness_equivalence_matches_legacy_mutation_and_activity_behavior(monkeypatch): + matrix = _load_matrix() + harness = CommentRoutingHarness(monkeypatch) + + scenarios = [ + ( + "contributor_plain_text_freshness", + lambda state: harness.request(issue_number=42, is_pull_request=False, issue_author="dana", comment_author="dana", comment_body="hello"), + lambda review: None, + ), + ( + "reviewer_plain_text_freshness", + lambda state: harness.request(issue_number=42, is_pull_request=False, issue_author="dana", comment_author="alice", comment_body="hello", comment_created_at="2026-03-17T10:00:00Z"), + lambda review: review.__setitem__("current_reviewer", "alice"), + ), + ( + "reviewer_activity_only_when_semantic_key_exists", + lambda state: harness.request(issue_number=42, is_pull_request=False, issue_author="dana", comment_author="alice", comment_body="hello", comment_id=100, comment_created_at="2026-03-17T10:00:00Z", comment_source_event_key="issue_comment:100"), + lambda review: ( + review.__setitem__("current_reviewer", "alice"), + review.__setitem__("last_reviewer_activity", "2026-03-17T09:00:00Z"), + review.__setitem__("transition_warning_sent", "2026-03-18T00:00:00Z"), + review.__setitem__("transition_notice_sent_at", "2026-03-25T00:00:00Z"), + review_state.accept_channel_event(review, "reviewer_comment", semantic_key="issue_comment:100", timestamp="2026-03-17T09:00:00Z", actor="alice"), + ), + ), + ( + "non_contributor_non_reviewer_noop", + lambda state: harness.request(issue_number=42, is_pull_request=False, issue_author="dana", comment_author="zoe", comment_body="hello"), + lambda review: review.__setitem__("current_reviewer", "alice"), + ), + ( + "command_plus_text_freshness", + lambda state: harness.request(issue_number=42, is_pull_request=False, issue_author="dana", comment_author="dana", comment_body="hello\n@guidelines-bot /queue"), + lambda review: None, + ), + ] + + assert matrix["scenarios"] == [name for name, _req, _prep in scenarios] + + for scenario_name, make_request, prepare_review in scenarios: + legacy_state = make_state() + new_state = make_state() + legacy_review = review_state.ensure_review_entry(legacy_state, 42, create=True) + new_review = review_state.ensure_review_entry(new_state, 42, create=True) + assert legacy_review is not None and new_review is not None + if prepare_review is not None: + prepare_review(legacy_review) + prepare_review(new_review) + request = make_request(new_state) + + legacy_changed = _legacy_record_conversation_freshness(legacy_state, request) + new_changed = comment_application.record_conversation_freshness(harness.runtime, new_state, request) + + assert new_changed == legacy_changed, scenario_name + assert new_state == legacy_state, scenario_name + + +def test_i2_comment_application_routes_using_string_kinds(): + assert comment_application._route_comment_application( + {"comment_class": "plain_text", "command_count": 0}, + comment_id=10, + ) == "freshness_only" + assert comment_application._route_comment_application( + {"comment_class": "command_only", "command_count": 1}, + comment_id=10, + ) == "command_only" + assert comment_application._route_comment_application( + {"comment_class": "command_plus_text", "command_count": 1}, + comment_id=10, + ) == "both" + assert comment_application._route_comment_application( + {"comment_class": "plain_text", "command_count": 0}, + comment_id=0, + ) == "noop" diff --git a/tests/unit/reviewer_bot/test_comment_routing.py b/tests/unit/reviewer_bot/test_comment_routing.py new file mode 100644 index 000000000..1439a59ae --- /dev/null +++ b/tests/unit/reviewer_bot/test_comment_routing.py @@ -0,0 +1,155 @@ +from types import SimpleNamespace + +import pytest + +from scripts.reviewer_bot_core.comment_routing_policy import PrCommentRouterOutcome +from scripts.reviewer_bot_lib import comment_application, comment_routing, review_state +from scripts.reviewer_bot_lib.context import CommentEventRequest +from tests.fixtures.comment_routing_harness import CommentRoutingHarness +from tests.fixtures.reviewer_bot import make_state + + +def test_record_conversation_freshness_returns_true_when_only_reviewer_activity_changes(monkeypatch): + harness = CommentRoutingHarness(monkeypatch) + assert harness.handlers is harness.runtime.handlers + state = make_state() + review = review_state.ensure_review_entry(state, 42, create=True) + assert review is not None + review["current_reviewer"] = "alice" + review["last_reviewer_activity"] = "2026-03-17T09:00:00Z" + review["transition_warning_sent"] = "2026-03-18T00:00:00Z" + review["transition_notice_sent_at"] = "2026-03-25T00:00:00Z" + review_state.accept_channel_event( + review, + "reviewer_comment", + semantic_key="issue_comment:100", + timestamp="2026-03-17T09:00:00Z", + actor="alice", + ) + request = harness.request( + issue_number=42, + is_pull_request=False, + issue_author="dana", + comment_id=100, + comment_author="alice", + comment_body="hello", + comment_created_at="2026-03-17T10:00:00Z", + comment_source_event_key="issue_comment:100", + ) + + changed = comment_application.record_conversation_freshness( + harness.runtime, + state, + request, + ) + + assert changed is True + assert review["last_reviewer_activity"] == "2026-03-17T10:00:00Z" + assert review["transition_warning_sent"] is None + assert review["transition_notice_sent_at"] is None + + +@pytest.mark.parametrize( + ("env", "expected"), + [ + ({"COMMENT_USER_TYPE": "Bot", "COMMENT_AUTHOR": "reviewer-bot"}, "bot_account"), + ({"COMMENT_INSTALLATION_ID": "12345"}, "github_app_or_other_automation"), + ({"COMMENT_USER_TYPE": "User", "COMMENT_AUTHOR": "alice"}, "repo_user_principal"), + ({"COMMENT_AUTHOR": "mystery", "COMMENT_USER_TYPE": ""}, "unknown_actor"), + ], +) +def test_classify_issue_comment_actor(monkeypatch, env, expected): + harness = CommentRoutingHarness(monkeypatch) + request = harness.request( + issue_number=42, + is_pull_request=False, + comment_author=env.get("COMMENT_AUTHOR", ""), + comment_body="hello", + comment_user_type=env.get("COMMENT_USER_TYPE", ""), + ) + request = CommentEventRequest( + **{ + **request.__dict__, + "comment_installation_id": env.get("COMMENT_INSTALLATION_ID", ""), + } + ) + assert comment_routing.classify_issue_comment_actor(request) == expected + + +def test_classify_comment_payload_distinguishes_command_plus_text(): + payload = comment_routing.classify_comment_payload( + SimpleNamespace( + BOT_MENTION="@guidelines-bot", + adapters=SimpleNamespace( + commands=SimpleNamespace( + strip_code_blocks=lambda body: body, + parse_command=lambda body: ("queue", []), + ) + ), + ), + "hello\n@guidelines-bot /queue", + ) + assert payload["comment_class"] == "command_plus_text" + assert payload["has_non_command_text"] is True + + +def test_route_issue_comment_trust_allows_only_same_repo_repo_user_principal(monkeypatch): + harness = CommentRoutingHarness(monkeypatch) + request = harness.request( + issue_number=42, + is_pull_request=True, + issue_author="carol", + comment_author="alice", + comment_body="hello", + ) + pr_admission = harness.pr_admission( + github_repository="rustfoundation/safety-critical-rust-coding-guidelines", + pr_head_full_name="rustfoundation/safety-critical-rust-coding-guidelines", + pr_author="carol", + ) + assert comment_routing.route_issue_comment_trust(harness.runtime, 42, request, pr_admission) == PrCommentRouterOutcome.TRUSTED_DIRECT + + +def test_route_issue_comment_trust_fails_closed_for_ambiguous_same_repo(monkeypatch): + harness = CommentRoutingHarness(monkeypatch) + request = harness.request( + issue_number=42, + is_pull_request=True, + issue_author="carol", + comment_author="alice", + comment_body="hello", + comment_user_type="", + ) + pr_admission = harness.pr_admission( + github_repository="rustfoundation/safety-critical-rust-coding-guidelines", + pr_head_full_name="rustfoundation/safety-critical-rust-coding-guidelines", + pr_author="carol", + ) + with pytest.raises(RuntimeError, match="Ambiguous same-repo PR comment trust posture"): + comment_routing.route_issue_comment_trust(harness.runtime, 42, request, pr_admission) + + +def test_comment_routing_module_delegates_routing_and_classification_to_core_policy(): + with open("scripts/reviewer_bot_lib/comment_routing.py", encoding="utf-8") as handle: + module_text = handle.read() + + assert "from scripts.reviewer_bot_core import comment_routing_policy" in module_text + assert "return comment_routing_policy.classify_issue_comment_actor(request)" in module_text + assert "return comment_routing_policy.classify_comment_payload(bot.BOT_MENTION, normalized, parsed)" in module_text + assert "return comment_routing_policy.route_issue_comment_trust(" in module_text + + +def test_comment_application_delegates_freshness_decision_to_core_policy(): + with open("scripts/reviewer_bot_lib/comment_application.py", encoding="utf-8") as handle: + module_text = handle.read() + + assert "comment_freshness_policy" in module_text + assert "decision = comment_freshness_policy.decide_comment_freshness(review_data, request)" in module_text + + +def test_k2_comment_routing_entrypoints_use_narrow_comment_runtime_protocol(): + with open("scripts/reviewer_bot_lib/comment_routing.py", encoding="utf-8") as handle: + module_text = handle.read() + + assert "CommentRoutingRuntimeContext" in module_text + assert "bot: CommentRoutingRuntimeContext" in module_text diff --git a/tests/unit/reviewer_bot/test_comment_routing_equivalence.py b/tests/unit/reviewer_bot/test_comment_routing_equivalence.py new file mode 100644 index 000000000..9b2791008 --- /dev/null +++ b/tests/unit/reviewer_bot/test_comment_routing_equivalence.py @@ -0,0 +1,220 @@ +import json +from pathlib import Path +from types import SimpleNamespace + +from scripts.reviewer_bot_core import comment_routing_policy +from scripts.reviewer_bot_lib import comment_routing +from tests.fixtures.comment_routing_harness import CommentRoutingHarness + + +def _load_fixture() -> dict: + return json.loads( + Path("tests/fixtures/equivalence/comment_routing/route_outcomes.json").read_text( + encoding="utf-8" + ) + ) + + +def _legacy_comment_line_is_command(bot_mention: str, line: str) -> bool: + return comment_routing_policy.comment_line_is_command(bot_mention, line) + + +def _legacy_classify_comment_payload(bot_mention: str, normalized_body: str, parsed_command) -> dict: + if not normalized_body: + return { + "comment_class": "empty_or_whitespace", + "has_non_command_text": False, + "command_count": 0, + "command": None, + "args": [], + "normalized_body": normalized_body, + } + lines = [line for line in normalized_body.splitlines() if line.strip()] + command_lines = [line for line in lines if _legacy_comment_line_is_command(bot_mention, line)] + non_command_lines = [line for line in lines if not _legacy_comment_line_is_command(bot_mention, line)] + command = None + args = [] + if parsed_command: + command, args = parsed_command + if command_lines and not non_command_lines: + comment_class = "command_only" + elif command_lines and non_command_lines: + comment_class = "command_plus_text" + else: + comment_class = "plain_text" + return { + "comment_class": comment_class, + "has_non_command_text": bool(non_command_lines), + "command_count": len(command_lines), + "command": command, + "args": args, + "normalized_body": normalized_body, + } + + +def _legacy_classify_issue_comment_actor(request) -> str: + comment_user_type = request.comment_user_type + comment_author = request.comment_author.strip() + sender_type = request.comment_sender_type + installation_id = request.comment_installation_id + via_github_app = request.comment_performed_via_github_app + if comment_user_type == "Bot" or comment_author.endswith("[bot]"): + return "bot_account" + if installation_id or via_github_app or (sender_type and sender_type not in {"User", "Bot"}): + return "github_app_or_other_automation" + if comment_user_type == "User" and comment_author and not comment_author.endswith("[bot]") and not installation_id and not via_github_app: + return "repo_user_principal" + return "unknown_actor" + + +def test_comment_routing_equivalence_fixture_exists(): + fixture = _load_fixture() + + assert fixture["harness_id"] == "C3a trust-routing and comment classification equivalence" + assert fixture["route_scenarios"] == [ + "same_repo_trusted_pr_comment", + "cross_repo_deferred_pr_comment", + "automation_or_self_noop_pr_comment", + "issue_comment_direct", + ] + + +def test_comment_payload_classification_equivalence_matches_frozen_outcomes(): + fixture = _load_fixture() + bot = SimpleNamespace( + BOT_MENTION="@guidelines-bot", + adapters=SimpleNamespace( + commands=SimpleNamespace( + strip_code_blocks=lambda body: body, + parse_command=lambda body: ("queue", []) if "/queue" in body else None, + ) + ), + ) + normalized = "hello\n@guidelines-bot /queue" + parsed = ("queue", []) + + assert "command_plus_text" in fixture["payload_scenarios"] + assert comment_routing.classify_comment_payload(bot, normalized) == _legacy_classify_comment_payload( + bot.BOT_MENTION, + normalized, + parsed, + ) + assert comment_routing_policy.classify_comment_payload(bot.BOT_MENTION, normalized, parsed) == _legacy_classify_comment_payload( + bot.BOT_MENTION, + normalized, + parsed, + ) + + +def test_route_outcome_equivalence_covers_trusted_deferred_noop_and_issue_direct(monkeypatch): + fixture = _load_fixture() + harness = CommentRoutingHarness(monkeypatch) + + scenarios = [ + { + "name": "same_repo_trusted_pr_comment", + "request": harness.request( + issue_number=42, + is_pull_request=True, + issue_author="carol", + comment_author="alice", + comment_body="hello", + comment_user_type="User", + ), + "trust_context": harness.trust_context( + route_outcome=comment_routing_policy.PrCommentRouterOutcome.TRUSTED_DIRECT, + github_repository="rustfoundation/safety-critical-rust-coding-guidelines", + pr_head_full_name="rustfoundation/safety-critical-rust-coding-guidelines", + pr_author="carol", + ), + }, + { + "name": "cross_repo_deferred_pr_comment", + "request": harness.request( + issue_number=42, + is_pull_request=True, + issue_author="carol", + comment_author="alice", + comment_body="hello", + comment_user_type="User", + ), + "trust_context": harness.trust_context( + route_outcome=comment_routing_policy.PrCommentRouterOutcome.DEFERRED_RECONCILE, + github_repository="rustfoundation/safety-critical-rust-coding-guidelines", + pr_head_full_name="fork/example", + pr_author="carol", + ), + }, + { + "name": "automation_or_self_noop_pr_comment", + "request": harness.request( + issue_number=42, + is_pull_request=True, + issue_author="carol", + comment_author="dependabot[bot]", + comment_body="hello", + comment_user_type="Bot", + ), + "trust_context": harness.trust_context( + route_outcome=comment_routing_policy.PrCommentRouterOutcome.SAFE_NOOP, + github_repository="rustfoundation/safety-critical-rust-coding-guidelines", + pr_head_full_name="rustfoundation/safety-critical-rust-coding-guidelines", + pr_author="carol", + ), + }, + { + "name": "issue_comment_direct", + "request": harness.request( + issue_number=42, + is_pull_request=False, + issue_author="carol", + comment_author="alice", + comment_body="hello", + comment_user_type="User", + ), + "trust_context": harness.trust_context( + github_repository="rustfoundation/safety-critical-rust-coding-guidelines", + ), + }, + ] + + assert fixture["route_scenarios"] == [scenario["name"] for scenario in scenarios] + + for scenario in scenarios: + actor_class = comment_routing_policy.classify_issue_comment_actor(scenario["request"]) + if scenario["request"].is_pull_request: + new_target = comment_routing_policy.classify_pr_comment_processing_target( + scenario["request"], + scenario["trust_context"], + actor_class=actor_class, + is_self_comment=False, + ) + if scenario["name"] == "same_repo_trusted_pr_comment": + assert new_target == comment_routing_policy.ProcessingTarget.PR_TRUSTED_DIRECT + if scenario["name"] == "cross_repo_deferred_pr_comment": + assert new_target == comment_routing_policy.PrCommentRouterOutcome.DEFERRED_RECONCILE + if scenario["name"] == "automation_or_self_noop_pr_comment": + assert new_target == comment_routing_policy.PrCommentRouterOutcome.SAFE_NOOP + assert comment_routing_policy.route_issue_comment_trust( + scenario["request"], + scenario["trust_context"], + processing_target=new_target, + ) == ( + comment_routing_policy.PrCommentRouterOutcome.TRUSTED_DIRECT + if new_target == comment_routing_policy.ProcessingTarget.PR_TRUSTED_DIRECT + else new_target + ) + else: + assert comment_routing_policy.route_issue_comment_trust( + scenario["request"], + scenario["trust_context"], + processing_target=None, + ) == comment_routing_policy.ProcessingTarget.ISSUE_DIRECT + + +def test_pr_comment_observer_helper_is_removed_after_router_cutover(monkeypatch): + fixture = _load_fixture() + module_text = Path("scripts/reviewer_bot_lib/comment_routing.py").read_text(encoding="utf-8") + + assert "deferred_observer_payload" in fixture["payload_scenarios"] + assert "build_pr_comment_observer_payload" not in module_text diff --git a/tests/unit/reviewer_bot/test_core_review_state_types.py b/tests/unit/reviewer_bot/test_core_review_state_types.py new file mode 100644 index 000000000..46050c224 --- /dev/null +++ b/tests/unit/reviewer_bot/test_core_review_state_types.py @@ -0,0 +1,85 @@ +from dataclasses import fields, is_dataclass + +from scripts.reviewer_bot_core import review_state_types + + +def test_core_review_state_types_are_limited_to_c1_mutation_scope(): + assert is_dataclass(review_state_types.AcceptedChannelRecord) + assert is_dataclass(review_state_types.DismissalAcceptedRecord) + assert is_dataclass(review_state_types.ReviewChannelState) + assert is_dataclass(review_state_types.ReviewEntryState) + + assert [field.name for field in fields(review_state_types.ReviewChannelState)] == [ + "accepted", + "seen_keys", + ] + assert [field.name for field in fields(review_state_types.ReviewEntryState)] == [ + "skipped", + "current_reviewer", + "cycle_started_at", + "active_cycle_started_at", + "assigned_at", + "active_head_sha", + "last_reviewer_activity", + "transition_warning_sent", + "transition_notice_sent_at", + "assignment_method", + "review_completed_at", + "review_completed_by", + "review_completion_source", + "mandatory_approver_required", + "mandatory_approver_label_applied_at", + "mandatory_approver_pinged_at", + "mandatory_approver_satisfied_by", + "mandatory_approver_satisfied_at", + "overdue_anchor", + "reviewer_comment", + "reviewer_review", + "contributor_comment", + "contributor_revision", + "review_dismissal", + "current_cycle_completion", + "current_cycle_write_approval", + ] + + +def test_core_review_state_types_preserve_current_sparse_and_precedence_shapes(): + entry = review_state_types.ReviewEntryState() + + assert entry.skipped == [] + assert entry.mandatory_approver_required is False + assert entry.current_cycle_completion == {} + assert entry.current_cycle_write_approval == {} + assert entry.reviewer_comment.seen_keys == [] + + accepted = review_state_types.AcceptedChannelRecord( + semantic_key="issue_comment:10", + timestamp="2026-03-17T10:00:00Z", + actor="alice", + reviewed_head_sha="head-1", + source_precedence=2, + payload={"kind": "comment"}, + ) + dismissed = review_state_types.DismissalAcceptedRecord( + semantic_key="pull_request_review_dismissed:11", + timestamp="2026-03-17T11:00:00Z", + ) + + entry.reviewer_comment.accepted = accepted + entry.review_dismissal.accepted = dismissed + + assert entry.reviewer_comment.accepted.source_precedence == 2 + assert entry.review_dismissal.accepted.semantic_key == "pull_request_review_dismissed:11" + + +def test_core_review_state_types_document_current_persisted_mapping_scope(): + module_doc = review_state_types.__doc__ or "" + entry_doc = review_state_types.ReviewEntryState.__doc__ or "" + + assert "review-entry initialization and defaulting" in module_doc + assert "channel-event acceptance and semantic-key tracking" in module_doc + assert "reviewer activity updates" in module_doc + assert "completion marking" in module_doc + assert "cycle-boundary behavior" in module_doc + assert "do not model deferred-gap diagnosis" in module_doc or "do not model" in module_doc + assert "persisted review entry fields" in entry_doc diff --git a/tests/unit/reviewer_bot/test_core_state_adapters.py b/tests/unit/reviewer_bot/test_core_state_adapters.py new file mode 100644 index 000000000..bffeb5633 --- /dev/null +++ b/tests/unit/reviewer_bot/test_core_state_adapters.py @@ -0,0 +1,110 @@ +from scripts.reviewer_bot_core import state_adapters +from tests.fixtures.reviewer_bot import make_state + + +def test_review_entry_adapter_round_trips_full_current_mutation_shape(): + persisted = { + "skipped": ["alice"], + "current_reviewer": "bob", + "cycle_started_at": "2026-03-17T09:00:00Z", + "active_cycle_started_at": "2026-03-17T09:00:00Z", + "assigned_at": "2026-03-17T09:00:00Z", + "active_head_sha": "head-1", + "last_reviewer_activity": "2026-03-17T10:00:00Z", + "transition_warning_sent": "2026-03-18T00:00:00Z", + "transition_notice_sent_at": "2026-03-19T00:00:00Z", + "assignment_method": "round-robin", + "review_completed_at": "2026-03-20T00:00:00Z", + "review_completed_by": "bob", + "review_completion_source": "unit-test", + "mandatory_approver_required": True, + "mandatory_approver_label_applied_at": "2026-03-20T01:00:00Z", + "mandatory_approver_pinged_at": "2026-03-20T02:00:00Z", + "mandatory_approver_satisfied_by": "carol", + "mandatory_approver_satisfied_at": "2026-03-20T03:00:00Z", + "overdue_anchor": None, + "reviewer_comment": { + "accepted": { + "semantic_key": "issue_comment:10", + "timestamp": "2026-03-17T10:00:00Z", + "actor": "alice", + "reviewed_head_sha": None, + "source_precedence": 1, + "payload": {"kind": "comment"}, + }, + "seen_keys": ["issue_comment:10"], + }, + "reviewer_review": { + "accepted": { + "semantic_key": "pull_request_review:11", + "timestamp": "2026-03-17T11:00:00Z", + "actor": "bob", + "reviewed_head_sha": "head-1", + "source_precedence": 2, + "payload": {"kind": "review"}, + }, + "seen_keys": ["pull_request_review:11"], + }, + "contributor_comment": {"accepted": None, "seen_keys": []}, + "contributor_revision": {"accepted": None, "seen_keys": []}, + "review_dismissal": { + "accepted": { + "semantic_key": "pull_request_review_dismissed:12", + "timestamp": "2026-03-18T11:00:00Z", + }, + "seen_keys": ["pull_request_review_dismissed:12"], + }, + "current_cycle_completion": { + "completed": True, + "completed_at": "2026-03-20T00:00:00Z", + "source": "unit-test", + "reviewer": "bob", + }, + "current_cycle_write_approval": {"approved": False}, + } + + adapted = state_adapters.review_entry_from_persisted(persisted) + + assert adapted is not None + assert state_adapters.review_entry_to_persisted(adapted) == persisted + + +def test_review_entry_adapter_matches_current_sparse_upgrade_semantics(): + adapted = state_adapters.review_entry_from_persisted(["alice", "bob"]) + + assert adapted is not None + assert state_adapters.review_entry_to_persisted(adapted) == { + "skipped": ["alice", "bob"], + "current_reviewer": None, + "cycle_started_at": None, + "active_cycle_started_at": None, + "assigned_at": None, + "active_head_sha": None, + "last_reviewer_activity": None, + "transition_warning_sent": None, + "transition_notice_sent_at": None, + "assignment_method": None, + "review_completed_at": None, + "review_completed_by": None, + "review_completion_source": None, + "mandatory_approver_required": False, + "mandatory_approver_label_applied_at": None, + "mandatory_approver_pinged_at": None, + "mandatory_approver_satisfied_by": None, + "mandatory_approver_satisfied_at": None, + "overdue_anchor": None, + "reviewer_comment": {"accepted": None, "seen_keys": []}, + "reviewer_review": {"accepted": None, "seen_keys": []}, + "contributor_comment": {"accepted": None, "seen_keys": []}, + "contributor_revision": {"accepted": None, "seen_keys": []}, + "review_dismissal": {"accepted": None, "seen_keys": []}, + "current_cycle_completion": {}, + "current_cycle_write_approval": {}, + } + + +def test_review_entry_adapter_returns_none_for_unusable_persisted_shape(): + state = make_state() + state["active_reviews"]["42"] = "broken" + + assert state_adapters.review_entry_from_persisted(state["active_reviews"]["42"]) is None diff --git a/tests/unit/reviewer_bot/test_deferred_gap_bookkeeping.py b/tests/unit/reviewer_bot/test_deferred_gap_bookkeeping.py new file mode 100644 index 000000000..d03568243 --- /dev/null +++ b/tests/unit/reviewer_bot/test_deferred_gap_bookkeeping.py @@ -0,0 +1,69 @@ +from scripts.reviewer_bot_lib import deferred_gap_bookkeeping, review_state +from tests.fixtures.fake_runtime import FakeReviewerBotRuntime +from tests.fixtures.reviewer_bot import make_state + + +def test_ensure_source_event_key_creates_and_updates_deferred_gap_payloads(monkeypatch): + review = review_state.ensure_review_entry(make_state(), 42, create=True) + assert review is not None + + deferred_gap_bookkeeping._ensure_source_event_key(review, "issue_comment:210", {"reason": "artifact_missing"}) + deferred_gap_bookkeeping._ensure_source_event_key(review, "issue_comment:210", {"reason": "reconcile_failed_closed"}) + + assert review["sidecars"]["deferred_gaps"]["issue_comment:210"] == { + "source_event_key": "issue_comment:210", + "reason": "reconcile_failed_closed", + } + + +def test_bookkeeping_owner_marks_clears_and_tracks_reconciled_source_events(monkeypatch): + review = review_state.ensure_review_entry(make_state(), 42, create=True) + assert review is not None + review["sidecars"]["deferred_gaps"]["issue_comment:210"] = {"reason": "artifact_missing"} + + assert deferred_gap_bookkeeping._clear_source_event_key(review, "issue_comment:210") is True + assert deferred_gap_bookkeeping._mark_reconciled_source_event(review, "issue_comment:210") is True + assert deferred_gap_bookkeeping._mark_reconciled_source_event(review, "issue_comment:210") is False + assert deferred_gap_bookkeeping._was_reconciled_source_event(review, "issue_comment:210") is True + assert review["sidecars"]["deferred_gaps"] == {} + assert review["sidecars"]["reconciled_source_events"] == { + "issue_comment:210": { + "source_event_key": "issue_comment:210", + "reconciled_at": None, + } + } + + +def test_update_deferred_gap_preserves_first_noted_and_refreshes_last_checked(monkeypatch): + runtime = FakeReviewerBotRuntime(monkeypatch) + review = review_state.ensure_review_entry(make_state(), 42, create=True) + assert review is not None + review["sidecars"]["deferred_gaps"]["issue_comment:210"] = { + "first_noted_at": "2026-03-17T09:00:00+00:00", + "last_checked_at": "2026-03-17T09:00:00+00:00", + } + runtime.clock.now = lambda: runtime.datetime(2026, 3, 18, tzinfo=runtime.timezone.utc) + + changed = deferred_gap_bookkeeping._update_deferred_gap( + runtime, + review, + { + "source_event_key": "issue_comment:210", + "source_event_name": "issue_comment", + "source_event_action": "created", + "pr_number": 42, + "source_created_at": "2026-03-17T10:00:00Z", + "source_run_id": 700, + "source_run_attempt": 1, + "source_workflow_file": ".github/workflows/reviewer-bot-pr-comment-observer.yml", + "source_artifact_name": "reviewer-bot-comment-context-700-attempt-1", + }, + "awaiting_observer_run", + "Trusted sweeper diagnostics for issue_comment:210.", + failure_kind="server_error", + ) + + assert changed is True + assert review["sidecars"]["deferred_gaps"]["issue_comment:210"]["first_noted_at"] == "2026-03-17T09:00:00+00:00" + assert review["sidecars"]["deferred_gaps"]["issue_comment:210"]["last_checked_at"] == "2026-03-18T00:00:00+00:00" + assert review["sidecars"]["deferred_gaps"]["issue_comment:210"]["failure_kind"] == "server_error" diff --git a/tests/unit/reviewer_bot/test_deferred_gap_diagnosis_equivalence.py b/tests/unit/reviewer_bot/test_deferred_gap_diagnosis_equivalence.py new file mode 100644 index 000000000..174018d30 --- /dev/null +++ b/tests/unit/reviewer_bot/test_deferred_gap_diagnosis_equivalence.py @@ -0,0 +1,65 @@ +import json +from pathlib import Path + +from scripts.reviewer_bot_core import deferred_gap_diagnosis + + +def _load_matrix() -> dict: + return json.loads( + Path("tests/fixtures/equivalence/deferred_gap_diagnosis/vocabulary_matrix.json").read_text( + encoding="utf-8" + ) + ) + + +def test_deferred_gap_diagnosis_vocabulary_matrix_exists_and_lists_reason_sets(): + matrix = _load_matrix() + + assert matrix["harness_id"] == "C5a deferred gap diagnosis vocabulary matrix" + assert matrix["reason_values"] == [ + "artifact_missing", + "artifact_invalid", + "observer_state_unknown", + "reconcile_failed_closed", + ] + assert "artifact_scan_unavailable" in matrix["diagnostic_reason_values"] + assert "exact_artifact_missing" in matrix["diagnostic_reason_values"] + + +def test_deferred_gap_diagnosis_matrix_freezes_visible_review_repair_categories_and_ownership(): + matrix = _load_matrix() + + assert matrix["visible_review_repair_categories"] == [ + "review_submission_repair", + "review_dismissal_repair", + "no_repair_recommended", + ] + assert matrix["ownership_decision"] == { + "diagnosis": "recommends", + "sweeper": "mutates", + } + + +def test_deferred_gap_diagnosis_core_produces_frozen_reason_and_recommendation_outputs(): + run_reason = deferred_gap_diagnosis.observer_run_reason_from_details( + {"status": "waiting", "conclusion": None, "name": "approval_pending"}, + {"status": "waiting", "conclusion": None, "name": "approval_pending"}, + ) + artifact_reason = deferred_gap_diagnosis.classify_artifact_gap_reason( + {"artifact_inspection_complete": True}, + ) + recommendation = deferred_gap_diagnosis.recommend_visible_review_repair( + {"current_reviewer": "alice"}, + { + "id": 202, + "submitted_at": "2026-03-25T11:00:00Z", + "commit_id": "head-1", + "user": {"login": "alice"}, + }, + "pull_request_review:202", + current_cycle_boundary=deferred_gap_diagnosis.parse_timestamp("2026-03-17T09:00:00Z"), + ) + + assert run_reason == "awaiting_observer_approval" + assert artifact_reason == "artifact_missing" + assert recommendation == ("alice", "2026-03-25T11:00:00Z", "head-1") diff --git a/tests/unit/reviewer_bot/test_fixture_contracts.py b/tests/unit/reviewer_bot/test_fixture_contracts.py new file mode 100644 index 000000000..8fc20cc90 --- /dev/null +++ b/tests/unit/reviewer_bot/test_fixture_contracts.py @@ -0,0 +1,155 @@ +from datetime import datetime, timezone + +from tests.fixtures.fake_clock import FakeClock +from tests.fixtures.fake_jitter import DeterministicJitter +from tests.fixtures.fake_sleeper import RecordingSleeper +from tests.fixtures.fake_uuid import FixedUuidSource +from tests.fixtures.focused_fake_services import ( + ArtifactDownloadTransportStub, + GitHubStub, + GraphQLTransportStub, + LockStub, + RestTransportStub, + StateStoreStub, + WorkflowBehaviorStub, +) +from tests.fixtures.recording_logger import RecordingLogger +from tests.fixtures.reviewer_bot_fakes import RouteGitHubApi, github_result + + +def test_shared_github_fixture_exports_core_transport_helpers(): + routes = RouteGitHubApi() + result = github_result(200, {"ok": True}) + + assert routes is not None + assert result.ok is True + + +def test_shared_github_fixture_smoke_routes_simple_request(): + routes = RouteGitHubApi().add_request("GET", "pulls/42", status_code=200, payload={"head": {"sha": "head-1"}}) + + result = routes.github_api_request("GET", "pulls/42") + + assert result.payload == {"head": {"sha": "head-1"}} + + +def test_fake_clock_can_set_and_advance_time(): + clock = FakeClock(datetime(2026, 3, 17, 10, 0, tzinfo=timezone.utc)) + + assert clock.now() == datetime(2026, 3, 17, 10, 0, tzinfo=timezone.utc) + clock.advance(seconds=30) + assert clock.now() == datetime(2026, 3, 17, 10, 0, 30, tzinfo=timezone.utc) + + +def test_recording_sleeper_captures_sleep_calls(): + sleeper = RecordingSleeper() + + sleeper.sleep(1.5) + sleeper.sleep(2.0) + + assert sleeper.calls == [1.5, 2.0] + + +def test_deterministic_jitter_replays_configured_values(): + jitter = DeterministicJitter([0.5, 0.75]) + + assert jitter.uniform(0.1, 1.0) == 0.5 + assert jitter.uniform(0.1, 1.0) == 0.75 + assert jitter.uniform(0.1, 1.0) == 0.75 + assert jitter.calls == [(0.1, 1.0), (0.1, 1.0), (0.1, 1.0)] + + +def test_fixed_uuid_source_replays_values_and_records_issued_ids(): + source = FixedUuidSource(["uuid-1", "uuid-2"]) + + assert source.uuid4_hex() == "uuid-1" + assert source.uuid4_hex() == "uuid-2" + assert source.uuid4_hex() == "uuid-2" + assert source.issued == ["uuid-1", "uuid-2", "uuid-2"] + + +def test_recording_logger_captures_structured_events(): + logger = RecordingLogger() + + logger.event("warning", "retrying request", issue_number=42, retry_attempt=2) + + assert logger.records == [ + { + "level": "warning", + "message": "retrying request", + "fields": {"issue_number": 42, "retry_attempt": 2}, + } + ] + + +def test_graphql_transport_stub_replays_sequence_and_keeps_last_value(): + transport = GraphQLTransportStub() + transport.stub_sequence([{"data": {"viewer": {"login": "bot"}}}, {"data": {"viewer": {"login": "bot-2"}}}]) + + assert transport.query("https://api.github.com/graphql", query="q") == {"data": {"viewer": {"login": "bot"}}} + assert transport.query("https://api.github.com/graphql", query="q") == {"data": {"viewer": {"login": "bot-2"}}} + assert transport.query("https://api.github.com/graphql", query="q") == {"data": {"viewer": {"login": "bot-2"}}} + + +def test_rest_transport_stub_routes_repo_urls_through_github_stub(): + github = GitHubStub(RouteGitHubApi().add_request("GET", "issues/42", result=github_result(200, {"ok": True}))) + transport = RestTransportStub(github) + + response = transport.request("GET", "https://api.github.com/repos/rustfoundation/safety-critical-rust-coding-guidelines/issues/42") + + assert response.status_code == 200 + assert response.json() == {"ok": True} + assert transport.calls[0]["url"].endswith("issues/42") + + +def test_artifact_download_transport_stub_replays_sequence_and_raises_exceptions(): + transport = ArtifactDownloadTransportStub() + transport.stub_sequence([RuntimeError("timeout"), {"ok": True}]) + + try: + transport.download("https://example.com/artifact.zip") + except RuntimeError as exc: + assert "timeout" in str(exc) + else: + raise AssertionError("expected RuntimeError") + + assert transport.download("https://example.com/artifact.zip") == {"ok": True} + assert transport.download("https://example.com/artifact.zip") == {"ok": True} + + +def test_state_store_stub_records_load_and_save_calls(): + store = StateStoreStub() + store.stub_load(lambda *, fail_on_unavailable=False: {"active_reviews": {"42": {}}}) + store.stub_save(lambda state: True) + + assert store.load_state(fail_on_unavailable=True) == {"active_reviews": {"42": {}}} + assert store.save_state({"active_reviews": {"42": {"current_reviewer": "alice"}}}) is True + assert store.load_calls == [{"fail_on_unavailable": True}] + assert store.save_calls == [{"active_reviews": {"42": {"current_reviewer": "alice"}}}] + + +def test_lock_stub_records_acquire_release_refresh_calls(): + lock = LockStub() + lock.stub(acquire=lambda: "token", release=lambda: True, refresh=lambda: False) + + assert lock.acquire() == "token" + assert lock.refresh() is False + assert lock.release() is True + assert lock.calls == ["acquire", "refresh", "release"] + + +def test_workflow_behavior_stub_records_behavior_calls(): + workflow = WorkflowBehaviorStub() + workflow.stub_pass_until(lambda state: ({**state, "restored": True}, ["alice"])) + workflow.stub_sync_members(lambda state: ({**state, "synced": True}, ["bob"])) + workflow.stub_sync_status_labels(lambda state, issue_numbers: True) + + state = {"active_reviews": {}} + assert workflow.process_pass_until_expirations(state) == ({"active_reviews": {}, "restored": True}, ["alice"]) + assert workflow.sync_members_with_queue(state) == ({"active_reviews": {}, "synced": True}, ["bob"]) + assert workflow.sync_status_labels_for_items(state, [42, 99]) is True + assert [call["name"] for call in workflow.calls] == [ + "process_pass_until_expirations", + "sync_members_with_queue", + "sync_status_labels_for_items", + ] diff --git a/tests/unit/reviewer_bot/test_github_api.py b/tests/unit/reviewer_bot/test_github_api.py new file mode 100644 index 000000000..1dfc7a422 --- /dev/null +++ b/tests/unit/reviewer_bot/test_github_api.py @@ -0,0 +1,363 @@ +import pytest + +from scripts.reviewer_bot_lib import automation, github_api, guidance +from scripts.reviewer_bot_lib.config import LOCK_API_RETRY_LIMIT, GitHubApiResult +from tests.fixtures.fake_jitter import DeterministicJitter +from tests.fixtures.fake_runtime import FakeReviewerBotRuntime +from tests.fixtures.fake_sleeper import RecordingSleeper +from tests.fixtures.http_responses import FakeGitHubResponse +from tests.fixtures.recording_logger import RecordingLogger +from tests.fixtures.reviewer_bot_fakes import RouteGitHubApi, github_result + + +def _bot(monkeypatch, *, config=None, github=None, **overrides): + bot = FakeReviewerBotRuntime(monkeypatch, github=github) + bot.logger = RecordingLogger() + bot.sleeper = RecordingSleeper() + bot.jitter = DeterministicJitter(0.0) + bot.set_config_value("REPO_OWNER", "rustfoundation") + bot.set_config_value("REPO_NAME", "safety-critical-rust-coding-guidelines") + bot.set_config_value("GITHUB_TOKEN", "token") + for name, value in (config or {}).items(): + bot.set_config_value(name, value) + for key, value in overrides.items(): + setattr(bot, key, value) + return bot + + +def test_github_api_request_retries_idempotent_get_on_502(monkeypatch): + github = RouteGitHubApi().add_request_sequence( + "GET", + "issues/42", + [ + github_result(502, {"message": "bad gateway"}), + github_result(200, {"ok": True}), + ], + ) + bot = _bot(monkeypatch, github=github) + result = github_api.github_api_request(bot, "GET", "issues/42", retry_policy="idempotent_read") + + assert result.ok is True + assert result.payload == {"ok": True} + assert result.retry_attempts == 1 + assert result.failure_kind is None + assert bot.sleeper.calls == [2.0] + + +def test_github_api_request_retries_transport_exception_for_idempotent_get(monkeypatch): + github = RouteGitHubApi().add_request_sequence( + "GET", + "issues/42", + [github_api.requests.RequestException("timeout"), github_result(200, {"ok": True})], + ) + bot = _bot(monkeypatch, github=github) + result = github_api.github_api_request(bot, "GET", "issues/42", retry_policy="idempotent_read") + + assert result.ok is True + assert result.payload == {"ok": True} + assert result.retry_attempts == 1 + assert bot.sleeper.calls == [2.0] + + +def test_github_api_request_classifies_not_found_without_retry(monkeypatch): + github = RouteGitHubApi().add_request("GET", "issues/42", result=github_result(404, {"message": "missing"})) + bot = _bot(monkeypatch, github=github) + result = github_api.github_api_request(bot, "GET", "issues/42", retry_policy="idempotent_read", suppress_error_log=True) + + assert result.ok is False + assert result.failure_kind == "not_found" + assert result.retry_attempts == 0 + assert github.requested_endpoints() == ["issues/42"] + + +def test_github_api_request_classifies_forbidden_without_retry(monkeypatch): + github = RouteGitHubApi().add_request("GET", "issues/42", result=github_result(403, {"message": "forbidden"})) + bot = _bot(monkeypatch, github=github) + result = github_api.github_api_request(bot, "GET", "issues/42", retry_policy="idempotent_read", suppress_error_log=True) + + assert result.ok is False + assert result.failure_kind == "forbidden" + assert result.retry_attempts == 0 + assert github.requested_endpoints() == ["issues/42"] + + +def test_github_graphql_request_retries_idempotent_query_on_502(monkeypatch): + bot = _bot(monkeypatch) + bot.graphql_transport.stub_sequence( + [ + FakeGitHubResponse(502, {"message": "bad gateway"}, "bad gateway"), + FakeGitHubResponse(200, {"data": {"viewer": {"login": "bot"}}}, "ok"), + ] + ) + result = github_api.github_graphql_request( + bot, + "query { viewer { login } }", + retry_policy="idempotent_read", + ) + + assert result.ok is True + assert result.payload == {"data": {"viewer": {"login": "bot"}}} + assert result.retry_attempts == 1 + + +def test_github_api_request_retries_rate_limit_then_succeeds(monkeypatch): + github = RouteGitHubApi().add_request_sequence( + "GET", + "issues/42", + [ + github_result(429, {"message": "slow down"}), + github_result(200, {"ok": True}), + ], + ) + result = github_api.github_api_request(_bot(monkeypatch, github=github), "GET", "issues/42", retry_policy="idempotent_read") + + assert result.ok is True + assert result.retry_attempts == 1 + + +def test_github_api_request_reports_retry_exhaustion_on_repeated_429(monkeypatch): + github = RouteGitHubApi().add_request_sequence( + "GET", + "issues/42", + [github_result(429, {"message": "slow down"})] * (LOCK_API_RETRY_LIMIT + 1), + ) + result = github_api.github_api_request(_bot(monkeypatch, github=github), "GET", "issues/42", retry_policy="idempotent_read", suppress_error_log=True) + + assert result.ok is False + assert result.failure_kind == "rate_limited" + assert result.retry_attempts == LOCK_API_RETRY_LIMIT + + +def test_github_graphql_request_reports_invalid_payload(monkeypatch): + bot = _bot(monkeypatch) + bot.graphql_transport.stub_sequence([FakeGitHubResponse(200, ValueError("bad json"), "bad json")]) + + result = github_api.github_graphql_request(bot, "query { viewer { login } }") + + assert result.ok is False + assert result.failure_kind == "invalid_payload" + + +def test_github_graphql_request_reports_graphql_errors(monkeypatch): + bot = _bot(monkeypatch) + bot.graphql_transport.stub_sequence([FakeGitHubResponse(200, {"errors": [{"message": "boom"}]}, "boom")]) + + result = github_api.github_graphql_request(bot, "query { viewer { login } }") + + assert result.ok is False + assert result.failure_kind == "invalid_payload" + + +def test_github_api_request_passes_timeout(monkeypatch): + github = RouteGitHubApi().add_request("GET", "issues/42", result=github_result(200, {"ok": True})) + bot = _bot(monkeypatch, github=github) + + result = github_api.github_api_request(bot, "GET", "issues/42", timeout_seconds=12.5) + + assert result.ok is True + assert bot.rest_transport.calls[0]["timeout_seconds"] == 12.5 + + +def test_github_api_request_rejects_idempotent_retry_for_non_get(monkeypatch): + with pytest.raises(ValueError, match="only valid for REST GET"): + github_api.github_api_request(_bot(monkeypatch), "POST", "issues/42", retry_policy="idempotent_read") + + +def test_github_graphql_request_rejects_idempotent_retry_for_mutation(monkeypatch): + with pytest.raises(ValueError, match="only valid for GraphQL queries"): + github_api.github_graphql_request( + _bot(monkeypatch), + "mutation { closeIssue(input: {}) { clientMutationId } }", + retry_policy="idempotent_read", + ) + + +def test_remove_label_reports_failure(monkeypatch): + bot = _bot(monkeypatch, github_api_request=lambda *args, **kwargs: GitHubApiResult(status_code=500, payload={"message": "boom"}, headers={}, text="boom", ok=False, failure_kind="server_error", retry_attempts=0, transport_error=None)) + + assert github_api.remove_label(bot, 42, "status: awaiting reviewer response") is False + + +def test_get_user_permission_status_distinguishes_unavailable(monkeypatch): + bot = _bot(monkeypatch, github_api_request=lambda *args, **kwargs: GitHubApiResult(status_code=502, payload={"message": "bad gateway"}, headers={}, text="bad gateway", ok=False, failure_kind="server_error", retry_attempts=1, transport_error=None)) + + assert github_api.get_user_permission_status(bot, "alice", "triage") == "unavailable" + assert github_api.check_user_permission(bot, "alice", "triage") is None + + +def test_get_issue_assignees_returns_none_when_fetch_unavailable(monkeypatch): + bot = _bot(monkeypatch, config={"IS_PULL_REQUEST": "true"}, github_api_request=lambda *args, **kwargs: GitHubApiResult(status_code=502, payload={"message": "bad gateway"}, headers={}, text="bad gateway", ok=False, failure_kind="server_error", retry_attempts=1, transport_error=None), github_api=lambda *args, **kwargs: (_ for _ in ()).throw(SystemExit(1))) + + assert github_api.get_issue_assignees(bot, 42) is None + + +def test_request_pr_reviewer_assignment_targets_pr_reviewers_endpoint(monkeypatch): + recorded = {} + + def fake_request(method, endpoint, data=None, suppress_error_log=True, **kwargs): + recorded.update({"method": method, "endpoint": endpoint, "data": data}) + return GitHubApiResult(201, {}, {}, "ok", True, None, 0, None) + + bot = _bot(monkeypatch, config={"IS_PULL_REQUEST": "true"}, github_api_request=fake_request) + + result = github_api.request_pr_reviewer_assignment(bot, 42, "alice") + + assert result.success is True + assert recorded == { + "method": "POST", + "endpoint": "pulls/42/requested_reviewers", + "data": {"reviewers": ["alice"]}, + } + + +def test_assign_issue_assignee_targets_issue_assignees_endpoint(monkeypatch): + recorded = {} + + def fake_request(method, endpoint, data=None, suppress_error_log=True, **kwargs): + recorded.update({"method": method, "endpoint": endpoint, "data": data}) + return GitHubApiResult(201, {}, {}, "ok", True, None, 0, None) + + bot = _bot(monkeypatch, github_api_request=fake_request) + + result = github_api.assign_issue_assignee(bot, 42, "alice") + + assert result.success is True + assert recorded == { + "method": "POST", + "endpoint": "issues/42/assignees", + "data": {"assignees": ["alice"]}, + } + + +def test_get_assignment_failure_comment_formats_pr_message(monkeypatch): + bot = _bot(monkeypatch) + failed = bot.AssignmentAttempt(success=False, status_code=422) + + message = guidance.get_assignment_failure_comment("alice", failed, is_pull_request=True) + + assert "PR Reviewers" in message + assert "API 422" in message + + +def test_get_assignment_failure_comment_formats_retry_exhaustion_message(monkeypatch): + bot = _bot(monkeypatch) + exhausted = bot.AssignmentAttempt(success=False, status_code=502, exhausted_retryable_failure=True) + + assert "PR Reviewers" in guidance.get_assignment_failure_comment("alice", exhausted, is_pull_request=True) + + +def test_remove_pr_reviewer_calls_pr_reviewers_delete(monkeypatch): + github = RouteGitHubApi() + github.add_api("DELETE", "pulls/42/requested_reviewers", {}) + bot = _bot(monkeypatch, github=github) + + assert github_api.remove_pr_reviewer(bot, 42, "alice") is True + assert [call.endpoint for call in github.api_calls] == ["pulls/42/requested_reviewers"] + + +def test_remove_issue_assignee_calls_issue_assignees_delete(monkeypatch): + github = RouteGitHubApi() + github.add_api("DELETE", "issues/42/assignees", {}) + bot = _bot(monkeypatch, github=github) + + assert github_api.remove_issue_assignee(bot, 42, "alice") is True + assert [call.endpoint for call in github.api_calls] == ["issues/42/assignees"] + + +def test_find_open_pr_for_branch_status_reports_unavailable_for_malformed_payload(monkeypatch): + bot = _bot(monkeypatch, github_api_request=lambda *args, **kwargs: GitHubApiResult(status_code=200, payload={"not": "a list"}, headers={}, text="ok", ok=True, failure_kind=None, retry_attempts=0, transport_error=None)) + + assert automation.find_open_pr_for_branch_status(bot, "feature") == ("unavailable", None) + + +def test_find_open_pr_for_branch_status_reports_unavailable_on_transport_failure(monkeypatch): + bot = _bot(monkeypatch, github_api_request=lambda *args, **kwargs: GitHubApiResult(status_code=502, payload={"message": "bad gateway"}, headers={}, text="bad gateway", ok=False, failure_kind="server_error", retry_attempts=1, transport_error=None)) + + assert automation.find_open_pr_for_branch_status(bot, "feature") == ("unavailable", None) + + +def test_github_api_request_reports_invalid_payload_for_malformed_json(monkeypatch): + github = RouteGitHubApi().add_request("GET", "issues/42", result=github_result(200, ValueError("bad json"), text="bad json")) + result = github_api.github_api_request(_bot(monkeypatch, github=github), "GET", "issues/42") + + assert result.ok is False + assert result.failure_kind == "invalid_payload" + + +def test_github_api_request_reports_retry_exhaustion_on_repeated_502(monkeypatch): + github = RouteGitHubApi().add_request_sequence( + "GET", + "issues/42", + [github_result(502, {"message": "bad gateway"})] * (LOCK_API_RETRY_LIMIT + 1), + ) + result = github_api.github_api_request(_bot(monkeypatch, github=github), "GET", "issues/42", retry_policy="idempotent_read", suppress_error_log=True) + + assert result.ok is False + assert result.failure_kind == "server_error" + assert result.retry_attempts == LOCK_API_RETRY_LIMIT + + +def test_github_api_request_reports_transport_retry_exhaustion(monkeypatch): + github = RouteGitHubApi().add_request_sequence( + "GET", + "issues/42", + [github_api.requests.RequestException("timeout")] * (LOCK_API_RETRY_LIMIT + 1), + ) + result = github_api.github_api_request(_bot(monkeypatch, github=github), "GET", "issues/42", retry_policy="idempotent_read", suppress_error_log=True) + + assert result.ok is False + assert result.failure_kind == "transport_error" + assert result.retry_attempts == LOCK_API_RETRY_LIMIT + assert "timeout" in str(result.transport_error) + + +def test_github_api_request_logs_transport_error_to_recording_logger(monkeypatch): + github = RouteGitHubApi().add_request_sequence("GET", "issues/42", [github_api.requests.RequestException("timeout")]) + bot = _bot(monkeypatch, github=github) + + result = github_api.github_api_request(bot, "GET", "issues/42", suppress_error_log=False) + + assert result.ok is False + assert bot.logger.records[-1]["level"] == "error" + assert "transport error" in bot.logger.records[-1]["message"] + + +def test_github_api_request_logs_error_response_to_recording_logger(monkeypatch): + github = RouteGitHubApi().add_request("GET", "issues/42", result=github_result(403, {"message": "forbidden"})) + bot = _bot(monkeypatch, github=github) + + result = github_api.github_api_request(bot, "GET", "issues/42", suppress_error_log=False) + + assert result.ok is False + assert bot.logger.records[-1]["level"] == "error" + assert bot.logger.records[-1]["fields"]["status_code"] == 403 + + +def test_github_graphql_request_passes_timeout(monkeypatch): + bot = _bot(monkeypatch) + bot.graphql_transport.stub_sequence([FakeGitHubResponse(200, {"data": {"viewer": {"login": "bot"}}}, "ok")]) + + result = github_api.github_graphql_request(bot, "query { viewer { login } }", timeout_seconds=9.5) + + assert result.ok is True + assert bot.graphql_transport.calls[0]["timeout_seconds"] == 9.5 + + +def test_find_open_pr_for_branch_status_blank_owner_or_branch_is_not_found(monkeypatch): + assert automation.find_open_pr_for_branch_status(_bot(monkeypatch, config={"REPO_OWNER": ""}), "feature") == ("not_found", None) + assert automation.find_open_pr_for_branch_status(_bot(monkeypatch), "") == ("not_found", None) + + +def test_find_open_pr_for_branch_status_reports_found(monkeypatch): + bot = _bot(monkeypatch, github_api_request=lambda *args, **kwargs: GitHubApiResult(200, [{"number": 42, "html_url": "https://example.com/pr/42"}], {}, "ok", True, None, 0, None)) + + status, pr = automation.find_open_pr_for_branch_status(bot, "feature") + + assert status == "found" + assert pr == {"number": 42, "html_url": "https://example.com/pr/42"} + + +def test_find_open_pr_for_branch_status_reports_not_found_for_empty_payload(monkeypatch): + bot = _bot(monkeypatch, github_api_request=lambda *args, **kwargs: GitHubApiResult(200, [], {}, "ok", True, None, 0, None)) + + assert automation.find_open_pr_for_branch_status(bot, "feature") == ("not_found", None) diff --git a/tests/unit/reviewer_bot/test_lease_lock.py b/tests/unit/reviewer_bot/test_lease_lock.py new file mode 100644 index 000000000..602c53a28 --- /dev/null +++ b/tests/unit/reviewer_bot/test_lease_lock.py @@ -0,0 +1,392 @@ +import sys +from types import SimpleNamespace + +import pytest + +from scripts.reviewer_bot_lib import lease_lock, state_store +from scripts.reviewer_bot_lib.config import GitHubApiResult, LeaseContext +from tests.fixtures.fake_clock import FakeClock +from tests.fixtures.fake_jitter import DeterministicJitter +from tests.fixtures.fake_sleeper import RecordingSleeper +from tests.fixtures.fake_uuid import FixedUuidSource +from tests.fixtures.recording_logger import RecordingLogger + + +def _lease_bot(**overrides): + config_values = { + "WORKFLOW_RUN_ID": "local-run", + "WORKFLOW_NAME": "reviewer-bot", + "WORKFLOW_JOB_NAME": "reviewer-bot", + } + bot = SimpleNamespace( + ACTIVE_LEASE_CONTEXT=None, + LOCK_REF_NAME="heads/reviewer-bot-state-lock", + LOCK_REF_BOOTSTRAP_BRANCH="main", + LOCK_API_RETRY_LIMIT=3, + LOCK_RETRY_BASE_SECONDS=0, + LOCK_MAX_WAIT_SECONDS=60, + LOCK_RENEWAL_WINDOW_SECONDS=60, + sys=sys, + time=SimpleNamespace(monotonic=lambda: 0.0), + clock=FakeClock(), + sleeper=RecordingSleeper(), + jitter=DeterministicJitter(0.0), + uuid_source=FixedUuidSource("token-123"), + logger=RecordingLogger(), + github_api_request=lambda *args, **kwargs: None, + get_state_issue_snapshot=lambda: SimpleNamespace(html_url="https://example.com/issues/314"), + get_config_value=lambda name, default="": config_values.get(name, default), + normalize_lock_metadata=state_store.normalize_lock_metadata, + parse_iso8601_timestamp=state_store.parse_iso8601_timestamp, + ) + for key, value in overrides.items(): + setattr(bot, key, value) + if "config_values" in overrides: + config_values.update(overrides["config_values"]) + delattr(bot, "config_values") + bot.clear_lock_metadata = lambda: lease_lock.clear_lock_metadata(bot) + bot.lock_api_retry_limit = lambda: bot.LOCK_API_RETRY_LIMIT + bot.lock_retry_base_seconds = lambda: bot.LOCK_RETRY_BASE_SECONDS + bot.lock_lease_ttl_seconds = lambda: getattr(bot, "LOCK_LEASE_TTL_SECONDS", 300) + bot.lock_max_wait_seconds = lambda: bot.LOCK_MAX_WAIT_SECONDS + bot.lock_renewal_window_seconds = lambda: bot.LOCK_RENEWAL_WINDOW_SECONDS + bot.lock_ref_name = lambda: bot.LOCK_REF_NAME + bot.lock_ref_bootstrap_branch = lambda: bot.LOCK_REF_BOOTSTRAP_BRANCH + bot.get_state_issue_html_url = lambda: lease_lock.get_state_issue_html_url(bot) + bot.get_lock_ref_display = lambda: lease_lock.get_lock_ref_display(bot) + bot.get_lock_ref_snapshot = lambda: lease_lock.get_lock_ref_snapshot(bot) + bot.build_lock_metadata = lambda *args: lease_lock.build_lock_metadata(bot, *args) + bot.create_lock_commit = lambda parent_sha, tree_sha, lock_meta: lease_lock.create_lock_commit( + bot, parent_sha, tree_sha, lock_meta + ) + bot.cas_update_lock_ref = lambda new_sha: lease_lock.cas_update_lock_ref(bot, new_sha) + bot.lock_is_currently_valid = lambda lock_meta, now=None: lease_lock.lock_is_currently_valid( + bot, lock_meta, now + ) + return bot + + +def test_acquire_lock_retries_until_expected_token_visible(): + bot = _lease_bot() + snapshots = iter( + [ + ("old-ref", "tree", {"lock_state": "unlocked", "lock_token": None}), + ("stale-ref", "tree", {"lock_state": "unlocked", "lock_token": None}), + ("stale-ref-2", "tree", {"lock_state": "unlocked", "lock_token": None}), + ( + "new-ref", + "tree", + { + "lock_state": "locked", + "lock_token": "token-123", + "lock_owner_run_id": "local-run", + "lock_owner_workflow": "reviewer-bot", + "lock_owner_job": "reviewer-bot", + "lock_expires_at": "2999-01-01T00:00:00+00:00", + }, + ), + ] + ) + + bot.get_lock_ref_snapshot = lambda: next(snapshots) + bot.create_lock_commit = lambda parent_sha, tree_sha, lock_meta: GitHubApiResult( + 201, {"sha": "commit-1"}, {}, "", True + ) + bot.cas_update_lock_ref = lambda new_sha: GitHubApiResult(200, {}, {}, "", True) + + context = lease_lock.acquire_state_issue_lease_lock(bot) + + assert context.lock_token == "token-123" + assert bot.ACTIVE_LEASE_CONTEXT is context + + +def test_acquire_lock_fails_closed_on_conflicting_visible_token(monkeypatch): + bot = _lease_bot() + snapshots = iter( + [ + ("old-ref", "tree", {"lock_state": "unlocked", "lock_token": None}), + ("new-ref", "tree", {"lock_state": "locked", "lock_token": "other-token"}), + ] + ) + + bot.get_lock_ref_snapshot = lambda: next(snapshots) + bot.create_lock_commit = lambda parent_sha, tree_sha, lock_meta: GitHubApiResult( + 201, {"sha": "commit-1"}, {}, "", True + ) + bot.cas_update_lock_ref = lambda new_sha: GitHubApiResult(200, {}, {}, "", True) + + with pytest.raises(RuntimeError, match="unexpected lock state"): + lease_lock.acquire_state_issue_lease_lock(bot) + + +def test_acquire_lock_succeeds_when_later_loop_observes_own_valid_token(): + bot = _lease_bot() + snapshots = iter( + [ + ("old-ref", "tree", {"lock_state": "unlocked", "lock_token": None}), + ("stale-ref", "tree", {"lock_state": "unlocked", "lock_token": None}), + ( + "new-ref", + "tree", + { + "lock_state": "locked", + "lock_token": "token-123", + "lock_owner_run_id": "local-run", + "lock_owner_workflow": "reviewer-bot", + "lock_owner_job": "reviewer-bot", + "lock_expires_at": "2999-01-01T00:00:00+00:00", + }, + ), + ] + ) + + bot.get_lock_ref_snapshot = lambda: next(snapshots) + bot.create_lock_commit = lambda parent_sha, tree_sha, lock_meta: GitHubApiResult( + 201, {"sha": "commit-1"}, {}, "", True + ) + bot.cas_update_lock_ref = lambda new_sha: GitHubApiResult(200, {}, {}, "", True) + + context = lease_lock.acquire_state_issue_lease_lock(bot) + + assert context.lock_token == "token-123" + assert bot.ACTIVE_LEASE_CONTEXT is context + + +def test_acquire_lock_fails_closed_when_own_token_has_mismatched_owner(): + bot = _lease_bot() + snapshots = iter( + [ + ( + "new-ref", + "tree", + { + "lock_state": "locked", + "lock_token": "token-123", + "lock_owner_run_id": "someone-else", + "lock_owner_workflow": "reviewer-bot", + "lock_owner_job": "reviewer-bot", + "lock_expires_at": "2999-01-01T00:00:00+00:00", + }, + ) + ] + ) + + bot.get_lock_ref_snapshot = lambda: next(snapshots) + + with pytest.raises(RuntimeError, match="owner metadata drifted"): + lease_lock.acquire_state_issue_lease_lock(bot) + + +def test_release_lock_retries_stale_unlocked_predecessor(monkeypatch): + context = LeaseContext( + lock_token="token-123", + lock_owner_run_id="run", + lock_owner_workflow="workflow", + lock_owner_job="job", + state_issue_url="https://example.com/issues/314", + lock_ref="refs/heads/reviewer-bot-state-lock", + lock_expires_at="2999-01-01T00:00:00+00:00", + ) + bot = _lease_bot(ACTIVE_LEASE_CONTEXT=context) + snapshots = iter( + [ + ("stale-ref", "tree", {"lock_state": "unlocked", "lock_token": None}), + ("new-ref", "tree", {"lock_state": "locked", "lock_token": "token-123"}), + ] + ) + + bot.get_lock_ref_snapshot = lambda: next(snapshots) + bot.create_lock_commit = lambda parent_sha, tree_sha, lock_meta: GitHubApiResult( + 201, {"sha": "commit-2"}, {}, "", True + ) + bot.cas_update_lock_ref = lambda new_sha: GitHubApiResult(200, {}, {}, "", True) + + assert lease_lock.release_state_issue_lease_lock(bot) is True + assert bot.ACTIVE_LEASE_CONTEXT is None + + +def test_release_lock_fails_closed_on_conflicting_token(monkeypatch): + context = LeaseContext( + lock_token="token-123", + lock_owner_run_id="run", + lock_owner_workflow="workflow", + lock_owner_job="job", + state_issue_url="https://example.com/issues/314", + lock_ref="refs/heads/reviewer-bot-state-lock", + lock_expires_at="2999-01-01T00:00:00+00:00", + ) + bot = _lease_bot(ACTIVE_LEASE_CONTEXT=context) + bot.get_lock_ref_snapshot = lambda: ( + "new-ref", + "tree", + {"lock_state": "locked", "lock_token": "other-token"}, + ) + + assert lease_lock.release_state_issue_lease_lock(bot) is False + assert bot.ACTIVE_LEASE_CONTEXT is None + + +def test_ensure_lock_ref_exists_uses_retry_aware_reads(monkeypatch): + observed = [] + + def fake_request(method, endpoint, data=None, extra_headers=None, **kwargs): + observed.append((endpoint, kwargs.get("retry_policy"))) + if endpoint == "git/ref/heads/reviewer-bot-state-lock" and len(observed) == 1: + return GitHubApiResult( + status_code=404, + payload={"message": "missing"}, + headers={}, + text="missing", + ok=False, + failure_kind="not_found", + retry_attempts=0, + transport_error=None, + ) + if endpoint == "git/ref/heads/reviewer-bot-state-lock": + return GitHubApiResult( + status_code=200, + payload={"object": {"sha": "base-sha"}}, + headers={}, + text="ok", + ok=True, + failure_kind=None, + retry_attempts=0, + transport_error=None, + ) + if endpoint == "git/ref/heads/main": + return GitHubApiResult( + status_code=200, + payload={"object": {"sha": "base-sha"}}, + headers={}, + text="ok", + ok=True, + failure_kind=None, + retry_attempts=1, + transport_error=None, + ) + if endpoint == "git/refs": + return GitHubApiResult( + status_code=201, + payload={"ref": "refs/heads/reviewer-bot-state-lock"}, + headers={}, + text="created", + ok=True, + failure_kind=None, + retry_attempts=0, + transport_error=None, + ) + raise AssertionError(endpoint) + + bot = _lease_bot(github_api_request=fake_request) + + assert lease_lock.ensure_lock_ref_exists(bot) == "base-sha" + assert observed[0] == ("git/ref/heads/reviewer-bot-state-lock", "idempotent_read") + assert observed[1] == ("git/ref/heads/main", "idempotent_read") + + +def test_ensure_lock_ref_exists_fails_closed_when_bootstrap_branch_unavailable(monkeypatch): + responses = iter( + [ + GitHubApiResult(404, {"message": "missing"}, {}, "missing", False, "not_found", 0, None), + GitHubApiResult(502, {"message": "bad gateway"}, {}, "bad gateway", False, "server_error", 1, None), + ] + ) + bot = _lease_bot(github_api_request=lambda *args, **kwargs: next(responses)) + + with pytest.raises(RuntimeError, match="Unable to read bootstrap branch"): + lease_lock.ensure_lock_ref_exists(bot) + + +def test_get_lock_ref_snapshot_fails_closed_on_invalid_commit_payload(): + responses = iter( + [ + GitHubApiResult( + status_code=200, + payload={"object": {"sha": "ref-sha"}}, + headers={}, + text="ok", + ok=True, + failure_kind=None, + retry_attempts=0, + transport_error=None, + ), + GitHubApiResult( + status_code=200, + payload={"message": "missing tree"}, + headers={}, + text="ok", + ok=True, + failure_kind=None, + retry_attempts=0, + transport_error=None, + ), + ] + ) + bot = _lease_bot(github_api_request=lambda method, endpoint, data=None, extra_headers=None, **kwargs: next(responses)) + + with pytest.raises(RuntimeError, match="missing tree SHA"): + lease_lock.get_lock_ref_snapshot(bot) + + +def test_renew_state_issue_lease_lock_fails_on_token_mismatch(monkeypatch): + context = LeaseContext( + lock_token="expected-token", + lock_owner_run_id="run-1", + lock_owner_workflow="wf", + lock_owner_job="job", + state_issue_url="https://example.com/state/1", + ) + bot = _lease_bot() + bot.get_lock_ref_snapshot = lambda: ( + "ref-sha", + "tree-sha", + {"lock_token": "different-token", "lock_state": "locked"}, + ) + + assert lease_lock.renew_state_issue_lease_lock(bot, context) is False + + +def test_ensure_lock_ref_exists_fails_closed_when_lock_ref_read_remains_unavailable(monkeypatch): + bot = _lease_bot( + github_api_request=lambda *args, **kwargs: GitHubApiResult( + 502, {"message": "bad gateway"}, {}, "bad gateway", False, "server_error", 1, None + ) + ) + + with pytest.raises(RuntimeError, match="Failed to read reviewer-bot lock ref"): + lease_lock.ensure_lock_ref_exists(bot) + + +def test_ensure_lock_ref_exists_fails_closed_when_bootstrap_branch_sha_missing(monkeypatch): + responses = iter( + [ + GitHubApiResult(404, {"message": "missing"}, {}, "missing", False, "not_found", 0, None), + GitHubApiResult(200, {"object": {}}, {}, "ok", True, None, 0, None), + ] + ) + bot = _lease_bot(github_api_request=lambda *args, **kwargs: next(responses)) + + with pytest.raises(RuntimeError, match="Bootstrap branch ref did not include SHA"): + lease_lock.ensure_lock_ref_exists(bot) + + +def test_get_lock_ref_snapshot_fails_closed_when_commit_fetch_unavailable(): + responses = iter( + [ + GitHubApiResult( + status_code=200, + payload={"object": {"sha": "ref-sha"}}, + headers={}, + text="ok", + ok=True, + failure_kind=None, + retry_attempts=0, + transport_error=None, + ), + GitHubApiResult(502, {"message": "bad gateway"}, {}, "bad gateway", False, "server_error", 1, None), + ] + ) + bot = _lease_bot(github_api_request=lambda *args, **kwargs: next(responses)) + + with pytest.raises(RuntimeError, match="Failed to read lock commit"): + lease_lock.get_lock_ref_snapshot(bot) diff --git a/tests/unit/reviewer_bot/test_lifecycle.py b/tests/unit/reviewer_bot/test_lifecycle.py new file mode 100644 index 000000000..e132fc40e --- /dev/null +++ b/tests/unit/reviewer_bot/test_lifecycle.py @@ -0,0 +1,357 @@ +import json +from pathlib import Path + +import pytest + +from scripts.reviewer_bot_lib import ( + comment_routing, + lifecycle, + maintenance, + maintenance_schedule, + review_state, + reviews, +) +from scripts.reviewer_bot_lib.config import GitHubApiResult +from tests.fixtures.comment_routing_harness import CommentRoutingHarness +from tests.fixtures.fake_runtime import FakeReviewerBotRuntime +from tests.fixtures.reviewer_bot import make_state + + +def test_handle_pull_request_target_synchronize_returns_true_for_head_only_mutation(monkeypatch): + runtime = FakeReviewerBotRuntime(monkeypatch) + state = make_state() + review = review_state.ensure_review_entry(state, 42, create=True) + assert review is not None + review["current_reviewer"] = "alice" + review["active_head_sha"] = "head-1" + review["contributor_revision"]["seen_keys"] = ["pull_request_sync:42:head-2"] + runtime.set_config_value("ISSUE_NUMBER", "42") + runtime.set_config_value("PR_HEAD_SHA", "head-2") + runtime.set_config_value("EVENT_CREATED_AT", "2026-03-17T10:00:00Z") + monkeypatch.setattr(reviews, "rebuild_pr_approval_state", lambda bot, issue_number, review_data: (None, None)) + + assert lifecycle.handle_pull_request_target_synchronize(runtime, state) is True + assert review["active_head_sha"] == "head-2" + + +def test_pr_comment_direct_path_is_epoch_gated(monkeypatch): + harness = CommentRoutingHarness(monkeypatch) + state = make_state(epoch="legacy_v14") + entry = review_state.ensure_review_entry(state, 42, create=True) + assert entry is not None + entry["current_reviewer"] = "alice" + request = harness.request( + issue_number=42, + is_pull_request=True, + issue_author="dana", + comment_author="alice", + comment_body="hello", + ) + trust_context = harness.trust_context( + github_repository="rustfoundation/safety-critical-rust-coding-guidelines", + comment_author_association="MEMBER", + current_workflow_file=".github/workflows/reviewer-bot-pr-comment-trusted.yml", + github_ref="refs/heads/main", + ) + harness.add_pull_request_metadata( + issue_number=42, + head_repo_full_name="rustfoundation/safety-critical-rust-coding-guidelines", + pr_author="dana", + ) + + assert comment_routing.handle_comment_event(harness.runtime, state, request, trust_context) is False + + +def test_check_overdue_reviews_skips_transition_after_transition_notice_sent(monkeypatch): + runtime = FakeReviewerBotRuntime(monkeypatch) + state = make_state() + review = review_state.ensure_review_entry(state, 42, create=True) + assert review is not None + review["current_reviewer"] = "alice" + review["assigned_at"] = "2026-03-01T00:00:00Z" + review["last_reviewer_activity"] = "2026-03-01T00:00:00Z" + review["transition_warning_sent"] = "2026-03-10T00:00:00Z" + review["transition_notice_sent_at"] = "2026-03-25T00:00:00Z" + runtime.get_issue_or_pr_snapshot = lambda issue_number: {"number": issue_number, "state": "open", "pull_request": {}, "labels": []} + runtime.get_pull_request_reviews = lambda issue_number: [] + + assert maintenance.check_overdue_reviews(runtime, state) == [] + + +def test_handle_transition_notice_records_transition_notice_sent_at_once(monkeypatch): + runtime = FakeReviewerBotRuntime(monkeypatch) + state = make_state() + review = review_state.ensure_review_entry(state, 42, create=True) + assert review is not None + review["current_reviewer"] = "alice" + posted = [] + runtime.post_comment = lambda issue_number, body: posted.append((issue_number, body)) or True + + assert lifecycle.handle_transition_notice(runtime, state, 42, "alice") is True + assert review["transition_notice_sent_at"] is not None + assert lifecycle.handle_transition_notice(runtime, state, 42, "alice") is False + assert len(posted) == 1 + + +def test_handle_transition_notice_message_does_not_claim_reassignment(monkeypatch): + runtime = FakeReviewerBotRuntime(monkeypatch) + state = make_state() + review_state.ensure_review_entry(state, 42, create=True) + posted = [] + runtime.post_comment = lambda issue_number, body: posted.append(body) or True + + assert lifecycle.handle_transition_notice(runtime, state, 42, "alice") is True + assert "reassigned to the next person in the queue" not in posted[0] + assert "/pass" in posted[0] + + +def test_l1_fake_runtime_and_bootstrap_keep_override_wiring_explicit_without_canonical_introspection(): + fake_runtime_text = Path("tests/fixtures/fake_runtime.py").read_text(encoding="utf-8") + bootstrap_text = Path("scripts/reviewer_bot_lib/bootstrap_runtime.py").read_text(encoding="utf-8") + + assert "def get_pull_request_reviews(" in fake_runtime_text + assert "def rebuild_pr_approval_state(" in fake_runtime_text + assert "def rebuild_pr_approval_state(" in bootstrap_text + assert "_mark_canonical" not in bootstrap_text + + +def test_reviewer_comment_clears_warning_and_transition_notice_markers(monkeypatch): + harness = CommentRoutingHarness(monkeypatch) + state = make_state() + review = review_state.ensure_review_entry(state, 42, create=True) + assert review is not None + review["current_reviewer"] = "alice" + review["transition_warning_sent"] = "2026-03-10T00:00:00Z" + review["transition_notice_sent_at"] = "2026-03-25T00:00:00Z" + request = harness.request( + issue_number=42, + is_pull_request=True, + issue_author="dana", + comment_author="alice", + comment_body="hello", + ) + trust_context = harness.trust_context( + github_repository="rustfoundation/safety-critical-rust-coding-guidelines", + comment_author_association="MEMBER", + current_workflow_file=".github/workflows/reviewer-bot-pr-comment-trusted.yml", + github_ref="refs/heads/main", + ) + harness.add_pull_request_metadata( + issue_number=42, + head_repo_full_name="rustfoundation/safety-critical-rust-coding-guidelines", + pr_author="dana", + ) + + assert comment_routing.handle_comment_event(harness.runtime, state, request, trust_context) is True + assert review["transition_warning_sent"] is None + assert review["transition_notice_sent_at"] is None + + +def test_scheduled_check_backfills_transition_notice_without_reposting(monkeypatch): + runtime = FakeReviewerBotRuntime(monkeypatch) + state = make_state() + review = review_state.ensure_review_entry(state, 42, create=True) + assert review is not None + review["current_reviewer"] = "alice" + review["assigned_at"] = "2026-03-01T00:00:00Z" + review["last_reviewer_activity"] = "2026-03-01T00:00:00Z" + review["transition_warning_sent"] = "2026-03-10T00:00:00Z" + monkeypatch.setattr(maintenance_schedule, "sweep_deferred_gaps", lambda bot, state: False) + monkeypatch.setattr(review_state, "repair_missing_reviewer_review_state", lambda bot, issue_number, review_data, *, reviews=None: False) + monkeypatch.setattr(maintenance_schedule, "maybe_record_head_observation_repair", lambda bot, issue_number, review_data: lifecycle.HeadObservationRepairResult(changed=False, outcome="unchanged")) + monkeypatch.setattr( + maintenance_schedule, + "check_overdue_reviews", + lambda bot, state: [ + { + "issue_number": 42, + "reviewer": "alice", + "days_overdue": 20, + "days_since_warning": 15, + "needs_warning": False, + "needs_transition": True, + } + ], + ) + runtime.get_pull_request_reviews = lambda issue_number: [] + runtime.get_issue_or_pr_snapshot = lambda issue_number: {"pull_request": {}} + posted = [] + runtime.post_comment = lambda issue_number, body: posted.append(body) or True + runtime.github_api = lambda method, endpoint, data=None: [ + { + "id": 99, + "created_at": "2026-03-25T15:22:42Z", + "body": "🔔 **Transition Period Ended**\n\nExisting notice", + "user": {"login": "github-actions[bot]"}, + } + ] + + assert maintenance.handle_scheduled_check_result(runtime, state).state_changed is True + assert review["transition_notice_sent_at"] == "2026-03-25T15:22:42Z" + assert posted == [] + + +def test_maybe_record_head_observation_repair_skips_unavailable_without_mutation(monkeypatch): + runtime = FakeReviewerBotRuntime(monkeypatch) + review_data = {"active_head_sha": "head-1", "contributor_revision": {"accepted": None}} + runtime.github_api_request = lambda method, endpoint, **kwargs: GitHubApiResult( + status_code=502, + payload={"message": "bad gateway"}, + headers={}, + text="bad gateway", + ok=False, + failure_kind="server_error", + retry_attempts=1, + transport_error=None, + ) + + result = lifecycle.maybe_record_head_observation_repair(runtime, 42, review_data) + + assert result == lifecycle.HeadObservationRepairResult( + changed=False, + outcome="skipped_unavailable", + failure_kind="server_error", + reason="pull_request_unavailable", + ) + assert review_data["active_head_sha"] == "head-1" + + +def test_maybe_record_head_observation_repair_reports_not_found(monkeypatch): + runtime = FakeReviewerBotRuntime(monkeypatch) + review_data = {"active_head_sha": "head-1", "contributor_revision": {"accepted": None}} + runtime.github_api_request = lambda method, endpoint, **kwargs: GitHubApiResult( + status_code=404, + payload={"message": "missing"}, + headers={}, + text="missing", + ok=False, + failure_kind="not_found", + retry_attempts=0, + transport_error=None, + ) + + result = lifecycle.maybe_record_head_observation_repair(runtime, 42, review_data) + + assert result.outcome == "skipped_not_found" + assert result.failure_kind == "not_found" + + +def test_maybe_record_head_observation_repair_reports_invalid_payload(monkeypatch): + runtime = FakeReviewerBotRuntime(monkeypatch) + review_data = {"active_head_sha": "head-1", "contributor_revision": {"accepted": None}} + runtime.github_api_request = lambda method, endpoint, **kwargs: GitHubApiResult( + status_code=200, + payload={"state": "open", "head": {}}, + headers={}, + text="ok", + ok=True, + failure_kind=None, + retry_attempts=0, + transport_error=None, + ) + + result = lifecycle.maybe_record_head_observation_repair(runtime, 42, review_data) + + assert result.outcome == "invalid_live_payload" + assert result.reason == "pull_request_head_unavailable" + + +def test_maybe_record_head_observation_repair_skips_not_open(monkeypatch): + runtime = FakeReviewerBotRuntime(monkeypatch) + review_data = {"active_head_sha": "head-1", "contributor_revision": {"accepted": None}} + runtime.github_api_request = lambda method, endpoint, **kwargs: GitHubApiResult( + status_code=200, + payload={"state": "closed", "head": {"sha": "head-1"}}, + headers={}, + text="ok", + ok=True, + failure_kind=None, + retry_attempts=0, + transport_error=None, + ) + + result = lifecycle.maybe_record_head_observation_repair(runtime, 42, review_data) + + assert result.outcome == "skipped_not_open" + + +def test_maybe_record_head_observation_repair_records_changed_head_once(monkeypatch): + runtime = FakeReviewerBotRuntime(monkeypatch) + review_data = { + "active_head_sha": "head-1", + "contributor_revision": {"accepted": None}, + "current_cycle_completion": {"completed": True}, + "current_cycle_write_approval": {"has_write_approval": True}, + "review_completed_at": "2026-03-10T00:00:00Z", + "review_completed_by": "alice", + "review_completion_source": "live_review_rebuild", + } + accepted = [] + runtime.github_api_request = lambda method, endpoint, **kwargs: GitHubApiResult( + status_code=200, + payload={"state": "open", "head": {"sha": "head-2"}}, + headers={}, + text="ok", + ok=True, + failure_kind=None, + retry_attempts=0, + transport_error=None, + ) + monkeypatch.setattr(lifecycle, "accept_channel_event", lambda review_data, channel, **kwargs: accepted.append((channel, kwargs)) or True) + + result = lifecycle.maybe_record_head_observation_repair(runtime, 42, review_data) + + assert result.outcome == "changed" + assert result.changed is True + assert review_data["active_head_sha"] == "head-2" + assert accepted[0][0] == "contributor_revision" + assert accepted[0][1]["semantic_key"] == "pull_request_head_observed:42:head-2" + assert review_data["current_cycle_completion"] == {} + assert review_data["current_cycle_write_approval"] == {} + assert review_data["review_completed_at"] is None + + +def test_handle_issue_or_pr_opened_fails_closed_when_assignees_unavailable(monkeypatch): + runtime = FakeReviewerBotRuntime(monkeypatch) + state = make_state() + runtime.set_config_value("ISSUE_NUMBER", "42") + runtime.set_config_value("ISSUE_LABELS", json.dumps(["coding guideline"])) + runtime.get_issue_assignees = lambda issue_number: None + + with pytest.raises(RuntimeError, match="Unable to determine assignees"): + lifecycle.handle_issue_or_pr_opened(runtime, state) + + +def test_issue_edit_by_author_records_contributor_freshness(monkeypatch): + runtime = FakeReviewerBotRuntime(monkeypatch) + state = make_state() + review = review_state.ensure_review_entry(state, 42, create=True) + assert review is not None + review["current_reviewer"] = "alice" + runtime.set_config_value("IS_PULL_REQUEST", "false") + runtime.set_config_value("ISSUE_NUMBER", "42") + runtime.set_config_value("ISSUE_AUTHOR", "dana") + runtime.set_config_value("SENDER_LOGIN", "dana") + runtime.set_config_value("ISSUE_TITLE", "New title") + runtime.set_config_value("ISSUE_BODY", "body") + runtime.set_config_value("ISSUE_CHANGES_TITLE_FROM", "Old title") + runtime.set_config_value("ISSUE_CHANGES_BODY_FROM", "body") + runtime.set_config_value("ISSUE_UPDATED_AT", "2026-03-17T10:00:00Z") + + assert lifecycle.handle_issue_edited_event(runtime, state) is True + accepted = review["contributor_comment"]["accepted"] + assert accepted["semantic_key"].startswith("issues_edit_title:42:") + + +def test_maybe_record_head_observation_repair_uses_github_api_fallback_after_system_exit(monkeypatch): + runtime = FakeReviewerBotRuntime(monkeypatch) + review_data = {"active_head_sha": "head-0", "contributor_revision": {"accepted": None}} + runtime.github_api_request = lambda *args, **kwargs: (_ for _ in ()).throw(SystemExit(1)) + runtime.github_api = lambda method, endpoint, data=None: {"state": "open", "head": {"sha": "head-1"}} + monkeypatch.setattr(lifecycle, "accept_channel_event", lambda review_data, channel, **kwargs: True) + + result = lifecycle.maybe_record_head_observation_repair(runtime, 42, review_data) + + assert result.changed is True + assert result.outcome == "changed" + assert review_data["active_head_sha"] == "head-1" diff --git a/tests/unit/reviewer_bot/test_lock_codec.py b/tests/unit/reviewer_bot/test_lock_codec.py new file mode 100644 index 000000000..79b04e1a7 --- /dev/null +++ b/tests/unit/reviewer_bot/test_lock_codec.py @@ -0,0 +1,46 @@ +from scripts.reviewer_bot_lib import lock_codec + + +def test_lock_codec_round_trips_marked_issue_block_metadata(): + lock_meta = { + "lock_state": "locked", + "lock_token": "token-123", + "lock_owner_run_id": "run-1", + } + + rendered = lock_codec.render_lock_commit_message(lock_meta) + parsed = lock_codec.parse_lock_commit_message(rendered) + + assert parsed["lock_state"] == "locked" + assert parsed["lock_token"] == "token-123" + assert parsed["lock_owner_run_id"] == "run-1" + + +def test_lock_codec_round_trips_commit_message_metadata(): + lock_meta = { + "lock_state": "locked", + "lock_token": "token-123", + "lock_owner_run_id": "run-1", + } + + rendered = lock_codec.render_lock_commit_message(lock_meta) + parsed = lock_codec.parse_lock_commit_message(rendered) + + assert parsed["lock_state"] == "locked" + assert parsed["lock_token"] == "token-123" + assert parsed["lock_owner_run_id"] == "run-1" + + +def test_lock_codec_normalizes_missing_metadata_keys(): + parsed = lock_codec.normalize_lock_metadata({"lock_state": "locked"}) + + assert parsed["lock_state"] == "locked" + assert parsed["lock_token"] is None + assert parsed["lock_owner_run_id"] is None + + +def test_lock_codec_rejects_invalid_commit_message(): + import pytest + + with pytest.raises(RuntimeError, match="invalid reviewer-bot lock commit message"): + lock_codec.parse_lock_commit_message("not a lock commit") diff --git a/tests/unit/reviewer_bot/test_maintenance.py b/tests/unit/reviewer_bot/test_maintenance.py new file mode 100644 index 000000000..91dad2968 --- /dev/null +++ b/tests/unit/reviewer_bot/test_maintenance.py @@ -0,0 +1,179 @@ +from scripts.reviewer_bot_lib import ( + lifecycle, + maintenance, + maintenance_schedule, + repair_records, + review_state, +) +from scripts.reviewer_bot_lib.config import GitHubApiResult +from tests.fixtures.fake_runtime import FakeReviewerBotRuntime +from tests.fixtures.reviewer_bot import make_state + + +def test_scheduled_check_repairs_missing_reviewer_review_state(monkeypatch): + state = make_state() + review = review_state.ensure_review_entry(state, 42, create=True) + assert review is not None + review["current_reviewer"] = "alice" + review["active_cycle_started_at"] = "2026-03-17T09:00:00Z" + bot = FakeReviewerBotRuntime(monkeypatch) + bot.ACTIVE_LEASE_CONTEXT = object() + bot.get_issue_or_pr_snapshot = lambda issue_number: {"pull_request": {}} + bot.github_api_request = lambda method, endpoint, data=None, extra_headers=None, **kwargs: GitHubApiResult( + 200, + {"state": "open", "head": {"sha": "head-1"}} + if endpoint == "pulls/42" + else [ + { + "id": 10, + "state": "COMMENTED", + "submitted_at": "2026-03-17T10:01:00Z", + "commit_id": "head-1", + "user": {"login": "alice"}, + } + ], + {}, + "ok", + True, + None, + 0, + None, + ) + bot.collect_touched_item = lambda issue_number: None + monkeypatch.setattr(maintenance_schedule, "sweep_deferred_gaps", lambda bot, state: False) + monkeypatch.setattr(maintenance_schedule, "maybe_record_head_observation_repair", lambda bot, issue_number, review_data: lifecycle.HeadObservationRepairResult(changed=False, outcome="unchanged")) + monkeypatch.setattr(maintenance_schedule, "check_overdue_reviews", lambda bot, state: []) + + assert maintenance.handle_scheduled_check_result(bot, state).state_changed is True + accepted = review["reviewer_review"]["accepted"] + assert accepted is not None + assert accepted["semantic_key"] == "pull_request_review:10" + assert review["last_reviewer_activity"] == "2026-03-17T10:01:00Z" + + +def test_scheduled_check_records_live_read_failure_and_continues(monkeypatch): + state = make_state() + review_42 = review_state.ensure_review_entry(state, 42, create=True) + review_43 = review_state.ensure_review_entry(state, 43, create=True) + assert review_42 is not None and review_43 is not None + review_42["current_reviewer"] = "alice" + review_43["current_reviewer"] = "bob" + overdue_called = [] + bot = FakeReviewerBotRuntime(monkeypatch) + bot.ACTIVE_LEASE_CONTEXT = object() + bot.collect_touched_item = lambda issue_number: None + bot.get_issue_or_pr_snapshot = lambda issue_number: {"pull_request": {}} + monkeypatch.setattr(maintenance_schedule, "sweep_deferred_gaps", lambda bot, state: False) + monkeypatch.setattr(maintenance_schedule, "check_overdue_reviews", lambda bot, state: overdue_called.append(True) or []) + monkeypatch.setattr(maintenance_schedule, "repair_missing_reviewer_review_state", lambda bot, issue_number, review_data: False) + + def fake_head_repair(bot, issue_number, review_data): + if issue_number == 42: + return lifecycle.HeadObservationRepairResult(changed=False, outcome="skipped_unavailable", failure_kind="server_error", reason="pull_request_unavailable") + repair_records.store_repair_marker(review_data, "head_observation_repair", { + "kind": "live_read_failure", + "phase": "head_observation_repair", + "reason": "stale", + "failure_kind": "server_error", + "recorded_at": "2026-03-01T00:00:00Z", + }) + return lifecycle.HeadObservationRepairResult(changed=False, outcome="unchanged") + + monkeypatch.setattr(maintenance_schedule, "maybe_record_head_observation_repair", fake_head_repair) + + assert maintenance.handle_scheduled_check_result(bot, state).state_changed is True + assert overdue_called == [True] + assert repair_records.load_repair_marker(review_42, "head_observation_repair")["kind"] == "live_read_failure" + assert repair_records.load_repair_marker(review_42, "head_observation_repair")["failure_kind"] == "server_error" + assert repair_records.load_repair_marker(review_43, "head_observation_repair") is None + + +def test_record_maintenance_repair_marker_ignores_recorded_at_for_identical_failure(monkeypatch): + review = review_state.ensure_review_entry(make_state(), 42, create=True) + assert review is not None + timestamps = iter(["2026-03-01T00:00:00Z", "2026-03-02T00:00:00Z"]) + bot = FakeReviewerBotRuntime(monkeypatch) + monkeypatch.setattr(maintenance_schedule, "_now_iso", lambda bot: next(timestamps)) + + first = maintenance_schedule._record_maintenance_repair_marker( + bot, + review, + phase="head_observation_repair", + reason="pull_request_unavailable", + failure_kind="server_error", + ) + second = maintenance_schedule._record_maintenance_repair_marker( + bot, + review, + phase="head_observation_repair", + reason="pull_request_unavailable", + failure_kind="server_error", + ) + + assert first is True + assert second is False + assert repair_records.load_repair_marker(review, "head_observation_repair")["recorded_at"] == "2026-03-01T00:00:00Z" + + +def test_tracked_pr_repair_pass_collects_touched_items_and_clears_review_repair_marker(monkeypatch): + state = make_state() + review = review_state.ensure_review_entry(state, 42, create=True) + assert review is not None + review["current_reviewer"] = "alice" + repair_records.store_repair_marker(review, "review_repair", { + "kind": "live_read_failure", + "phase": "review_repair", + "reason": "stale", + "failure_kind": None, + "recorded_at": "2026-03-01T00:00:00Z", + }) + bot = FakeReviewerBotRuntime(monkeypatch) + bot.get_issue_or_pr_snapshot = lambda issue_number: {"number": issue_number, "state": "open", "pull_request": {}, "labels": []} + monkeypatch.setattr(maintenance_schedule, "repair_missing_reviewer_review_state", lambda bot, issue_number, review_data: True) + monkeypatch.setattr( + maintenance_schedule, + "maybe_record_head_observation_repair", + lambda bot, issue_number, review_data: lifecycle.HeadObservationRepairResult(changed=False, outcome="unchanged"), + ) + + assert maintenance_schedule._run_tracked_pr_repairs(bot, state) is True + assert bot.drain_touched_items() == [42] + assert repair_records.load_repair_marker(review, "review_repair") is None + + +def test_finalize_schedule_result_drains_touched_items_for_projection_followup(monkeypatch): + bot = FakeReviewerBotRuntime(monkeypatch) + bot.collect_touched_item(42) + bot.collect_touched_item(99) + + result = maintenance_schedule._finalize_schedule_result(bot, True) + + assert result == maintenance.ScheduleHandlerResult( + state_changed=True, + touched_items=[42, 99], + ) + assert bot.drain_touched_items() == [] + + +def test_scheduled_check_clears_head_observation_repair_marker_after_success(monkeypatch): + state = make_state() + review = review_state.ensure_review_entry(state, 42, create=True) + assert review is not None + review["current_reviewer"] = "alice" + repair_records.store_repair_marker(review, "head_observation_repair", { + "kind": "live_read_failure", + "phase": "head_observation_repair", + "reason": "pull_request_unavailable", + "failure_kind": "server_error", + "recorded_at": "2026-03-01T00:00:00Z", + }) + bot = FakeReviewerBotRuntime(monkeypatch) + bot.ACTIVE_LEASE_CONTEXT = object() + bot.get_issue_or_pr_snapshot = lambda issue_number: {"number": issue_number, "state": "open", "pull_request": {}, "labels": []} + monkeypatch.setattr(maintenance_schedule, "sweep_deferred_gaps", lambda bot, state: False) + monkeypatch.setattr(maintenance_schedule, "repair_missing_reviewer_review_state", lambda bot, issue_number, review_data: False) + monkeypatch.setattr(maintenance_schedule, "maybe_record_head_observation_repair", lambda bot, issue_number, review_data: lifecycle.HeadObservationRepairResult(changed=False, outcome="unchanged")) + monkeypatch.setattr(maintenance_schedule, "check_overdue_reviews", lambda bot, state: []) + + assert maintenance.handle_scheduled_check_result(bot, state).state_changed is True + assert repair_records.load_repair_marker(review, "head_observation_repair") is None diff --git a/tests/unit/reviewer_bot/test_mandatory_approver_policy_equivalence.py b/tests/unit/reviewer_bot/test_mandatory_approver_policy_equivalence.py new file mode 100644 index 000000000..026b04022 --- /dev/null +++ b/tests/unit/reviewer_bot/test_mandatory_approver_policy_equivalence.py @@ -0,0 +1,244 @@ +import json +from copy import deepcopy +from pathlib import Path +from types import SimpleNamespace + +from scripts.reviewer_bot_core import mandatory_approver_policy +from scripts.reviewer_bot_lib import reviews +from tests.fixtures.reviewer_bot import make_state, make_tracked_review_state + + +def _load_matrix() -> dict: + return json.loads( + Path("tests/fixtures/equivalence/mandatory_approver_policy/decision_matrix.json").read_text(encoding="utf-8") + ) + + +def _make_bot(*, ensure_label_exists=True, add_label_result=True, post_comment_result=True, remove_label_error=False): + comments = [] + labels_added = [] + labels_removed = [] + bot = SimpleNamespace( + github=SimpleNamespace( + ensure_label_exists=lambda label: ensure_label_exists, + post_comment=lambda issue_number, body: comments.append((issue_number, body)) or post_comment_result, + ), + add_label_with_status=lambda issue_number, label: labels_added.append((issue_number, label)) or add_label_result, + remove_label_with_status=( + (lambda issue_number, label: (_ for _ in ()).throw(RuntimeError("remove failed"))) + if remove_label_error + else (lambda issue_number, label: labels_removed.append((issue_number, label)) or True) + ), + logger=SimpleNamespace(event=lambda *args, **kwargs: None), + ) + return bot, comments, labels_added, labels_removed + + +def _legacy_trigger_mandatory_approver_escalation(bot, state: dict, issue_number: int) -> bool: + review_data = reviews.review_state.ensure_review_entry(state, issue_number, create=True) + if review_data is None: + return False + now = reviews._now_iso() + state_changed = False + if not review_data.get("mandatory_approver_required"): + review_data["mandatory_approver_required"] = True + review_data["mandatory_approver_satisfied_by"] = None + review_data["mandatory_approver_satisfied_at"] = None + state_changed = True + if bot.github.ensure_label_exists(reviews.MANDATORY_TRIAGE_APPROVER_LABEL): + try: + if bot.add_label_with_status(issue_number, reviews.MANDATORY_TRIAGE_APPROVER_LABEL): + if review_data.get("mandatory_approver_label_applied_at") is None: + review_data["mandatory_approver_label_applied_at"] = now + state_changed = True + except RuntimeError as exc: + reviews._log(bot, "warning", f"Unable to apply escalation label on #{issue_number}: {exc}", issue_number=issue_number, error=str(exc)) + if review_data.get("mandatory_approver_pinged_at") is None: + if bot.github.post_comment(issue_number, reviews.MANDATORY_TRIAGE_ESCALATION_TEMPLATE): + review_data["mandatory_approver_pinged_at"] = now + state_changed = True + return state_changed + + +def _legacy_satisfy_mandatory_approver_requirement(bot, state: dict, issue_number: int, approver: str) -> bool: + review_data = reviews.review_state.ensure_review_entry(state, issue_number, create=True) + if review_data is None or not review_data.get("mandatory_approver_required"): + return False + if review_data.get("mandatory_approver_satisfied_at"): + return False + now = reviews._now_iso() + review_data["mandatory_approver_required"] = False + review_data["mandatory_approver_satisfied_by"] = approver + review_data["mandatory_approver_satisfied_at"] = now + try: + bot.remove_label_with_status(issue_number, reviews.MANDATORY_TRIAGE_APPROVER_LABEL) + except RuntimeError as exc: + reviews._log(bot, "warning", f"Unable to remove escalation label on #{issue_number}: {exc}", issue_number=issue_number, error=str(exc)) + bot.github.post_comment(issue_number, reviews.MANDATORY_TRIAGE_SATISFIED_TEMPLATE.format(approver=approver)) + return True + + +def _build_row(row_id: str): + state = make_state() + review = make_tracked_review_state(state, 42, reviewer="alice") + kwargs = {} + if row_id == "escalation_required_label_apply_ping_required": + return state, reviews.trigger_mandatory_approver_escalation, _legacy_trigger_mandatory_approver_escalation, kwargs + if row_id == "escalation_already_required_label_and_ping_already_recorded": + review["mandatory_approver_required"] = True + review["mandatory_approver_label_applied_at"] = "2026-03-20T09:00:00Z" + review["mandatory_approver_pinged_at"] = "2026-03-20T09:30:00Z" + return state, reviews.trigger_mandatory_approver_escalation, _legacy_trigger_mandatory_approver_escalation, kwargs + if row_id == "satisfaction_allowed_with_label_remove": + review["mandatory_approver_required"] = True + kwargs = {"approver": "carol"} + return state, reviews.satisfy_mandatory_approver_requirement, _legacy_satisfy_mandatory_approver_requirement, kwargs + if row_id == "satisfaction_denied_when_not_required": + kwargs = {"approver": "carol"} + return state, reviews.satisfy_mandatory_approver_requirement, _legacy_satisfy_mandatory_approver_requirement, kwargs + if row_id == "satisfaction_denied_when_already_satisfied": + review["mandatory_approver_required"] = True + review["mandatory_approver_satisfied_at"] = "2026-03-20T10:00:00Z" + kwargs = {"approver": "carol"} + return state, reviews.satisfy_mandatory_approver_requirement, _legacy_satisfy_mandatory_approver_requirement, kwargs + raise AssertionError(f"Unhandled row: {row_id}") + + +def test_h2a_mandatory_approver_matrix_matches_frozen_decision_rows(monkeypatch): + matrix = _load_matrix() + + assert matrix["harness_id"] == "H2a mandatory-approver decision equivalence" + + monkeypatch.setattr(reviews, "_now_iso", lambda: "2026-03-21T12:00:00+00:00") + + for row in matrix["rows"]: + state, current_fn, legacy_fn, kwargs = _build_row(row["id"]) + current_state = deepcopy(state) + legacy_state = deepcopy(state) + current_bot, current_comments, current_labels_added, current_labels_removed = _make_bot() + legacy_bot, legacy_comments, legacy_labels_added, legacy_labels_removed = _make_bot() + + current_result = current_fn(current_bot, current_state, 42, **kwargs) + legacy_result = legacy_fn(legacy_bot, legacy_state, 42, **kwargs) + + assert current_result == row["result"], row["id"] + assert current_result == legacy_result, row["id"] + assert current_state == legacy_state, row["id"] + assert current_comments == legacy_comments, row["id"] + assert current_labels_added == legacy_labels_added, row["id"] + assert current_labels_removed == legacy_labels_removed, row["id"] + + +def test_h2a_mandatory_approver_matrix_fixture_covers_exact_decision_rows(): + matrix = _load_matrix() + + assert [row["id"] for row in matrix["rows"]] == [ + "escalation_required_label_apply_ping_required", + "escalation_already_required_label_and_ping_already_recorded", + "satisfaction_allowed_with_label_remove", + "satisfaction_denied_when_not_required", + "satisfaction_denied_when_already_satisfied", + ] + + +def _legacy_escalation_decision(review_data: dict, *, now: str, label_exists: bool) -> dict[str, object]: + require_escalation = not review_data.get("mandatory_approver_required") + return { + "allow": True, + "require_escalation": require_escalation, + "clear_satisfaction": require_escalation, + "attempt_label_apply": bool(label_exists), + "record_label_applied_at": bool(label_exists) and review_data.get("mandatory_approver_label_applied_at") is None, + "post_ping": review_data.get("mandatory_approver_pinged_at") is None, + "now": now, + } + + +def _legacy_satisfaction_decision(review_data: dict, *, approver: str, now: str) -> dict[str, object]: + if not review_data.get("mandatory_approver_required"): + return {"allow": False} + if review_data.get("mandatory_approver_satisfied_at"): + return {"allow": False} + return { + "allow": True, + "approver": approver, + "now": now, + "attempt_label_remove": True, + "post_comment": True, + } + + +def test_h2b_mandatory_approver_policy_matches_legacy_decision_rows(): + matrix = _load_matrix() + now = "2026-03-21T12:00:00+00:00" + + for row in matrix["rows"]: + state, _, _, kwargs = _build_row(row["id"]) + review_data = deepcopy(state["active_reviews"]["42"]) + if row["id"].startswith("escalation_"): + assert mandatory_approver_policy.decide_mandatory_approver_escalation( + review_data, + now=now, + label_exists=True, + ) == _legacy_escalation_decision(review_data, now=now, label_exists=True) + else: + assert mandatory_approver_policy.decide_mandatory_approver_satisfaction( + review_data, + approver=str(kwargs["approver"]), + now=now, + ) == _legacy_satisfaction_decision(review_data, approver=str(kwargs["approver"]), now=now) + + +def test_h2b_mandatory_approver_execution_support_uses_core_decision_and_retains_side_effects(monkeypatch): + state = make_state() + make_tracked_review_state(state, 42, reviewer="alice") + bot, comments, labels_added, _ = _make_bot() + decision_inputs = {} + + monkeypatch.setattr(reviews, "_now_iso", lambda: "2026-03-21T12:00:00+00:00") + monkeypatch.setattr( + mandatory_approver_policy, + "decide_mandatory_approver_escalation", + lambda review_data, *, now, label_exists: decision_inputs.update( + { + "current_required": review_data.get("mandatory_approver_required"), + "now": now, + "label_exists": label_exists, + } + ) + or { + "allow": True, + "require_escalation": True, + "clear_satisfaction": True, + "attempt_label_apply": True, + "record_label_applied_at": True, + "post_ping": True, + "now": now, + }, + ) + + changed = reviews.trigger_mandatory_approver_escalation(bot, state, 42) + + assert changed is True + assert decision_inputs == { + "current_required": False, + "now": "2026-03-21T12:00:00+00:00", + "label_exists": True, + } + assert labels_added == [(42, reviews.MANDATORY_TRIAGE_APPROVER_LABEL)] + assert comments == [(42, reviews.MANDATORY_TRIAGE_ESCALATION_TEMPLATE)] + + +def test_h2c_handle_pr_approved_review_has_zero_external_callers(): + reviews_text = Path("scripts/reviewer_bot_lib/reviews.py").read_text(encoding="utf-8") + callers = [] + + for path in Path("scripts").rglob("*.py"): + if path.as_posix() == "scripts/reviewer_bot_lib/reviews.py": + continue + text = path.read_text(encoding="utf-8") + if "handle_pr_approved_review(" in text or "from .reviews import handle_pr_approved_review" in text: + callers.append(path.as_posix()) + + assert callers == [] + assert "def handle_pr_approved_review(" not in reviews_text diff --git a/tests/unit/reviewer_bot/test_members.py b/tests/unit/reviewer_bot/test_members.py new file mode 100644 index 000000000..2f7d44a56 --- /dev/null +++ b/tests/unit/reviewer_bot/test_members.py @@ -0,0 +1,53 @@ +from scripts.reviewer_bot_lib import members, queue +from tests.fixtures.fake_runtime import FakeReviewerBotRuntime + + +class TextResponse: + def __init__(self, status_code: int, text: str): + self.status_code = status_code + self.text = text + + +def test_fetch_members_parses_producers_from_members_table(monkeypatch): + runtime = FakeReviewerBotRuntime(monkeypatch) + runtime.rest_transport.stub( + lambda **kwargs: TextResponse( + 200, + """ +| Member Name | Role | GitHub Username | +| --- | --- | --- | +| Alice Example | Producer | @alice | +| Bob Example | Observer | @bob | +| Carol Example | Producer | carol | +""", + ) + ) + + result = members.fetch_members(runtime) + + assert result.ok is True + assert result.producers == [ + {"github": "alice", "name": "Alice Example"}, + {"github": "carol", "name": "Carol Example"}, + ] + + +def test_fetch_members_logs_warning_and_returns_empty_list_on_failure(monkeypatch): + runtime = FakeReviewerBotRuntime(monkeypatch) + runtime.rest_transport.stub(lambda **kwargs: (_ for _ in ()).throw(RuntimeError("timeout"))) + + result = members.fetch_members(runtime) + + assert result.ok is False + assert result.producers == [] + + +def test_queue_sync_members_with_queue_uses_runtime_fetch_members(monkeypatch): + runtime = FakeReviewerBotRuntime(monkeypatch) + runtime.stub_fetch_members(lambda: [{"github": "alice", "name": "Alice Example"}]) + state = {"queue": [], "pass_until": [], "current_index": 0} + + updated, changes = queue.sync_members_with_queue(runtime, state) + + assert updated["queue"] == [{"github": "alice", "name": "Alice Example"}] + assert changes == ["Added alice to queue"] diff --git a/tests/unit/reviewer_bot/test_overdue.py b/tests/unit/reviewer_bot/test_overdue.py new file mode 100644 index 000000000..2258a6b0a --- /dev/null +++ b/tests/unit/reviewer_bot/test_overdue.py @@ -0,0 +1,196 @@ +from datetime import timedelta + +from scripts.reviewer_bot_core import approval_policy +from scripts.reviewer_bot_lib import maintenance, review_state +from tests.fixtures.fake_runtime import FakeReviewerBotRuntime +from tests.fixtures.reviewer_bot import ( + accept_contributor_comment, + accept_contributor_revision, + accept_reviewer_review, + iso_z, + issue_snapshot, + make_state, + make_tracked_review_state, + pull_request_payload, + review_payload, +) +from tests.fixtures.reviewer_bot_fakes import RouteGitHubApi + + +def _runtime(monkeypatch, routes=None): + runtime = FakeReviewerBotRuntime(monkeypatch) + runtime.get_issue_or_pr_snapshot = lambda issue_number: issue_snapshot(issue_number, state="open", is_pull_request=True) + runtime.get_user_permission_status = lambda username, required_permission="push": "granted" + if routes is not None: + runtime.github.stub(routes) + return runtime + + +def _approval_incomplete_result(*_args, **_kwargs): + return { + "ok": True, + "completion": {"completed": False}, + "write_approval": {"has_write_approval": False}, + "current_head_sha": "head-1", + } + + +def test_check_overdue_reviews_skips_pr_with_current_head_reviewer_review(monkeypatch): + state = make_state() + review = make_tracked_review_state(state, 42, reviewer="alice", assigned_at="2026-03-01T00:00:00Z", active_cycle_started_at="2026-03-01T00:00:00Z") + accept_reviewer_review(review, semantic_key="pull_request_review:10", timestamp="2026-03-02T00:00:00Z", actor="alice", reviewed_head_sha="head-1") + routes = RouteGitHubApi().add_pull_request_snapshot(42, pull_request_payload(42, head_sha="head-1")) + runtime = _runtime(monkeypatch, routes) + monkeypatch.setattr(approval_policy, "compute_pr_approval_state_result", _approval_incomplete_result) + + assert maintenance.check_overdue_reviews(runtime, state) == [] + + +def test_check_overdue_reviews_consumes_only_stable_reviewer_response_fields(monkeypatch): + runtime = FakeReviewerBotRuntime(monkeypatch) + now = runtime.datetime.now(runtime.timezone.utc) + anchor_timestamp = iso_z(now - timedelta(days=runtime.REVIEW_DEADLINE_DAYS + 1)) + state = make_state() + make_tracked_review_state(state, 42, reviewer="alice", assigned_at=anchor_timestamp, active_cycle_started_at=anchor_timestamp) + runtime.get_issue_or_pr_snapshot = lambda issue_number: issue_snapshot(issue_number, state="open", is_pull_request=True) + runtime.adapters.review_state.compute_reviewer_response_state = lambda issue_number, review_data, **kwargs: { + "state": "awaiting_reviewer_response", + "anchor_timestamp": anchor_timestamp, + "ignored": {"reviewer_review": "not consumed"}, + } + + overdue = maintenance.check_overdue_reviews(runtime, state) + + assert overdue == [ + { + "issue_number": 42, + "reviewer": "alice", + "days_overdue": 1, + "days_since_warning": 0, + "needs_warning": True, + "needs_transition": False, + } + ] + + +def test_check_overdue_reviews_skips_item_when_snapshot_unavailable(monkeypatch): + state = make_state() + review = review_state.ensure_review_entry(state, 42, create=True) + assert review is not None + review["current_reviewer"] = "alice" + review["assigned_at"] = "2026-03-01T00:00:00Z" + review["last_reviewer_activity"] = "2026-03-01T00:00:00Z" + runtime = FakeReviewerBotRuntime(monkeypatch) + runtime.get_issue_or_pr_snapshot = lambda issue_number: None + + assert maintenance.check_overdue_reviews(runtime, state) == [] + + +def test_handle_overdue_review_warning_only_records_successful_comment(monkeypatch): + state = make_state() + review = review_state.ensure_review_entry(state, 42, create=True) + assert review is not None + runtime = FakeReviewerBotRuntime(monkeypatch) + runtime.post_comment = lambda issue_number, body: False + + assert maintenance.handle_overdue_review_warning(runtime, state, 42, "alice") is False + assert review["transition_warning_sent"] is None + + +def test_check_overdue_reviews_uses_contributor_comment_timestamp_when_turn_returns_to_reviewer(monkeypatch): + runtime = FakeReviewerBotRuntime(monkeypatch) + now = runtime.datetime.now(runtime.timezone.utc) + assigned_at = iso_z(now - timedelta(days=runtime.REVIEW_DEADLINE_DAYS + 20)) + reviewer_review_at = iso_z(now - timedelta(days=runtime.REVIEW_DEADLINE_DAYS + 19)) + contributor_comment_at = iso_z(now - timedelta(days=runtime.REVIEW_DEADLINE_DAYS, minutes=1)) + state = make_state() + review = make_tracked_review_state(state, 42, reviewer="alice", assigned_at=assigned_at, active_cycle_started_at=assigned_at) + accept_reviewer_review(review, semantic_key="pull_request_review:10", timestamp=reviewer_review_at, actor="alice", reviewed_head_sha="head-1") + accept_contributor_comment(review, semantic_key="issue_comment:20", timestamp=contributor_comment_at, actor="bob") + routes = RouteGitHubApi().add_pull_request_snapshot(42, pull_request_payload(42, head_sha="head-1")) + runtime = _runtime(monkeypatch, routes) + monkeypatch.setattr(approval_policy, "compute_pr_approval_state_result", _approval_incomplete_result) + + overdue = maintenance.check_overdue_reviews(runtime, state) + assert overdue[0]["issue_number"] == 42 + assert overdue[0]["needs_warning"] is True + assert overdue[0]["days_overdue"] == 0 + + +def test_check_overdue_reviews_uses_contributor_revision_timestamp_when_head_changes_after_review(monkeypatch): + runtime = FakeReviewerBotRuntime(monkeypatch) + now = runtime.datetime.now(runtime.timezone.utc) + assigned_at = iso_z(now - timedelta(days=runtime.REVIEW_DEADLINE_DAYS + 20)) + reviewer_review_at = iso_z(now - timedelta(days=runtime.REVIEW_DEADLINE_DAYS + 19)) + contributor_revision_at = iso_z(now - timedelta(days=runtime.REVIEW_DEADLINE_DAYS, minutes=1)) + state = make_state() + review = make_tracked_review_state(state, 42, reviewer="alice", assigned_at=assigned_at, active_cycle_started_at=assigned_at) + accept_reviewer_review(review, semantic_key="pull_request_review:10", timestamp=reviewer_review_at, actor="alice", reviewed_head_sha="head-1") + accept_contributor_revision(review, semantic_key="pull_request_sync:42:head-2", timestamp=contributor_revision_at, actor="alice", head_sha="head-2") + routes = RouteGitHubApi().add_pull_request_snapshot(42, pull_request_payload(42, head_sha="head-2")).add_pull_request_reviews(42, []) + runtime = _runtime(monkeypatch, routes) + + overdue = maintenance.check_overdue_reviews(runtime, state) + assert overdue[0]["issue_number"] == 42 + assert overdue[0]["needs_warning"] is True + assert overdue[0]["days_overdue"] == 0 + + +def test_check_overdue_reviews_ignores_same_head_contributor_revision_after_valid_reviewer_review(monkeypatch): + state = make_state() + review = make_tracked_review_state(state, 42, reviewer="alice", assigned_at="2026-03-01T00:00:00Z", active_cycle_started_at="2026-03-01T00:00:00Z") + accept_reviewer_review(review, semantic_key="pull_request_review:10", timestamp="2026-03-02T00:00:00Z", actor="alice", reviewed_head_sha="head-1") + accept_contributor_revision(review, semantic_key="pull_request_head_observed:42:head-1", timestamp="2026-03-12T00:00:00Z", actor="alice", head_sha="head-1") + routes = RouteGitHubApi().add_pull_request_snapshot(42, pull_request_payload(42, head_sha="head-1")).add_pull_request_reviews(42, []) + runtime = _runtime(monkeypatch, routes) + monkeypatch.setattr(approval_policy, "compute_pr_approval_state_result", _approval_incomplete_result) + + assert maintenance.check_overdue_reviews(runtime, state) == [] + + +def test_check_overdue_reviews_uses_live_current_head_review_when_stored_review_is_stale(monkeypatch): + state = make_state() + review = make_tracked_review_state(state, 42, reviewer="alice", assigned_at="2026-03-01T00:00:00Z", active_cycle_started_at="2026-03-01T00:00:00Z") + accept_reviewer_review(review, semantic_key="pull_request_review:99", timestamp="2026-03-02T00:00:00Z", actor="alice", reviewed_head_sha="head-0") + routes = RouteGitHubApi().add_pull_request_snapshot(42, pull_request_payload(42, head_sha="head-1")).add_pull_request_reviews( + 42, + [ + review_payload(10, state="COMMENTED", submitted_at="2026-03-20T00:00:00Z", commit_id="head-1", author="alice"), + review_payload(99, state="COMMENTED", submitted_at="2026-03-21T00:00:00Z", commit_id="head-0", author="alice"), + ], + ) + runtime = _runtime(monkeypatch, routes) + monkeypatch.setattr(approval_policy, "compute_pr_approval_state_result", _approval_incomplete_result) + + assert maintenance.check_overdue_reviews(runtime, state) == [] + + +def test_refresh_reviewer_review_from_live_preferred_review_does_not_clear_transition_warning_when_activity_not_advanced(monkeypatch): + review = make_tracked_review_state(make_state(), 42, reviewer="alice", active_cycle_started_at="2026-03-17T09:00:00Z") + review["reviewer_review"] = { + "accepted": { + "semantic_key": "pull_request_review:10", + "timestamp": "2026-03-17T10:01:00Z", + "actor": "alice", + "reviewed_head_sha": "head-1", + "source_precedence": 1, + "payload": {}, + }, + "seen_keys": ["pull_request_review:10"], + } + review["last_reviewer_activity"] = "2026-03-17T10:01:00Z" + review["transition_warning_sent"] = "2026-04-01T12:12:04Z" + review["transition_notice_sent_at"] = "2026-04-15T12:12:04Z" + routes = RouteGitHubApi().add_request("GET", "pulls/42", status_code=200, payload=pull_request_payload(42, head_sha="head-1")).add_pull_request_reviews( + 42, + [{"id": 10, "state": "COMMENTED", "submitted_at": "2026-03-17T10:01:00Z", "commit_id": "head-1", "user": {"login": "alice"}}], + ) + runtime = _runtime(monkeypatch, routes) + + changed, preferred_review = review_state.refresh_reviewer_review_from_live_preferred_review(runtime, 42, review) + + assert changed is False + assert preferred_review is not None + assert review["last_reviewer_activity"] == "2026-03-17T10:01:00Z" + assert review["transition_warning_sent"] == "2026-04-01T12:12:04Z" + assert review["transition_notice_sent_at"] == "2026-04-15T12:12:04Z" diff --git a/tests/unit/reviewer_bot/test_privileged_command_planning_equivalence.py b/tests/unit/reviewer_bot/test_privileged_command_planning_equivalence.py new file mode 100644 index 000000000..cfc107c1f --- /dev/null +++ b/tests/unit/reviewer_bot/test_privileged_command_planning_equivalence.py @@ -0,0 +1,246 @@ +import json +from pathlib import Path + +from scripts.reviewer_bot_core import privileged_command_policy +from tests.fixtures.comment_routing_harness import CommentRoutingHarness + +C6D_DELETION_MANIFEST = [ + "embedded PR title/body fallback construction in automation.create_pull_request", + "embedded branch template derivation at rev-parse call site in handle_accept_no_fls_changes_command", +] + + +def _load_matrix() -> dict: + return json.loads( + Path("tests/fixtures/equivalence/privileged_command_planning/contract_matrix.json").read_text( + encoding="utf-8" + ) + ) + + +def test_privileged_command_planning_contract_matrix_exists_and_lists_ordered_plan_outputs(): + matrix = _load_matrix() + + assert matrix["harness_id"] == "C6a privileged command planning contract matrix" + assert matrix["ordered_execution_steps"] == [ + "working_tree_clean_check", + "audit_no_impact_check", + "spec_lock_update", + "changed_files_validation", + "branch_name_derivation", + "branch_existence_check", + "branch_creation", + "git_add_spec_lock", + "git_commit", + "git_push", + "pull_request_create", + ] + + +def test_privileged_command_planning_contract_matrix_freezes_messages_revalidation_and_handoff_metadata(): + matrix = _load_matrix() + + assert matrix["commit_message"] == "chore: update spec.lock; no affected guidelines" + assert matrix["pull_request_title"] == "chore: update spec.lock (no guideline impact)" + assert matrix["branch_name_format"] == "chore/spec-lock--issue-" + assert matrix["revalidation_checkpoints"] == [ + "issue_not_pull_request", + "fls_audit_label_present", + "triage_permission_granted", + "working_tree_clean_before_update", + "audit_no_impact_before_update", + "changed_files_exact_after_update", + ] + assert matrix["handoff_fail_closed_reasons"] == [ + "pull_request_target_not_allowed", + "missing_fls_audit_label", + "authorization_unavailable", + "authorization_failed", + ] + assert matrix["revalidation_fail_closed_reasons"] == [ + "unsupported_command", + "pull_request_target_not_allowed", + "missing_fls_audit_label", + "authorization_unavailable", + "authorization_failed", + ] + assert matrix["execution_result_codes"] == [ + "working_tree_not_clean", + "audit_reported_guideline_impact", + "audit_failed", + "update_failed", + "already_up_to_date", + "unexpected_changed_files", + "branch_or_push_failed", + "pull_request_creation_failed", + "opened_pull_request", + ] + assert matrix["pending_command_metadata_keys"] == [ + "source_event_key", + "command_name", + "issue_number", + "actor", + "authorization_required_permission", + "authorization_authorized", + "target_kind", + "target_number", + "target_labels_snapshot", + "status", + "created_at", + "completed_at", + "result_code", + "result_message", + "opened_pr_url", + ] + assert matrix["execute_plan_keys"] == ["record", "execution_context"] + assert matrix["execute_plan_record_keys"] == matrix["pending_command_metadata_keys"] + assert matrix["execute_plan_execution_context_keys"] == [ + "target_repo_root", + "base_branch", + "branch_probe_name", + "branch_name", + "expected_changed_files", + "existing_open_pr_url", + ] + assert matrix["trusted_acknowledgment_comment"].startswith("✅ Recorded pending privileged command") + + +def test_privileged_command_policy_produces_frozen_handoff_metadata_and_ordered_plan(monkeypatch): + harness = CommentRoutingHarness(monkeypatch) + request = harness.request( + issue_number=42, + is_pull_request=False, + comment_author="alice", + comment_body="@guidelines-bot /accept-no-fls-changes", + ) + + request = request.__class__(**{**request.__dict__, "issue_labels": ("fls-audit",)}) + handoff = privileged_command_policy.validate_accept_no_fls_changes_handoff( + request, + "granted", + source_event_key="issue_comment:100", + ) + pending = privileged_command_policy.build_pending_privileged_command( + created_at="2026-04-06T12:34:56+00:00", + handoff=handoff, + ) + plan = privileged_command_policy.plan_accept_no_fls_changes_execution( + issue_number=42, + audit_returncode=0, + audit_details="", + update_returncode=0, + update_details="", + changed_files_after=["src/spec.lock"], + branch_date="2026-04-06", + base_branch="main", + branch_exists=False, + ) + + assert isinstance(handoff, privileged_command_policy.AllowedPrivilegedHandoff) + assert list(pending.__dict__) == [ + "source_event_key", + "command_name", + "issue_number", + "actor", + "authorization_required_permission", + "authorization_authorized", + "target_kind", + "target_number", + "target_labels_snapshot", + "status", + "created_at", + "completed_at", + "result_code", + "result_message", + "opened_pr_url", + ] + assert plan.ordered_steps == privileged_command_policy.ORDERED_EXECUTION_STEPS + assert plan.revalidation_checkpoints == privileged_command_policy.REVALIDATION_CHECKPOINTS + assert plan.expected_changed_files == ["src/spec.lock"] + assert plan.branch_probe_name == "chore/spec-lock-2026-04-06-issue-42" + assert plan.commit_message == privileged_command_policy.COMMIT_MESSAGE + assert plan.pull_request_title == privileged_command_policy.PR_TITLE + assert plan.git_checkout_args == ["git", "checkout", "-b", "chore/spec-lock-2026-04-06-issue-42"] + assert plan.git_push_args == ["git", "push", "origin", "chore/spec-lock-2026-04-06-issue-42"] + + +def test_privileged_command_policy_prevalidation_and_revalidation_return_typed_execution_handoff(): + preflight = privileged_command_policy.prevalidate_accept_no_fls_changes_request( + type( + "Request", + (), + { + "is_pull_request": False, + "issue_labels": ("fls-audit",), + "issue_number": 42, + "actor": "alice", + }, + )(), + "granted", + [], + ) + revalidation = privileged_command_policy.revalidate_pending_accept_no_fls_changes( + privileged_command_policy.PendingAcceptNoFlsChangesRecord( + source_event_key="issue_comment:100", + command_name=privileged_command_policy.PrivilegedCommandId.ACCEPT_NO_FLS_CHANGES.value, + issue_number=42, + actor="alice", + authorization_required_permission="triage", + authorization_authorized=True, + target_kind="issue", + target_number=42, + target_labels_snapshot=("fls-audit",), + status="pending", + created_at="2026-04-06T12:34:56+00:00", + ), + {"number": 42, "labels": [{"name": "fls-audit"}]}, + "granted", + target_repo_root="/tmp/repo", + ) + + assert isinstance(preflight, privileged_command_policy.ExecutePrivilegedPlan) + assert list(preflight.__dict__) == ["record", "execution_context"] + assert preflight.record.command_name == "accept-no-fls-changes" + assert preflight.record.target_labels_snapshot == ("fls-audit",) + assert preflight.execution_context.target_repo_root == "" + assert isinstance(revalidation, privileged_command_policy.ExecutePrivilegedPlan) + assert revalidation.record.command_name == "accept-no-fls-changes" + assert revalidation.record.target_labels_snapshot == ("fls-audit",) + assert revalidation.execution_context.target_repo_root == "/tmp/repo" + + +def test_j1_privileged_policy_derives_branch_probe_and_final_plan_names_explicitly(): + assert privileged_command_policy.derive_accept_no_fls_changes_branch_name( + issue_number=42, + branch_date="2026-04-06", + ) == "chore/spec-lock-2026-04-06-issue-42" + assert privileged_command_policy.derive_accept_no_fls_changes_branch_name( + issue_number=42, + branch_date="2026-04-06", + branch_suffix="123456", + ) == "chore/spec-lock-2026-04-06-issue-42-123456" + + +def test_j1_privileged_policy_separates_post_update_assessment_from_final_plan_construction(): + assessment = privileged_command_policy.assess_accept_no_fls_changes_post_update( + audit_returncode=0, + audit_details="", + update_returncode=0, + update_details="", + changed_files_after=["src/spec.lock"], + ) + + assert assessment is None + + +def test_c6d_deletion_manifest_proves_automation_no_longer_embeds_removed_planning_logic(): + module_text = Path("scripts/reviewer_bot_lib/automation.py").read_text(encoding="utf-8") + + assert C6D_DELETION_MANIFEST == [ + "embedded PR title/body fallback construction in automation.create_pull_request", + "embedded branch template derivation at rev-parse call site in handle_accept_no_fls_changes_command", + ] + assert "title = title or privileged_command_policy.PR_TITLE" not in module_text + assert 'f"chore/spec-lock-{branch_date}-issue-{issue_number}"' not in module_text + assert "def _resolve_accept_no_fls_changes_plan(" in module_text + assert '["git", "rev-parse", "--verify", provisional.branch_probe_name]' in module_text diff --git a/tests/unit/reviewer_bot/test_project_board.py b/tests/unit/reviewer_bot/test_project_board.py new file mode 100644 index 000000000..d366afeea --- /dev/null +++ b/tests/unit/reviewer_bot/test_project_board.py @@ -0,0 +1,207 @@ +import json +from pathlib import Path + +from scripts.reviewer_bot_lib import project_board, reviews +from scripts.reviewer_bot_lib.config import ( + REVIEWER_BOARD_FIELD_NEEDS_ATTENTION, + REVIEWER_BOARD_OPTION_ATTENTION_PROJECTION_REPAIR_REQUIRED, + REVIEWER_BOARD_OPTION_AWAITING_CONTRIBUTOR, + STATUS_AWAITING_CONTRIBUTOR_RESPONSE_LABEL, +) +from tests.fixtures.fake_runtime import FakeReviewerBotRuntime +from tests.fixtures.reviewer_bot import ( + accept_reviewer_comment, + accept_reviewer_review, + issue_snapshot, + make_state, + make_tracked_review_state, + pull_request_payload, + review_payload, + valid_reviewer_board_metadata, +) +from tests.fixtures.reviewer_bot_fakes import RouteGitHubApi + + +def _runtime(monkeypatch, routes=None): + runtime = FakeReviewerBotRuntime(monkeypatch) + if routes is not None: + runtime.github.stub(routes) + return runtime + + +def test_reviewer_board_preflight_validates_manifest(monkeypatch): + runtime = _runtime(monkeypatch) + runtime.set_config_value("REVIEWER_BOARD_ENABLED", "true") + runtime.set_config_value("REVIEWER_BOARD_TOKEN", "board-token") + runtime.github_graphql = lambda query, variables=None, *, token=None: valid_reviewer_board_metadata() + + preflight = project_board.reviewer_board_preflight(runtime) + + assert preflight.enabled is True + assert preflight.valid is True + assert preflight.project_id == "PVT_kwDOB" + + +def test_reviewer_board_preflight_is_disabled_without_runtime_flag(monkeypatch): + runtime = _runtime(monkeypatch) + + preflight = project_board.reviewer_board_preflight(runtime) + + assert preflight.enabled is False + assert preflight.valid is True + assert preflight.project_id is None + + +def test_preview_board_projection_valid_manifest_yields_preview_output(monkeypatch): + state = make_state() + make_tracked_review_state(state, 42, reviewer="alice", assigned_at="2026-03-20T12:34:56Z", active_cycle_started_at="2026-03-20T12:34:56Z") + runtime = _runtime(monkeypatch) + runtime.get_issue_or_pr_snapshot = lambda issue_number: issue_snapshot(issue_number, state="open") + + preview = project_board.preview_board_projection_for_item(runtime, state, 42) + + assert preview.classification == "open_tracked_assigned" + assert preview.eligible is True + assert preview.desired is not None + assert preview.desired.review_state == "Awaiting Reviewer" + assert preview.desired.reviewer == "alice" + + +def test_preview_board_projection_consumes_only_stable_reviewer_response_fields(monkeypatch): + state = make_state() + make_tracked_review_state(state, 42, reviewer="alice", assigned_at="2026-03-20T12:34:56Z", active_cycle_started_at="2026-03-20T12:34:56Z") + runtime = _runtime(monkeypatch) + runtime.get_issue_or_pr_snapshot = lambda issue_number: issue_snapshot(issue_number, state="open", is_pull_request=True) + runtime.adapters.review_state.compute_reviewer_response_state = lambda issue_number, review_data, **kwargs: { + "state": "awaiting_reviewer_response", + "anchor_timestamp": "2026-03-21T08:00:00Z", + "reason": "review_head_stale", + "ignored": {"contributor_handoff": "not consumed"}, + } + + preview = project_board.preview_board_projection_for_item(runtime, state, 42) + + assert preview.desired is not None + assert preview.desired.review_state == "Awaiting Reviewer" + assert preview.desired.waiting_since == "2026-03-21" + + +def test_preview_board_projection_tracked_unassigned_maps_to_unassigned(monkeypatch): + state = make_state() + make_tracked_review_state(state, 42) + runtime = _runtime(monkeypatch) + runtime.get_issue_or_pr_snapshot = lambda issue_number: issue_snapshot(issue_number, state="open") + + preview = project_board.preview_board_projection_for_item(runtime, state, 42) + + assert preview.classification == "open_tracked_unassigned" + assert preview.desired is not None + assert preview.desired.review_state == "Unassigned" + assert preview.desired.reviewer is None + + +def test_preview_board_projection_closed_item_maps_to_archive_intent(monkeypatch): + state = make_state() + make_tracked_review_state(state, 42, reviewer="alice") + runtime = _runtime(monkeypatch) + runtime.get_issue_or_pr_snapshot = lambda issue_number: issue_snapshot(issue_number, state="closed") + + preview = project_board.preview_board_projection_for_item(runtime, state, 42) + + assert preview.classification == "closed" + assert preview.desired is not None + assert preview.desired.archive is True + assert preview.desired.ensure_membership is False + + +def test_preview_board_projection_open_untracked_maps_to_archive_intent(monkeypatch): + state = make_state() + runtime = _runtime(monkeypatch) + runtime.get_issue_or_pr_snapshot = lambda issue_number: issue_snapshot(issue_number, state="open") + + preview = project_board.preview_board_projection_for_item(runtime, state, 42) + + assert preview.classification == "open_untracked" + assert preview.desired is not None + assert preview.desired.archive is True + + +def test_preview_board_projection_formats_dates_at_day_granularity(monkeypatch): + state = make_state() + review = make_tracked_review_state(state, 42, reviewer="alice", assigned_at="2026-03-20T12:34:56Z", active_cycle_started_at="2026-03-20T12:34:56Z") + accept_reviewer_comment(review, semantic_key="issue_comment:1", timestamp="2026-03-21T08:00:00Z", actor="alice") + accept_reviewer_review(review, semantic_key="pull_request_review:10", timestamp="2026-03-21T08:00:00Z", actor="alice", reviewed_head_sha="head-1", source_precedence=1) + routes = RouteGitHubApi().add_pull_request_snapshot(42, pull_request_payload(42, head_sha="head-1")) + routes.add_pull_request_reviews(42, []) + runtime = _runtime(monkeypatch, routes) + runtime.get_issue_or_pr_snapshot = lambda issue_number: issue_snapshot(issue_number, state="open", is_pull_request=True) + runtime.adapters.review_state.compute_reviewer_response_state = lambda issue_number, review_data, **kwargs: { + "state": "awaiting_reviewer_response", + "anchor_timestamp": "2026-03-21T08:00:00Z", + } + + preview = project_board.preview_board_projection_for_item(runtime, state, 42) + + assert preview.desired is not None + assert preview.desired.assigned_at == "2026-03-20" + assert preview.desired.waiting_since == "2026-03-21" + + +def test_preview_board_projection_keeps_parity_with_refreshed_live_review_state(monkeypatch): + state = make_state() + review = make_tracked_review_state(state, 42, reviewer="alice", assigned_at="2026-03-17T09:00:00Z", active_cycle_started_at="2026-03-17T09:00:00Z") + accept_reviewer_review(review, semantic_key="pull_request_review:99", timestamp="2026-03-17T11:00:00Z", actor="alice", reviewed_head_sha="head-0", source_precedence=1) + routes = RouteGitHubApi().add_pull_request_snapshot(42, pull_request_payload(42, head_sha="head-1")).add_pull_request_reviews( + 42, + [ + review_payload(10, state="COMMENTED", submitted_at="2026-03-17T10:01:00Z", commit_id="head-1", author="alice"), + review_payload(99, state="COMMENTED", submitted_at="2026-03-17T11:00:00Z", commit_id="head-0", author="alice"), + ], + ) + runtime = _runtime(monkeypatch, routes) + runtime.get_issue_or_pr_snapshot = lambda issue_number: issue_snapshot(issue_number, state="open", is_pull_request=True) + runtime.get_user_permission_status = lambda username, required_permission="push": "granted" + + desired_labels, _ = reviews.project_status_labels_for_item(runtime, 42, state) + preview = project_board.preview_board_projection_for_item(runtime, state, 42) + + assert desired_labels == {STATUS_AWAITING_CONTRIBUTOR_RESPONSE_LABEL} + assert preview.desired is not None + assert preview.desired.review_state == REVIEWER_BOARD_OPTION_AWAITING_CONTRIBUTOR + + +def test_preview_board_projection_marks_projection_repair_as_attention(monkeypatch): + state = make_state() + make_tracked_review_state( + state, + 42, + reviewer="alice", + assigned_at="2026-03-20T12:34:56Z", + active_cycle_started_at="2026-03-20T12:34:56Z", + repair_needed={"kind": "projection_failure", "reason": "projection_failed"}, + ) + runtime = _runtime(monkeypatch) + runtime.get_issue_or_pr_snapshot = lambda issue_number: issue_snapshot(issue_number, state="open") + + preview = project_board.preview_board_projection_for_item(runtime, state, 42) + + assert preview.desired is not None + assert preview.desired.needs_attention == REVIEWER_BOARD_OPTION_ATTENTION_PROJECTION_REPAIR_REQUIRED + + +def test_reviewer_board_manifest_includes_projection_repair_attention_option(): + options = project_board.REVIEWER_BOARD_PROJECT_MANIFEST[REVIEWER_BOARD_FIELD_NEEDS_ATTENTION]["options"] + + assert REVIEWER_BOARD_OPTION_ATTENTION_PROJECTION_REPAIR_REQUIRED in options + + +def test_h2a_mandatory_approver_matrix_stays_focused_on_mandatory_approver_decisions(): + matrix = json.loads( + Path("tests/fixtures/equivalence/mandatory_approver_policy/decision_matrix.json").read_text(encoding="utf-8") + ) + + assert matrix["harness_id"] == "H2a mandatory-approver decision equivalence" + assert matrix["out_of_scope"] == [ + "reviewer-response derivation", + "label writes outside mandatory approver decisions", + ] diff --git a/tests/unit/reviewer_bot/test_reconcile_artifacts.py b/tests/unit/reviewer_bot/test_reconcile_artifacts.py new file mode 100644 index 000000000..8a8c58283 --- /dev/null +++ b/tests/unit/reviewer_bot/test_reconcile_artifacts.py @@ -0,0 +1,155 @@ +from types import SimpleNamespace + +import pytest + +from scripts.reviewer_bot_lib import ( + reconcile, + reconcile_payloads, + reconcile_reads, + review_state, +) +from scripts.reviewer_bot_lib.config import GitHubApiResult +from tests.fixtures.fake_runtime import FakeReviewerBotRuntime +from tests.fixtures.reconcile_harness import review_comment_payload +from tests.fixtures.reviewer_bot import make_state +from tests.fixtures.reviewer_bot_fakes import RouteGitHubApi, github_result + + +def test_review_comment_artifact_identity_validation(monkeypatch): + state = make_state() + review = review_state.ensure_review_entry(state, 42, create=True) + assert review is not None + review["current_reviewer"] = "alice" + runtime = FakeReviewerBotRuntime(monkeypatch) + runtime.stub_deferred_payload( + review_comment_payload( + pr_number=42, + comment_id=304, + source_event_key="pull_request_review_comment:304", + body="review comment body", + comment_class="plain_text", + has_non_command_text=True, + source_created_at="2026-03-17T10:00:00Z", + actor_login="alice", + actor_id=6, + actor_class="repo_user_principal", + pull_request_review_id=10, + in_reply_to_id=200, + source_run_id=704, + source_run_attempt=1, + ) + ) + runtime.set_config_value("WORKFLOW_RUN_TRIGGERING_NAME", "Reviewer Bot PR Review Comment Observer") + runtime.set_config_value("WORKFLOW_RUN_TRIGGERING_ID", "704") + runtime.set_config_value("WORKFLOW_RUN_TRIGGERING_ATTEMPT", "1") + runtime.set_config_value("WORKFLOW_RUN_TRIGGERING_CONCLUSION", "success") + routes = ( + RouteGitHubApi() + .add_request("GET", "pulls/42", status_code=200, payload={"user": {"login": "dana"}, "labels": []}) + .add_request( + "GET", + "pulls/comments/304", + status_code=200, + payload={ + "body": "review comment body", + "user": {"login": "alice", "type": "User"}, + "author_association": "MEMBER", + "performed_via_github_app": None, + "created_at": "2026-03-17T10:00:00Z", + }, + ) + ) + runtime.github.stub(routes) + + runtime.ACTIVE_LEASE_CONTEXT = object() + + assert reconcile.handle_workflow_run_event_result(runtime, state).state_changed is True + + +def test_validate_workflow_run_artifact_identity_accepts_matching_contract(): + bot = SimpleNamespace( + get_config_value=lambda name, default="": { + "WORKFLOW_RUN_TRIGGERING_NAME": "Reviewer Bot PR Comment Observer", + "WORKFLOW_RUN_TRIGGERING_ID": "610", + "WORKFLOW_RUN_TRIGGERING_ATTEMPT": "1", + "WORKFLOW_RUN_TRIGGERING_CONCLUSION": "success", + }.get(name, default), + ) + payload = { + "source_event_name": "issue_comment", + "source_event_action": "created", + "source_workflow_name": "Reviewer Bot PR Comment Observer", + "source_workflow_file": ".github/workflows/reviewer-bot-pr-comment-observer.yml", + "source_run_id": 610, + "source_run_attempt": 1, + } + + reconcile_payloads.validate_workflow_run_artifact_identity(bot, payload) + + +def test_read_reconcile_object_fails_closed_for_unavailable(monkeypatch): + runtime = FakeReviewerBotRuntime(monkeypatch) + routes = RouteGitHubApi().add_request( + "GET", + "pulls/42", + result=github_result(502, {"message": "bad gateway"}, retry_attempts=1), + ) + runtime.github.stub(routes) + + with pytest.raises(reconcile_reads.ReconcileReadError, match="pull request #42 unavailable") as exc_info: + reconcile_reads.read_reconcile_object(runtime, "pulls/42", label="pull request #42") + + assert exc_info.value.failure_kind == "server_error" + + +def test_read_optional_reconcile_object_returns_none_for_not_found(monkeypatch): + runtime = FakeReviewerBotRuntime(monkeypatch) + runtime.github_api_request = lambda method, endpoint, data=None, extra_headers=None, **kwargs: GitHubApiResult( + status_code=404, + payload={"message": "missing"}, + headers={}, + text="missing", + ok=False, + failure_kind="not_found", + retry_attempts=0, + transport_error=None, + ) + + assert reconcile_reads.read_optional_reconcile_object(runtime, "pulls/42/reviews/11", label="live review #11") is None + + +def test_read_live_pr_replay_context_normalizes_author_and_labels(monkeypatch): + runtime = FakeReviewerBotRuntime(monkeypatch) + routes = RouteGitHubApi().add_request( + "GET", + "pulls/42", + status_code=200, + payload={"user": {"login": "dana"}, "labels": [{"name": "coding guideline"}, {"name": "fls-audit"}]}, + ) + runtime.github.stub(routes) + + context = reconcile_reads.read_live_pr_replay_context(runtime, 42) + + assert context == reconcile_reads.LivePrReplayContext( + issue_author="dana", + issue_labels=("coding guideline", "fls-audit"), + ) + + +def test_read_live_comment_replay_context_normalizes_comment_metadata(): + context = reconcile_reads.read_live_comment_replay_context( + { + "user": {"login": "alice", "type": "User"}, + "author_association": "MEMBER", + "performed_via_github_app": None, + }, + {"actor_login": "alice"}, + ) + + assert context == reconcile_reads.LiveCommentReplayContext( + comment_author="alice", + comment_user_type="User", + comment_sender_type="User", + comment_installation_id="", + comment_performed_via_github_app=False, + ) diff --git a/tests/unit/reviewer_bot/test_reconcile_replay.py b/tests/unit/reviewer_bot/test_reconcile_replay.py new file mode 100644 index 000000000..3ce43cc1d --- /dev/null +++ b/tests/unit/reviewer_bot/test_reconcile_replay.py @@ -0,0 +1,31 @@ +from scripts.reviewer_bot_lib import lifecycle, reconcile, review_state +from tests.fixtures.fake_runtime import FakeReviewerBotRuntime +from tests.fixtures.reviewer_bot import make_state + + +def test_reconcile_active_review_entry_uses_explicit_head_repair_changed_field(monkeypatch): + state = make_state() + review = review_state.ensure_review_entry(state, 42, create=True) + assert review is not None + review["current_reviewer"] = "alice" + + runtime = FakeReviewerBotRuntime(monkeypatch) + runtime.set_config_value("IS_PULL_REQUEST", "true") + runtime.maybe_record_head_observation_repair = lambda issue_number, review_data: lifecycle.HeadObservationRepairResult( + changed=False, + outcome="unchanged", + ) + runtime.get_pull_request_reviews = lambda issue_number: [] + monkeypatch.setattr(reconcile, "refresh_reviewer_review_from_live_preferred_review", lambda bot, issue_number, review_data, **kwargs: (False, None)) + monkeypatch.setattr(reconcile, "_record_review_rebuild", lambda bot, state_obj, issue_number, review_data: False) + + message, success, changed = reconcile.reconcile_active_review_entry( + runtime, + state, + 42, + require_pull_request_context=True, + ) + + assert success is True + assert changed is False + assert "no reconciliation transitions applied" in message diff --git a/tests/unit/reviewer_bot/test_reconcile_replay_equivalence.py b/tests/unit/reviewer_bot/test_reconcile_replay_equivalence.py new file mode 100644 index 000000000..4ab4375fc --- /dev/null +++ b/tests/unit/reviewer_bot/test_reconcile_replay_equivalence.py @@ -0,0 +1,132 @@ +import json +from pathlib import Path + +from scripts.reviewer_bot_core import reconcile_replay_policy + + +def _load_matrix() -> dict: + return json.loads( + Path("tests/fixtures/equivalence/reconcile_replay/scenario_matrix.json").read_text( + encoding="utf-8" + ) + ) + + +def test_reconcile_replay_scenario_matrix_fixture_exists_and_names_required_rows(): + matrix = _load_matrix() + + assert matrix["harness_id"] == "C4a reconcile replay scenario matrix" + assert [row["scenario_id"] for row in matrix["scenarios"]] == [ + "noop_artifact", + "deferred_comment_replay_matching_live_comment", + "deferred_comment_replay_missing_live_comment", + "deferred_comment_replay_digest_or_classification_drift", + "deferred_review_submitted_replay", + "deferred_review_dismissed_replay", + "deferred_review_comment_replay", + "freshness_only_update", + "fail_closed_gap_update", + ] + + +def test_reconcile_replay_scenario_matrix_records_explicit_delta_gap_and_diagnostic_columns(): + matrix = _load_matrix() + + for row in matrix["scenarios"]: + assert row["expected_state_delta"] + assert isinstance(row["expected_touched_items"], list) + assert row["expected_deferred_gap_mutation"] + assert row["expected_reconciled_source_events_mutation"] + assert row["expected_diagnostic_output"] + + +def test_reconcile_replay_scenario_matrix_keeps_materially_distinct_drift_cases_separate(): + matrix = _load_matrix() + row_ids = {row["scenario_id"] for row in matrix["scenarios"]} + + assert "deferred_comment_replay_missing_live_comment" in row_ids + assert "deferred_comment_replay_digest_or_classification_drift" in row_ids + assert row_ids >= {"freshness_only_update", "fail_closed_gap_update", "noop_artifact"} + + +def test_reconcile_replay_policy_covers_noop_freshness_and_fail_closed_rows(): + noop = reconcile_replay_policy.decide_observer_noop( + source_event_key="issue_comment:210", + reason="trusted_direct_same_repo_human_comment", + ) + missing_live = reconcile_replay_policy.decide_comment_replay( + comment_id=210, + source_comment_class="command_plus_text", + source_has_non_command_text=True, + source_freshness_eligible=True, + live_comment_found=False, + live_body_digest_matches=False, + live_classified=None, + live_failure_kind="not_found", + runbook_path="runbook/path.md", + ) + drift = reconcile_replay_policy.decide_comment_replay( + comment_id=210, + source_comment_class="command_only", + source_has_non_command_text=False, + source_freshness_eligible=False, + live_comment_found=True, + live_body_digest_matches=True, + live_classified={"comment_class": "command_only", "has_non_command_text": False, "command_count": 2}, + live_failure_kind=None, + runbook_path="runbook/path.md", + ) + + assert noop.source_event_key == "issue_comment:210" + assert missing_live.record_source_freshness is True + assert missing_live.failed_closed_reason == "reconcile_failed_closed" + assert drift.failed_closed_reason == "reconcile_failed_closed" + assert drift.record_source_freshness is False + assert drift.replay_comment_command is False + + +def test_h3_reconcile_replay_policy_preserves_freshness_for_classification_drift_when_source_was_eligible(): + drift = reconcile_replay_policy.decide_comment_replay( + comment_id=211, + source_comment_class="plain_text", + source_has_non_command_text=True, + source_freshness_eligible=True, + live_comment_found=True, + live_body_digest_matches=True, + live_classified={"comment_class": "command_plus_text", "has_non_command_text": True, "command_count": 1}, + live_failure_kind=None, + runbook_path="runbook/path.md", + ) + + assert drift.record_source_freshness is True + assert drift.failed_closed_reason == "reconcile_failed_closed" + + +def test_reconcile_replay_policy_covers_submitted_and_dismissed_review_rows(): + submitted = reconcile_replay_policy.decide_review_submitted_replay( + source_event_key="pull_request_review:11", + actor_login="alice", + current_reviewer="alice", + live_commit_id="head-1", + live_submitted_at="2026-03-17T10:00:00Z", + ) + dismissed = reconcile_replay_policy.decide_review_dismissed_replay( + source_event_key="pull_request_review_dismissed:12", + timestamp="2026-03-17T10:10:00Z", + ) + + assert submitted.accept_reviewer_review is True + assert submitted.mark_reconciled is True + assert dismissed.accept_review_dismissal is True + assert dismissed.clear_gap is True + + +def test_h3_deferred_comment_replay_success_path_stays_policy_owned(): + reconcile_text = Path("scripts/reviewer_bot_lib/reconcile.py").read_text(encoding="utf-8") + policy_text = Path("scripts/reviewer_bot_core/reconcile_replay_policy.py").read_text(encoding="utf-8") + + assert "if context.payload.comment_class in {\"command_only\", \"command_plus_text\"}:" not in reconcile_text + assert "if decision.replay_comment_command:" in reconcile_text + assert "if decision.mark_reconciled:" in reconcile_text + assert "if decision.clear_gap:" in reconcile_text + assert "def decide_comment_replay(" in policy_text diff --git a/tests/unit/reviewer_bot/test_reconcile_unit.py b/tests/unit/reviewer_bot/test_reconcile_unit.py new file mode 100644 index 000000000..d627347ee --- /dev/null +++ b/tests/unit/reviewer_bot/test_reconcile_unit.py @@ -0,0 +1,633 @@ +from pathlib import Path +from types import SimpleNamespace +from typing import get_type_hints + +import pytest + +from scripts.reviewer_bot_lib import ( + lifecycle, + reconcile, + reconcile_payloads, + review_state, +) +from scripts.reviewer_bot_lib.config import GitHubApiResult +from scripts.reviewer_bot_lib.runtime_protocols import ( + ReconcileRectifyRuntimeContext, + ReconcileWorkflowRuntimeContext, +) +from tests.fixtures.fake_runtime import FakeReviewerBotRuntime +from tests.fixtures.reconcile_harness import ReconcileHarness, review_submitted_payload +from tests.fixtures.reviewer_bot import make_state +from tests.fixtures.reviewer_bot_env import set_workflow_run_event_payload + + +def test_parse_deferred_context_payload_returns_typed_review_payload(): + payload = review_submitted_payload( + pr_number=42, + review_id=11, + source_event_key="pull_request_review:11", + source_submitted_at="2026-03-17T10:00:00Z", + source_review_state="COMMENTED", + source_commit_id="head-1", + actor_login="alice", + source_run_id=500, + source_run_attempt=2, + ) + + parsed = reconcile.parse_deferred_context_payload(payload) + + assert isinstance(parsed, reconcile.DeferredReviewPayload) + assert parsed.identity.source_event_name == "pull_request_review" + assert parsed.review_id == 11 + assert parsed.pr_number == 42 + + +def test_parse_deferred_context_payload_returns_typed_comment_payload(): + payload = { + "payload_kind": "deferred_comment", + "schema_version": 3, + "source_workflow_name": "Reviewer Bot PR Comment Router", + "source_workflow_file": ".github/workflows/reviewer-bot-pr-comment-router.yml", + "source_run_id": 610, + "source_run_attempt": 1, + "source_event_name": "issue_comment", + "source_event_action": "created", + "source_event_key": "issue_comment:210", + "pr_number": 42, + "comment_id": 210, + "comment_body": "@guidelines-bot /queue", + "comment_created_at": "2026-03-17T10:00:00Z", + "comment_author": "bob", + "comment_author_id": 123, + "comment_user_type": "User", + "comment_sender_type": "User", + "comment_installation_id": None, + "comment_performed_via_github_app": False, + "issue_author": "dana", + "issue_state": "open", + "issue_labels": ["coding guideline"], + } + + parsed = reconcile.parse_deferred_context_payload(payload) + + assert isinstance(parsed, reconcile.DeferredCommentPayload) + assert parsed.identity.source_event_key == "issue_comment:210" + assert parsed.comment_id == 210 + + +def test_build_deferred_comment_replay_context_returns_typed_context(): + payload = reconcile.DeferredCommentPayload( + identity=reconcile.DeferredArtifactIdentity( + payload_kind=reconcile_payloads.DeferredPayloadKind.DEFERRED_COMMENT, + schema_version=3, + source_run_id=610, + source_run_attempt=1, + source_event_name="issue_comment", + source_event_action="created", + source_event_key="issue_comment:210", + pr_number=42, + ), + comment_id=210, + comment_body="hello\n@guidelines-bot /queue", + comment_created_at="2026-03-17T10:00:00Z", + comment_author="bob", + comment_author_id=123, + comment_user_type="User", + comment_sender_type="User", + comment_installation_id=None, + comment_performed_via_github_app=False, + issue_author="dana", + issue_state="open", + issue_labels=("coding guideline",), + raw_payload={"source_event_key": "issue_comment:210"}, + ) + + context = reconcile.build_deferred_comment_replay_context( + payload, + expected_event_name="issue_comment", + live_comment_endpoint="issues/comments/210", + ) + + assert isinstance(context, reconcile.DeferredCommentReplayContext) + assert context.comment_id == 210 + assert context.pr_number == 42 + assert context.source_freshness_eligible is True + + +def test_build_deferred_comment_replay_context_rejects_mismatched_source_event_key(): + payload = reconcile.DeferredCommentPayload( + identity=reconcile.DeferredArtifactIdentity( + payload_kind=reconcile_payloads.DeferredPayloadKind.DEFERRED_COMMENT, + schema_version=3, + source_run_id=610, + source_run_attempt=1, + source_event_name="issue_comment", + source_event_action="created", + source_event_key="issue_comment:999", + pr_number=42, + ), + comment_id=210, + comment_body="@guidelines-bot /queue", + comment_created_at="2026-03-17T10:00:00Z", + comment_author="bob", + comment_author_id=123, + comment_user_type="User", + comment_sender_type="User", + comment_installation_id=None, + comment_performed_via_github_app=False, + issue_author="dana", + issue_state="open", + issue_labels=("coding guideline",), + raw_payload={"source_event_key": "issue_comment:999"}, + ) + + with pytest.raises(RuntimeError, match="source_event_key mismatch"): + reconcile.build_deferred_comment_replay_context( + payload, + expected_event_name="issue_comment", + live_comment_endpoint="issues/comments/210", + ) + + +def test_build_deferred_review_replay_context_returns_typed_context(): + payload = reconcile.DeferredReviewPayload( + identity=reconcile.DeferredArtifactIdentity( + payload_kind=reconcile_payloads.DeferredPayloadKind.DEFERRED_REVIEW_SUBMITTED, + schema_version=3, + source_run_id=500, + source_run_attempt=2, + source_event_name="pull_request_review", + source_event_action="submitted", + source_event_key="pull_request_review:11", + pr_number=42, + ), + review_id=11, + source_submitted_at="2026-03-17T10:00:00Z", + source_review_state="COMMENTED", + source_commit_id="head-1", + actor_login="alice", + raw_payload={"source_event_key": "pull_request_review:11"}, + ) + + context = reconcile.build_deferred_review_replay_context( + payload, + expected_event_action="submitted", + ) + + assert isinstance(context, reconcile.DeferredReviewReplayContext) + assert context.review_id == 11 + assert context.pr_number == 42 + assert context.actor_login == "alice" + + +def test_build_deferred_review_replay_context_rejects_mismatched_source_event_key(): + payload = reconcile.DeferredReviewPayload( + identity=reconcile.DeferredArtifactIdentity( + payload_kind=reconcile_payloads.DeferredPayloadKind.DEFERRED_REVIEW_SUBMITTED, + schema_version=3, + source_run_id=500, + source_run_attempt=2, + source_event_name="pull_request_review", + source_event_action="submitted", + source_event_key="pull_request_review:99", + pr_number=42, + ), + review_id=11, + source_submitted_at="2026-03-17T10:00:00Z", + source_review_state="COMMENTED", + source_commit_id="head-1", + actor_login="alice", + raw_payload={"source_event_key": "pull_request_review:99"}, + ) + + with pytest.raises(RuntimeError, match="source_event_key mismatch"): + reconcile.build_deferred_review_replay_context( + payload, + expected_event_action="submitted", + ) + + +def test_parse_deferred_context_payload_returns_typed_observer_noop_payload(): + payload = { + "schema_version": 1, + "kind": "observer_noop", + "reason": "not a command", + "source_workflow_name": "Reviewer Bot PR Comment Observer", + "source_workflow_file": ".github/workflows/reviewer-bot-pr-comment-observer.yml", + "source_run_id": 610, + "source_run_attempt": 1, + "source_event_name": "issue_comment", + "source_event_action": "created", + "source_event_key": "issue_comment:210", + "pr_number": 42, + } + + parsed = reconcile.parse_deferred_context_payload(payload) + + assert isinstance(parsed, reconcile.ObserverNoopPayload) + assert parsed.reason == "not a command" + assert parsed.pr_number == 42 + + +def test_handle_workflow_run_event_result_collects_touched_item(monkeypatch): + runtime = FakeReviewerBotRuntime(monkeypatch) + runtime.ACTIVE_LEASE_CONTEXT = object() + runtime.set_config_value("EVENT_NAME", "workflow_run") + set_workflow_run_event_payload(runtime.config, "Reviewer Bot PR Comment Observer") + runtime.set_config_value("WORKFLOW_RUN_TRIGGERING_ATTEMPT", "1") + runtime.set_config_value("WORKFLOW_RUN_TRIGGERING_CONCLUSION", "success") + runtime.stub_deferred_payload( + { + "schema_version": 1, + "kind": "observer_noop", + "reason": "trusted_direct_same_repo_human_comment", + "source_workflow_name": "Reviewer Bot PR Comment Observer", + "source_workflow_file": ".github/workflows/reviewer-bot-pr-comment-observer.yml", + "source_run_id": 610, + "source_run_attempt": 1, + "source_event_name": "issue_comment", + "source_event_action": "created", + "source_event_key": "issue_comment:210", + "pr_number": 42, + } + ) + state = make_state(epoch="freshness_v15") + + result = reconcile.handle_workflow_run_event_result(runtime, state) + + assert result.state_changed is False + assert result.touched_items == [42] + assert runtime.drain_touched_items() == [] + assert "42" in state["active_reviews"] + + +def test_reconcile_active_review_entry_uses_explicit_head_repair_changed_field(monkeypatch): + state = make_state() + review = review_state.ensure_review_entry(state, 42, create=True) + assert review is not None + review["current_reviewer"] = "alice" + runtime = FakeReviewerBotRuntime(monkeypatch) + runtime.set_config_value("IS_PULL_REQUEST", "true") + runtime.maybe_record_head_observation_repair = lambda issue_number, review_data: lifecycle.HeadObservationRepairResult(changed=False, outcome="unchanged") + runtime.get_pull_request_reviews = lambda issue_number: [] + monkeypatch.setattr(reconcile, "refresh_reviewer_review_from_live_preferred_review", lambda bot, issue_number, review_data, **kwargs: (False, None)) + monkeypatch.setattr(reconcile, "_record_review_rebuild", lambda bot, state_obj, issue_number, review_data: False) + + message, success, changed = reconcile.reconcile_active_review_entry(runtime, state, 42, require_pull_request_context=True) + + assert success is True + assert changed is False + assert "no reconciliation transitions applied" in message + + +def test_parse_deferred_context_payload_rejects_unsupported_payload(): + with pytest.raises(RuntimeError, match="Unsupported deferred workflow_run payload"): + reconcile.parse_deferred_context_payload({"schema_version": 2}) + + +def test_reconcile_deferred_comment_fail_closes_for_command_ambiguity(monkeypatch): + runtime = FakeReviewerBotRuntime(monkeypatch) + state = make_state() + review = review_state.ensure_review_entry(state, 42, create=True) + assert review is not None + comment_body = "@guidelines-bot /claim" + payload = reconcile.DeferredCommentPayload( + identity=reconcile.DeferredArtifactIdentity( + payload_kind=reconcile_payloads.DeferredPayloadKind.DEFERRED_COMMENT, + schema_version=3, + source_run_id=603, + source_run_attempt=1, + source_event_name="issue_comment", + source_event_action="created", + source_event_key="issue_comment:201", + pr_number=42, + ), + comment_id=201, + comment_body=comment_body, + comment_created_at="2026-03-17T10:00:00Z", + comment_author="alice", + comment_author_id=5, + comment_user_type="User", + comment_sender_type="User", + comment_installation_id=None, + comment_performed_via_github_app=False, + issue_author="dana", + issue_state="open", + issue_labels=("coding guideline",), + raw_payload={ + "payload_kind": "deferred_comment", + "comment_id": 201, + "comment_body": comment_body, + "comment_created_at": "2026-03-17T10:00:00Z", + "comment_author": "alice", + "comment_author_id": 5, + "comment_user_type": "User", + "comment_sender_type": "User", + "comment_installation_id": None, + "comment_performed_via_github_app": False, + "issue_author": "dana", + "issue_state": "open", + "issue_labels": ["coding guideline"], + "source_event_key": "issue_comment:201", + "source_event_name": "issue_comment", + "source_event_action": "created", + "pr_number": 42, + "source_run_id": 603, + "source_run_attempt": 1, + "source_workflow_name": "Reviewer Bot PR Comment Router", + "source_workflow_file": ".github/workflows/reviewer-bot-pr-comment-router.yml", + }, + ) + context = reconcile.build_deferred_comment_replay_context( + payload, + expected_event_name="issue_comment", + live_comment_endpoint="issues/comments/201", + ) + monkeypatch.setattr( + reconcile, + "_read_live_pr_replay_context", + lambda bot, pr_number: SimpleNamespace(issue_author="dana", issue_labels=()), + ) + monkeypatch.setattr( + reconcile, + "_read_reconcile_object", + lambda bot, endpoint, *, label: { + "body": comment_body, + "user": {"login": "alice", "type": "User"}, + "author_association": "MEMBER", + "performed_via_github_app": False, + }, + ) + monkeypatch.setattr( + reconcile, + "classify_comment_payload", + lambda bot, body: { + "comment_class": "command_only", + "has_non_command_text": False, + "command_count": 2, + "command": None, + "args": [], + "normalized_body": body, + }, + ) + monkeypatch.setattr( + reconcile, + "process_comment_event", + lambda *args, **kwargs: pytest.fail("ambiguous replay must fail closed before command application"), + ) + + changed = reconcile._reconcile_deferred_comment(runtime, state, review, context) + + assert changed is True + assert review["sidecars"]["deferred_gaps"]["issue_comment:201"]["reason"] == "reconcile_failed_closed" + assert "no longer resolves to exactly one command" in review["sidecars"]["deferred_gaps"]["issue_comment:201"]["diagnostic_summary"] + + +def test_workflow_run_reconcile_uses_artifact_contract_for_router_no_artifact_success(monkeypatch): + runtime = FakeReviewerBotRuntime(monkeypatch) + runtime.ACTIVE_LEASE_CONTEXT = object() + runtime.set_config_value("EVENT_NAME", "workflow_run") + runtime.set_config_value("REVIEWER_BOT_WORKFLOW_KIND", "reconcile") + runtime.set_config_value("WORKFLOW_RUN_TRIGGERING_CONCLUSION", "success") + set_workflow_run_event_payload(runtime.config, "Reviewer Bot PR Comment Router") + runtime.load_deferred_payload = lambda: (_ for _ in ()).throw(RuntimeError("missing artifact")) + state = make_state(epoch="freshness_v15") + + result = reconcile.handle_workflow_run_event_result(runtime, state) + + assert result == reconcile.WorkflowRunHandlerResult(False, []) + + +def test_read_reconcile_reviews_rejects_non_list_payload(monkeypatch): + runtime = FakeReviewerBotRuntime(monkeypatch) + runtime.get_pull_request_reviews = lambda issue_number: {"unexpected": True} + + with pytest.raises(reconcile.ReconcileReadError, match="payload invalid"): + reconcile._read_reconcile_reviews(runtime, 42) + + +def test_read_optional_reconcile_object_returns_none_for_not_found(monkeypatch): + runtime = FakeReviewerBotRuntime(monkeypatch) + runtime.github_api_request = lambda method, endpoint, data=None, extra_headers=None, **kwargs: GitHubApiResult( + status_code=404, + payload={"message": "missing"}, + headers={}, + text="missing", + ok=False, + failure_kind="not_found", + retry_attempts=0, + transport_error=None, + ) + + assert reconcile._read_optional_reconcile_object(runtime, "pulls/42/reviews/11", label="live review #11") is None + + +def test_reconcile_harness_exposes_deferred_payload_store(monkeypatch): + payload = review_submitted_payload( + pr_number=42, + review_id=11, + source_event_key="pull_request_review:11", + source_submitted_at="2026-03-17T10:00:00Z", + source_review_state="COMMENTED", + source_commit_id="head-1", + actor_login="alice", + source_run_id=500, + source_run_attempt=2, + ) + harness = ReconcileHarness(monkeypatch, payload) + + assert harness.deferred_payloads is harness.runtime.deferred_payloads + + +def test_reconcile_module_delegates_replay_decision_logic_to_core_policy(): + reconcile_text = Path("scripts/reviewer_bot_lib/reconcile.py").read_text(encoding="utf-8") + + assert "from scripts.reviewer_bot_core import reconcile_replay_policy" in reconcile_text + assert "reconcile_replay_policy.decide_comment_replay(" in reconcile_text + assert "reconcile_replay_policy.decide_review_submitted_replay(" in reconcile_text + assert "reconcile_replay_policy.decide_review_dismissed_replay(" in reconcile_text + + +@pytest.mark.parametrize( + ("payload", "expected_handler_name"), + [ + ( + { + "schema_version": 2, + "source_workflow_name": "Reviewer Bot PR Comment Observer", + "source_workflow_file": ".github/workflows/reviewer-bot-pr-comment-observer.yml", + "source_run_id": 610, + "source_run_attempt": 1, + "source_event_name": "issue_comment", + "source_event_action": "created", + "source_event_key": "issue_comment:210", + "pr_number": 42, + "comment_id": 210, + "comment_class": "command_only", + "has_non_command_text": False, + "source_body_digest": "abc123", + "source_created_at": "2026-03-17T10:00:00Z", + "actor_login": "bob", + }, + "_handle_issue_comment_workflow_run", + ), + ( + review_submitted_payload( + pr_number=42, + review_id=11, + source_event_key="pull_request_review:11", + source_submitted_at="2026-03-17T10:00:00Z", + source_review_state="COMMENTED", + source_commit_id="head-1", + actor_login="alice", + source_run_id=500, + source_run_attempt=2, + ), + "_handle_review_submitted_workflow_run", + ), + ( + { + **review_submitted_payload( + pr_number=42, + review_id=12, + source_event_key="pull_request_review_dismissed:12", + source_submitted_at="2026-03-17T10:00:00Z", + source_review_state="COMMENTED", + source_commit_id="head-1", + actor_login="alice", + source_run_id=501, + source_run_attempt=1, + ), + "payload_kind": "deferred_review_dismissed", + "source_workflow_name": "Reviewer Bot PR Review Dismissed Observer", + "source_workflow_file": ".github/workflows/reviewer-bot-pr-review-dismissed-observer.yml", + "source_event_action": "dismissed", + }, + "_handle_review_dismissed_workflow_run", + ), + ( + { + "schema_version": 2, + "source_workflow_name": "Reviewer Bot PR Review Comment Observer", + "source_workflow_file": ".github/workflows/reviewer-bot-pr-review-comment-observer.yml", + "source_run_id": 711, + "source_run_attempt": 1, + "source_event_name": "pull_request_review_comment", + "source_event_action": "created", + "source_event_key": "pull_request_review_comment:310", + "pr_number": 42, + "comment_id": 310, + "comment_class": "plain_text", + "has_non_command_text": True, + "source_body_digest": "abc123", + "source_created_at": "2026-03-17T10:00:00Z", + "actor_login": "alice", + }, + "_handle_review_comment_workflow_run", + ), + ( + { + "schema_version": 1, + "kind": "observer_noop", + "reason": "not a command", + "source_workflow_name": "Reviewer Bot PR Comment Observer", + "source_workflow_file": ".github/workflows/reviewer-bot-pr-comment-observer.yml", + "source_run_id": 610, + "source_run_attempt": 1, + "source_event_name": "issue_comment", + "source_event_action": "created", + "source_event_key": "issue_comment:210", + "pr_number": 42, + }, + "_handle_observer_noop_workflow_run", + ), + ], +) +def test_workflow_run_dispatch_matrix_routes_supported_payloads(payload, expected_handler_name): + parsed_payload = reconcile.parse_deferred_context_payload(payload) + + assert reconcile._workflow_run_handler_for_payload(parsed_payload).__name__ == expected_handler_name + + +def test_workflow_run_dispatch_matrix_rejects_unsupported_pairs(): + parsed_payload = reconcile.DeferredCommentPayload( + identity=reconcile.DeferredArtifactIdentity( + payload_kind=reconcile_payloads.DeferredPayloadKind.DEFERRED_COMMENT, + schema_version=3, + source_run_id=610, + source_run_attempt=1, + source_event_name="issue_comment", + source_event_action="edited", + source_event_key="issue_comment:210", + pr_number=42, + ), + comment_id=210, + comment_body="@guidelines-bot /queue", + comment_created_at="2026-03-17T10:00:00Z", + comment_author="bob", + comment_author_id=123, + comment_user_type="User", + comment_sender_type="User", + comment_installation_id=None, + comment_performed_via_github_app=False, + issue_author="dana", + issue_state="open", + issue_labels=("coding guideline",), + raw_payload={"source_event_key": "issue_comment:210"}, + ) + + assert reconcile._workflow_run_handler_for_payload(parsed_payload) is None + + +def test_k1b_context_module_freezes_two_reconcile_runtime_subseams_not_one_mega_protocol(): + context_text = Path("scripts/reviewer_bot_lib/runtime_protocols.py").read_text(encoding="utf-8") + + assert "class ReconcileWorkflowRuntimeContext(Protocol):" in context_text + assert "class ReconcileRectifyRuntimeContext(Protocol):" in context_text + assert "class ReconcileAdaptersContext(Protocol):" in context_text + assert "class ReconcileReviewStateAdapterContext(Protocol):" in context_text + assert "class ReconcileWorkflowRuntimeContext(ReviewerBotContext, Protocol):" not in context_text + assert "class ReconcileRectifyRuntimeContext(ReviewerBotContext, Protocol):" not in context_text + + +def test_k1c_workflow_run_reconcile_entrypoint_uses_frozen_workflow_runtime_protocol(): + hints = get_type_hints(reconcile.handle_workflow_run_event_result) + + assert hints["bot"] is ReconcileWorkflowRuntimeContext + + +def test_k1g_rectify_reconcile_entrypoints_use_finalized_rectify_runtime_protocol(): + reconcile_hints = get_type_hints(reconcile.reconcile_active_review_entry) + rectify_hints = get_type_hints(reconcile.handle_rectify_command) + rebuild_hints = get_type_hints(reconcile._record_review_rebuild) + + assert reconcile_hints["bot"] is ReconcileRectifyRuntimeContext + assert rectify_hints["bot"] is ReconcileRectifyRuntimeContext + assert rebuild_hints["bot"] is ReconcileRectifyRuntimeContext + + +def test_k1d_rectify_refresh_keeps_retained_owner_boundaries_explicit_in_reconcile_source(): + reconcile_text = Path("scripts/reviewer_bot_lib/reconcile.py").read_text(encoding="utf-8") + context_text = Path("scripts/reviewer_bot_lib/runtime_protocols.py").read_text(encoding="utf-8") + + assert "refresh_reviewer_review_from_live_preferred_review(" in reconcile_text + assert "rebuild_pr_approval_state_result(" in reconcile_text + assert "approval_policy.find_triage_approval_after(" in reconcile_text + assert "def github_api_request(self, *args, **kwargs) -> Any: ..." in context_text + assert "def github_api(self, *args, **kwargs) -> Any | None: ..." in context_text + assert "def parse_iso8601_timestamp(self, value: Any) -> datetime | None: ..." in context_text + assert "def get_user_permission_status(self, username: str, required_permission: str = \"triage\") -> str: ..." in context_text + + +def test_k1e_rectify_finalization_keeps_retained_approval_support_explicit_but_workflow_seam_unchanged(): + reconcile_text = Path("scripts/reviewer_bot_lib/reconcile.py").read_text(encoding="utf-8") + context_text = Path("scripts/reviewer_bot_lib/runtime_protocols.py").read_text(encoding="utf-8") + + assert "approval_policy.find_triage_approval_after(" in reconcile_text + assert "live_review_support.parse_github_timestamp(" in reconcile_text or "live_review_support.parse_github_timestamp(" in Path("scripts/reviewer_bot_core/approval_policy.py").read_text(encoding="utf-8") + assert "live_review_support.permission_status(bot, author, \"triage\")" in Path("scripts/reviewer_bot_core/approval_policy.py").read_text(encoding="utf-8") + assert "def parse_iso8601_timestamp(self, value: Any) -> datetime | None: ..." in context_text + assert "def get_user_permission_status(self, username: str, required_permission: str = \"triage\") -> str: ..." in context_text + workflow_block = context_text.split("class ReconcileWorkflowRuntimeContext(Protocol):", 1)[1].split("class ReconcileRectifyGitHubContext(Protocol):", 1)[0] + assert "parse_github_timestamp" not in workflow_block + assert 'required_permission: str = "triage"' not in workflow_block diff --git a/tests/unit/reviewer_bot/test_retrying.py b/tests/unit/reviewer_bot/test_retrying.py new file mode 100644 index 000000000..877cab522 --- /dev/null +++ b/tests/unit/reviewer_bot/test_retrying.py @@ -0,0 +1,39 @@ +from scripts.reviewer_bot_lib import retrying +from tests.fixtures.fake_jitter import DeterministicJitter + + +def test_is_retryable_status_matches_expected_statuses(): + assert retrying.is_retryable_status(429) is True + assert retrying.is_retryable_status(500) is True + assert retrying.is_retryable_status(503) is True + assert retrying.is_retryable_status(404) is False + assert retrying.is_retryable_status(403) is False + assert retrying.is_retryable_status(None) is False + + +def test_additional_attempts_for_policy_supports_known_policies(): + assert retrying.additional_attempts_for_policy(retrying.RETRY_POLICY_NONE, 5) == 0 + assert retrying.additional_attempts_for_policy(retrying.RETRY_POLICY_IDEMPOTENT_READ, 5) == 5 + + +def test_additional_attempts_for_policy_rejects_unknown_policy(): + try: + retrying.additional_attempts_for_policy("unexpected", 3) + except ValueError as exc: + assert "Unsupported retry policy" in str(exc) + else: + raise AssertionError("expected ValueError for unsupported retry policy") + + +def test_max_attempts_for_policy_includes_initial_attempt(): + assert retrying.max_attempts_for_policy(retrying.RETRY_POLICY_NONE, 4) == 1 + assert retrying.max_attempts_for_policy(retrying.RETRY_POLICY_IDEMPOTENT_READ, 4) == 5 + + +def test_bounded_exponential_delay_uses_jitter_and_caps_growth(): + jitter = DeterministicJitter([0.25, 0.5, 0.75]) + + assert retrying.bounded_exponential_delay(2.0, 1, jitter=jitter) == 2.25 + assert retrying.bounded_exponential_delay(2.0, 2, jitter=jitter) == 4.5 + assert retrying.bounded_exponential_delay(2.0, 4, jitter=jitter) == 8.75 + assert jitter.calls == [(0, 2.0), (0, 4.0), (0, 8.0)] diff --git a/tests/unit/reviewer_bot/test_review_read_support.py b/tests/unit/reviewer_bot/test_review_read_support.py new file mode 100644 index 000000000..bd658ed0a --- /dev/null +++ b/tests/unit/reviewer_bot/test_review_read_support.py @@ -0,0 +1,125 @@ + +from scripts.reviewer_bot_core import live_review_support +from tests.fixtures.fake_runtime import FakeReviewerBotRuntime +from tests.fixtures.reviewer_bot_fakes import RouteGitHubApi, github_result + + +def test_pull_request_read_result_uses_fallback_payload_after_system_exit(monkeypatch): + runtime = FakeReviewerBotRuntime(monkeypatch) + routes = RouteGitHubApi().raise_system_exit_on_request().add_api("GET", "pulls/42", {"head": {"sha": "head-1"}}) + runtime.github.stub(routes) + + result = live_review_support.read_pull_request_result(runtime, 42) + + assert result == {"ok": True, "pull_request": {"head": {"sha": "head-1"}}} + + +def test_pull_request_read_result_reports_not_found(monkeypatch): + runtime = FakeReviewerBotRuntime(monkeypatch) + routes = RouteGitHubApi().add_request( + "GET", + "pulls/42", + result=github_result(404, {"message": "missing"}), + ) + runtime.github.stub(routes) + + result = live_review_support.read_pull_request_result(runtime, 42) + + assert result == {"ok": False, "reason": "pull_request_not_found", "failure_kind": "not_found"} + + +def test_pull_request_read_result_reports_invalid_payload(monkeypatch): + runtime = FakeReviewerBotRuntime(monkeypatch) + routes = RouteGitHubApi().add_request( + "GET", + "pulls/42", + status_code=200, + payload=["not", "a", "dict"], + ) + runtime.github.stub(routes) + + result = live_review_support.read_pull_request_result(runtime, 42) + + assert result == {"ok": False, "reason": "pull_request_unavailable", "failure_kind": "invalid_payload"} + + +def test_get_pull_request_reviews_result_paginates(monkeypatch): + runtime = FakeReviewerBotRuntime(monkeypatch) + routes = ( + RouteGitHubApi() + .add_request( + "GET", + "pulls/42/reviews?per_page=100&page=1", + status_code=200, + payload=[{"id": index} for index in range(100)], + ) + .add_request( + "GET", + "pulls/42/reviews?per_page=100&page=2", + status_code=200, + payload=[{"id": 100}], + ) + ) + runtime.github.stub(routes) + + result = live_review_support.read_pull_request_reviews_result(runtime, 42) + + assert result["ok"] is True + assert len(result["reviews"]) == 101 + + +def test_get_pull_request_reviews_result_uses_fallback_loader_after_system_exit(monkeypatch): + runtime = FakeReviewerBotRuntime(monkeypatch) + routes = RouteGitHubApi().raise_system_exit_on_request().add_api( + "GET", + "pulls/42/reviews?per_page=100&page=1", + [{"id": 10}], + ) + runtime.github.stub(routes) + runtime.get_pull_request_reviews = lambda issue_number: [{"id": 10}] + + result = live_review_support.read_pull_request_reviews_result(runtime, 42) + + assert result == {"ok": True, "reviews": [{"id": 10}]} + + +def test_get_pull_request_reviews_result_reports_unavailable(monkeypatch): + runtime = FakeReviewerBotRuntime(monkeypatch) + routes = RouteGitHubApi().add_request( + "GET", + "pulls/42/reviews?per_page=100&page=1", + result=github_result(502, {"message": "bad gateway"}, retry_attempts=1), + ) + runtime.github.stub(routes) + + result = live_review_support.read_pull_request_reviews_result(runtime, 42) + + assert result == {"ok": False, "reason": "reviews_unavailable", "failure_kind": "server_error"} + + +def test_get_pull_request_reviews_result_reports_invalid_payload(monkeypatch): + runtime = FakeReviewerBotRuntime(monkeypatch) + routes = RouteGitHubApi().add_request( + "GET", + "pulls/42/reviews?per_page=100&page=1", + status_code=200, + payload={"not": "a list"}, + ) + runtime.github.stub(routes) + + result = live_review_support.read_pull_request_reviews_result(runtime, 42) + + assert result == {"ok": False, "reason": "reviews_unavailable", "failure_kind": "invalid_payload"} + + +def test_permission_status_fails_closed_for_unknown_values(monkeypatch): + runtime = FakeReviewerBotRuntime(monkeypatch) + runtime.github.get_user_permission_status = lambda username, required_permission="push": "mystery" + + assert live_review_support.permission_status(runtime, "alice", "push") == "unavailable" + + +def test_parse_github_timestamp_rejects_invalid_values(): + assert live_review_support.parse_github_timestamp(None) is None + assert live_review_support.parse_github_timestamp("") is None + assert live_review_support.parse_github_timestamp("not-a-timestamp") is None diff --git a/tests/unit/reviewer_bot/test_review_state_equivalence.py b/tests/unit/reviewer_bot/test_review_state_equivalence.py new file mode 100644 index 000000000..2b932b4c4 --- /dev/null +++ b/tests/unit/reviewer_bot/test_review_state_equivalence.py @@ -0,0 +1,371 @@ +import json +from copy import deepcopy +from pathlib import Path + +from scripts.reviewer_bot_core import ( + live_review_support, + review_state_live_repair, + review_state_machine, + reviewer_review_helpers, +) +from scripts.reviewer_bot_lib import review_state, reviews +from tests.fixtures.reviewer_bot import make_state + + +def _load_review_state_fixture(name: str) -> dict: + return json.loads( + Path(f"tests/fixtures/equivalence/review_state/{name}").read_text(encoding="utf-8") + ) + + +def _load_live_read_repair_fixture() -> dict: + return json.loads( + Path("tests/fixtures/equivalence/review_state_live_repair/scenarios.json").read_text(encoding="utf-8") + ) + + +def test_review_state_equivalence_harness_shell_and_fixture_inventory_exist(): + assert Path("tests/fixtures/equivalence/review_state/api_inventory.md").exists() + assert Path("tests/fixtures/equivalence/review_state/local_state_only_scenarios.json").exists() + assert Path("tests/fixtures/equivalence/review_state_live_repair/api_inventory.md").exists() + assert Path("tests/fixtures/equivalence/review_state_live_repair/scenarios.json").exists() + + +def test_review_state_equivalence_harness_shell_documents_future_scope(): + module_text = Path("tests/unit/reviewer_bot/test_review_state_equivalence.py").read_text( + encoding="utf-8" + ) + + assert "local_state_only_scenarios.json" in module_text + assert "review_state_live_repair/scenarios.json" in module_text + + +def test_g2a_live_read_repair_inventory_freezes_exact_public_apis_and_scenarios(): + inventory = Path("tests/fixtures/equivalence/review_state_live_repair/api_inventory.md").read_text( + encoding="utf-8" + ) + fixture = _load_live_read_repair_fixture() + + assert "accept_reviewer_review_from_live_review" in inventory + assert "refresh_reviewer_review_from_live_preferred_review" in inventory + assert "repair_missing_reviewer_review_state" in inventory + + assert fixture["harness_id"] == "G2a live-read-assisted review repair inventory" + assert fixture["scope"] == "live-read-assisted review repair" + assert fixture["apis"] == [ + "accept_reviewer_review_from_live_review", + "refresh_reviewer_review_from_live_preferred_review", + "repair_missing_reviewer_review_state", + ] + assert "ensure_review_entry" not in fixture["apis"] + assert "accept_channel_event" not in fixture["apis"] + assert fixture["scenarios"] == [ + "matching current reviewer and valid submitted review", + "reviewer mismatch", + "no preferred review found", + "head repair without review acceptance", + "no-op repair path", + ] + + +def test_local_state_only_post_deletion_fixture_driven_proof(monkeypatch): + fixture = _load_review_state_fixture("local_state_only_scenarios.json") + fixed_now = "2026-03-17T10:00:00+00:00" + monkeypatch.setattr(review_state, "_now_iso", lambda: fixed_now) + + assert fixture["harness_id"] == "C1b/C1c mutable review-state mutation equivalence" + assert fixture["scope"] == "local-state-only mutation" + + scenarios = [ + ( + "ensure_review_entry_create", + lambda state: review_state.ensure_review_entry(state, 42, create=True), + lambda state: review_state_machine.ensure_review_entry(state, 42, create=True), + make_state(), + ), + ( + "ensure_review_entry_sparse_list_upgrade", + lambda state: review_state.ensure_review_entry(state, 42, create=False), + lambda state: review_state_machine.ensure_review_entry(state, 42, create=False), + {**make_state(), "active_reviews": {"42": ["alice"]}}, + ), + ( + "accept_channel_event_precedence", + lambda state: review_state.accept_channel_event( + state["active_reviews"]["42"], + "reviewer_comment", + semantic_key="issue_comment:101", + timestamp="2026-03-17T11:00:00Z", + actor="alice", + source_precedence=2, + payload={"kind": "comment"}, + ), + lambda state: review_state_machine.accept_channel_event( + state["active_reviews"]["42"], + "reviewer_comment", + semantic_key="issue_comment:101", + timestamp="2026-03-17T11:00:00Z", + actor="alice", + source_precedence=2, + payload={"kind": "comment"}, + ), + {**make_state(), "active_reviews": {"42": {"reviewer_comment": {"accepted": None, "seen_keys": []}}}}, + ), + ( + "record_reviewer_activity_clears_transition_timers", + lambda state: review_state.record_reviewer_activity( + state["active_reviews"]["42"], "2026-03-17T11:00:00Z" + ), + lambda state: review_state_machine.record_reviewer_activity( + state["active_reviews"]["42"], "2026-03-17T11:00:00Z" + ), + { + **make_state(), + "active_reviews": { + "42": { + "last_reviewer_activity": "2026-03-17T10:00:00Z", + "transition_warning_sent": "2026-03-17T10:30:00Z", + "transition_notice_sent_at": "2026-03-17T10:45:00Z", + } + }, + }, + ), + ( + "record_transition_notice_sent", + lambda state: review_state.record_transition_notice_sent( + state["active_reviews"]["42"], "2026-03-17T12:00:00Z" + ), + lambda state: review_state_machine.record_transition_notice_sent( + state["active_reviews"]["42"], "2026-03-17T12:00:00Z" + ), + {**make_state(), "active_reviews": {"42": {}}}, + ), + ( + "set_current_reviewer_resets_cycle_local_fields", + lambda state: review_state.set_current_reviewer( + state, 42, "alice", assignment_method="manual" + ), + lambda state: review_state_machine.set_current_reviewer( + state, 42, "alice", now=fixed_now, assignment_method="manual" + ), + { + **make_state(), + "active_reviews": { + "42": { + "sidecars": {"pending_privileged_commands": {"issue_comment:10": {"status": "pending"}}}, + "current_cycle_completion": {"completed": True}, + "current_cycle_write_approval": {"approved": True}, + } + }, + }, + ), + ( + "update_reviewer_activity_case_insensitive", + lambda state: review_state.update_reviewer_activity(state, 42, "ALICE"), + lambda state: review_state_machine.update_reviewer_activity(state, 42, "ALICE", now=fixed_now), + {**make_state(), "active_reviews": {"42": {"current_reviewer": "alice"}}}, + ), + ( + "mark_review_complete", + lambda state: review_state.mark_review_complete(state, 42, "alice", "unit-test"), + lambda state: review_state_machine.mark_review_complete( + state, + 42, + "alice", + "unit-test", + completed_at=fixed_now, + ), + make_state(), + ), + ( + "semantic_key_seen_materializes_channel_map", + lambda state: review_state.semantic_key_seen( + state["active_reviews"]["42"], "reviewer_comment", "issue_comment:100" + ), + lambda state: review_state_machine.semantic_key_seen( + state["active_reviews"]["42"], "reviewer_comment", "issue_comment:100" + ), + {**make_state(), "active_reviews": {"42": {}}}, + ), + ] + + for scenario_name, delegated, owner, initial_state in scenarios: + delegated_state = deepcopy(initial_state) + owner_state = deepcopy(initial_state) + + delegated_result = delegated(delegated_state) + owner_result = owner(owner_state) + + assert delegated_result == owner_result, scenario_name + assert delegated_state == owner_state, scenario_name + + +def test_get_current_cycle_boundary_post_deletion_fixture_driven_proof(): + fixture = _load_review_state_fixture("local_state_only_scenarios.json") + + class Bot: + @staticmethod + def parse_iso8601_timestamp(value): + return reviews.parse_github_timestamp(value) + + review_data = { + "active_cycle_started_at": None, + "cycle_started_at": "2026-03-17T09:00:00Z", + "assigned_at": "2026-03-17T08:00:00Z", + } + + assert fixture["scope"] == "local-state-only mutation" + assert review_state.get_current_cycle_boundary(Bot(), deepcopy(review_data)) == live_review_support.get_current_cycle_boundary( + deepcopy(review_data), + parse_timestamp=Bot.parse_iso8601_timestamp, + ) + + +def test_live_read_assisted_post_deletion_fixture_driven_proof(monkeypatch): + fixture = _load_live_read_repair_fixture() + + class Bot: + def __init__(self): + self.pull_request_result = {"ok": True, "pull_request": {"head": {"sha": "head-1"}}} + self.preferred_review = { + "id": 10, + "submitted_at": "2026-03-17T10:01:00Z", + "commit_id": "head-1", + "user": {"login": "alice"}, + } + + @staticmethod + def parse_iso8601_timestamp(value): + return reviews.parse_github_timestamp(value) + + monkeypatch.setattr(live_review_support, "read_pull_request_result", lambda bot, issue_number: bot.pull_request_result) + monkeypatch.setattr( + reviewer_review_helpers, + "get_preferred_current_reviewer_review_for_cycle", + lambda bot, issue_number, review_data, **kwargs: bot.preferred_review, + ) + + assert fixture["scope"] == "live-read-assisted review repair" + + delegated_review = { + "current_reviewer": "alice", + "reviewer_review": {"accepted": None, "seen_keys": []}, + "last_reviewer_activity": None, + "transition_warning_sent": None, + "transition_notice_sent_at": None, + } + owner_review = deepcopy(delegated_review) + delegated_bot = Bot() + owner_bot = Bot() + + assert review_state.accept_reviewer_review_from_live_review( + delegated_review, + delegated_bot.preferred_review, + actor="alice", + ) == review_state_live_repair.accept_reviewer_review_from_live_review( + owner_review, + owner_bot.preferred_review, + actor="alice", + ) + assert delegated_review == owner_review + + delegated_review = { + "current_reviewer": "alice", + "reviewer_review": {"accepted": None, "seen_keys": []}, + "last_reviewer_activity": None, + "transition_warning_sent": None, + "transition_notice_sent_at": None, + } + owner_review = deepcopy(delegated_review) + assert review_state.refresh_reviewer_review_from_live_preferred_review( + delegated_bot, + 42, + delegated_review, + ) == review_state_live_repair.refresh_reviewer_review_from_live_preferred_review( + owner_bot, + 42, + owner_review, + ) + assert delegated_review == owner_review + + delegated_review = deepcopy(delegated_review) + owner_review = deepcopy(owner_review) + assert review_state.repair_missing_reviewer_review_state( + delegated_bot, + 42, + delegated_review, + ) == review_state_live_repair.repair_missing_reviewer_review_state( + owner_bot, + 42, + owner_review, + ) + assert delegated_review == owner_review + + +def test_review_state_live_repair_behavior_no_longer_depends_on_reviews_parallel_bridge(monkeypatch): + class Bot: + pull_request_result = {"ok": True, "pull_request": {"head": {"sha": "head-1"}}} + preferred_review = { + "id": 10, + "submitted_at": "2026-03-17T10:01:00Z", + "commit_id": "head-1", + "user": {"login": "alice"}, + } + + @staticmethod + def parse_iso8601_timestamp(value): + return reviews.parse_github_timestamp(value) + + monkeypatch.setattr(live_review_support, "read_pull_request_result", lambda bot, issue_number: bot.pull_request_result) + monkeypatch.setattr( + reviewer_review_helpers, + "get_preferred_current_reviewer_review_for_cycle", + lambda bot, issue_number, review_data, **kwargs: bot.preferred_review, + ) + + if hasattr(reviews, "refresh_reviewer_review_from_live_preferred_review"): + monkeypatch.setattr( + reviews, + "refresh_reviewer_review_from_live_preferred_review", + lambda *args, **kwargs: (_ for _ in ()).throw(AssertionError("review_state bridge must not depend on reviews parallel bridge")), + ) + + review = { + "current_reviewer": "alice", + "reviewer_review": {"accepted": None, "seen_keys": []}, + "last_reviewer_activity": None, + "transition_warning_sent": None, + "transition_notice_sent_at": None, + } + + changed, preferred_review = review_state.refresh_reviewer_review_from_live_preferred_review(Bot(), 42, review) + + assert changed is True + assert preferred_review is not None + assert review["reviewer_review"]["accepted"]["semantic_key"] == "pull_request_review:10" + + +def test_g2b_review_state_machine_no_longer_owns_live_read_repair_behavior(): + machine_text = Path("scripts/reviewer_bot_core/review_state_machine.py").read_text(encoding="utf-8") + live_repair_text = Path("scripts/reviewer_bot_core/review_state_live_repair.py").read_text(encoding="utf-8") + helper_text = Path("scripts/reviewer_bot_core/reviewer_review_helpers.py").read_text(encoding="utf-8") + + assert "build_reviewer_review_record_from_live_review" not in machine_text + assert "get_preferred_current_reviewer_review_for_cycle" not in machine_text + assert "read_pull_request_result" not in machine_text + assert "def accept_reviewer_review_from_live_review(" not in machine_text + assert "def refresh_reviewer_review_from_live_preferred_review(" not in machine_text + assert "def repair_missing_reviewer_review_state(" not in machine_text + assert "def upsert_channel_accepted_record(" in machine_text + assert "def accept_reviewer_review_from_live_review(" in live_repair_text + assert "def refresh_reviewer_review_from_live_preferred_review(" in live_repair_text + assert "def repair_missing_reviewer_review_state(" in live_repair_text + assert "reviewer_review_helpers.build_reviewer_review_record_from_live_review(" in live_repair_text + assert "reviewer_review_helpers.get_preferred_current_reviewer_review_for_cycle(" in live_repair_text + assert "review_state_machine.upsert_channel_accepted_record(" in live_repair_text + assert "legacy_reviews.build_reviewer_review_record_from_live_review(" not in live_repair_text + assert "legacy_reviews.get_preferred_current_reviewer_review_for_cycle(" not in live_repair_text + assert "bot.github" not in live_repair_text + assert "github_api" not in live_repair_text + assert "def build_reviewer_review_record_from_live_review(" in helper_text + assert "def get_preferred_current_reviewer_review_for_cycle(" in helper_text diff --git a/tests/unit/reviewer_bot/test_review_submission_gap_repair_equivalence.py b/tests/unit/reviewer_bot/test_review_submission_gap_repair_equivalence.py new file mode 100644 index 000000000..69301e6bc --- /dev/null +++ b/tests/unit/reviewer_bot/test_review_submission_gap_repair_equivalence.py @@ -0,0 +1,66 @@ +import json +from pathlib import Path + +from scripts.reviewer_bot_core import deferred_gap_diagnosis + + +def _load_matrix() -> dict: + return json.loads( + Path("tests/fixtures/equivalence/review_submission_gap_repair/scenario_matrix.json").read_text( + encoding="utf-8" + ) + ) + + +def test_h4a_review_submission_gap_matrix_freezes_exact_visible_review_repair_outputs(): + matrix = _load_matrix() + + assert matrix["harness_id"] == "H4a review-submitted gap repair flow equivalence" + scenario = matrix["scenarios"][0] + + recommendation = deferred_gap_diagnosis.recommend_visible_review_repair( + {"current_reviewer": "alice"}, + { + "id": 202, + "submitted_at": "2026-03-25T11:00:00Z", + "commit_id": "head-1", + "user": {"login": "alice"}, + }, + "pull_request_review:202", + current_cycle_boundary=deferred_gap_diagnosis.parse_timestamp("2026-03-17T09:00:00Z"), + ) + + assert scenario["expected_reason"] is None + assert scenario["expected_diagnostic_reason"] is None + assert scenario["expected_repair_category"] == "review_submission_repair" + assert recommendation == ( + scenario["expected_recommendation_payload"]["author"], + scenario["expected_recommendation_payload"]["submitted_at"], + scenario["expected_recommendation_payload"]["commit_id"], + ) + + +def test_h4b_review_submission_gap_recommendation_flow_moves_to_core_owner(): + matrix = _load_matrix() + scenario = matrix["scenarios"][0] + + recommendation = deferred_gap_diagnosis.recommend_review_submission_gap_repair( + {"current_reviewer": "alice"}, + { + "id": 202, + "submitted_at": "2026-03-25T11:00:00Z", + "commit_id": "head-1", + "user": {"login": "alice"}, + }, + "pull_request_review:202", + artifact_status="artifact_missing", + current_cycle_boundary=deferred_gap_diagnosis.parse_timestamp("2026-03-17T09:00:00Z"), + ) + sweeper_text = Path("scripts/reviewer_bot_lib/sweeper.py").read_text(encoding="utf-8") + + assert recommendation == { + "category": scenario["expected_repair_category"], + "payload": scenario["expected_recommendation_payload"], + } + assert "deferred_gap_diagnosis.recommend_review_submission_gap_repair(" in sweeper_text + assert "artifact_status != \"exact_artifact_match\"" not in sweeper_text diff --git a/tests/unit/reviewer_bot/test_reviewer_response_equivalence.py b/tests/unit/reviewer_bot/test_reviewer_response_equivalence.py new file mode 100644 index 000000000..174b73c38 --- /dev/null +++ b/tests/unit/reviewer_bot/test_reviewer_response_equivalence.py @@ -0,0 +1,422 @@ +import json +from datetime import datetime, timezone +from pathlib import Path + +from scripts.reviewer_bot_core import ( + live_review_support, + reviewer_response_policy, + reviewer_review_helpers, +) +from scripts.reviewer_bot_lib import reviews +from tests.fixtures.reviewer_bot import ( + accept_contributor_revision, + accept_reviewer_review, + make_state, + make_tracked_review_state, + pull_request_payload, + review_payload, +) +from tests.fixtures.reviewer_bot_fakes import RouteGitHubApi, github_result +from tests.unit.reviewer_bot.test_reviews_live_fetch import _runtime + + +def _load_matrix() -> dict: + return json.loads( + Path("tests/fixtures/equivalence/reviewer_response/scenario_matrix.json").read_text(encoding="utf-8") + ) + + +def _run_scenario(monkeypatch, scenario_id: str) -> dict[str, object]: + runtime, review = _build_scenario(monkeypatch, scenario_id) + return reviews.compute_reviewer_response_state(runtime, 42, review) + + +def _build_scenario(monkeypatch, scenario_id: str): + state = make_state() + review = make_tracked_review_state(state, 42, reviewer="alice", active_cycle_started_at="2026-03-17T09:00:00Z") + + if scenario_id == "awaiting_reviewer_response_no_reviewer_activity": + routes = RouteGitHubApi().add_pull_request_snapshot(42, pull_request_payload(42, head_sha="head-1")).add_pull_request_reviews(42, []) + runtime = _runtime(monkeypatch, routes) + return runtime, review + + if scenario_id == "awaiting_reviewer_response_review_head_stale": + accept_reviewer_review(review, semantic_key="pull_request_review:10", timestamp="2026-03-17T10:01:00Z", actor="alice", reviewed_head_sha="head-1", source_precedence=1) + routes = RouteGitHubApi().add_pull_request_snapshot(42, pull_request_payload(42, head_sha="head-2")).add_pull_request_reviews(42, []) + runtime = _runtime(monkeypatch, routes) + return runtime, review + + if scenario_id == "awaiting_reviewer_response_contributor_revision_newer": + accept_reviewer_review(review, semantic_key="pull_request_review:99", timestamp="2026-03-17T11:00:00Z", actor="alice", reviewed_head_sha="head-0", source_precedence=1) + accept_contributor_revision(review, semantic_key="pull_request_sync:42:head-1", timestamp="2026-03-17T12:00:00Z", actor="alice", head_sha="head-1") + routes = RouteGitHubApi().add_pull_request_snapshot(42, pull_request_payload(42, head_sha="head-1")).add_pull_request_reviews( + 42, + [review_payload(99, state="COMMENTED", submitted_at="2026-03-17T11:00:00Z", commit_id="head-0", author="alice")], + ) + runtime = _runtime(monkeypatch, routes) + return runtime, review + + if scenario_id == "awaiting_contributor_response_completion_missing": + routes = RouteGitHubApi().add_pull_request_snapshot(42, pull_request_payload(42, head_sha="head-1")).add_pull_request_reviews( + 42, + [review_payload(10, state="COMMENTED", submitted_at="2026-03-17T10:01:00Z", commit_id="head-1", author="alice")], + ) + runtime = _runtime(monkeypatch, routes) + monkeypatch.setattr( + reviews, + "rebuild_pr_approval_state", + lambda bot, issue_number, review_data, **kwargs: ({"completed": False}, {"has_write_approval": False}), + ) + return runtime, review + + if scenario_id == "awaiting_write_approval_write_approval_missing": + accept_reviewer_review(review, semantic_key="pull_request_review:10", timestamp="2026-03-17T10:01:00Z", actor="alice", reviewed_head_sha="head-1", source_precedence=1) + routes = RouteGitHubApi().add_pull_request_snapshot(42, pull_request_payload(42, head_sha="head-1")).add_pull_request_reviews( + 42, + [review_payload(10, state="APPROVED", submitted_at="2026-03-17T10:01:00Z", commit_id="head-1", author="bob")], + ) + runtime = _runtime(monkeypatch, routes) + runtime.get_user_permission_status = lambda username, required_permission="triage": "denied" + return runtime, review + + if scenario_id == "projection_failed_pull_request_unavailable": + routes = RouteGitHubApi().add_request("GET", "pulls/42", result=github_result(502, {"message": "bad gateway"}, retry_attempts=1)) + runtime = _runtime(monkeypatch, routes) + return runtime, review + + if scenario_id == "projection_failed_pull_request_head_unavailable": + routes = RouteGitHubApi().add_request("GET", "pulls/42", status_code=200, payload={"state": "open", "head": {}}) + runtime = _runtime(monkeypatch, routes) + return runtime, review + + if scenario_id == "projection_failed_live_review_state_unknown": + accept_reviewer_review(review, semantic_key="pull_request_review:10", timestamp="2026-03-17T10:01:00Z", actor="alice", reviewed_head_sha="head-1", source_precedence=1) + routes = RouteGitHubApi().add_pull_request_snapshot(42, pull_request_payload(42, head_sha="head-1")).add_pull_request_reviews( + 42, + [review_payload(10, state="APPROVED", submitted_at="2026-03-17T10:01:00Z", commit_id="head-1", author="alice")], + ) + runtime = _runtime(monkeypatch, routes) + runtime.get_user_permission_status = lambda username, required_permission="triage": "unavailable" + return runtime, review + + raise AssertionError(f"Unhandled scenario: {scenario_id}") + + +def _legacy_compute_reviewer_response_state( + bot, + issue_number: int, + review_data: dict, + *, + issue_snapshot: dict | None = None, + pull_request: dict | None = None, + reviews_data: list[dict] | None = None, +) -> dict[str, object]: + if issue_snapshot is None: + issue_snapshot = bot.github.get_issue_or_pr_snapshot(issue_number) + if not isinstance(issue_snapshot, dict): + return {"state": "projection_failed", "reason": "issue_snapshot_unavailable"} + is_pr = isinstance(issue_snapshot.get("pull_request"), dict) + current_reviewer = review_data.get("current_reviewer") + if not isinstance(current_reviewer, str) or not current_reviewer.strip(): + return {"state": "untracked", "reason": "no_current_reviewer"} + + reviewer_comment = review_data.get("reviewer_comment", {}).get("accepted") + reviewer_review = review_data.get("reviewer_review", {}).get("accepted") + contributor_comment = review_data.get("contributor_comment", {}).get("accepted") + + if not is_pr: + if not reviewer_comment and not reviewer_review: + return { + "state": "awaiting_reviewer_response", + "reason": "no_reviewer_activity", + "anchor_timestamp": _legacy_initial_reviewer_anchor(review_data), + "reviewer_comment": reviewer_comment, + "reviewer_review": reviewer_review, + "contributor_comment": contributor_comment, + "contributor_handoff": None, + } + latest_reviewer_response = reviewer_comment + if reviewer_review_helpers.compare_records( + reviewer_review, + latest_reviewer_response, + parse_timestamp=live_review_support.parse_github_timestamp, + ) > 0: + latest_reviewer_response = reviewer_review + completion = review_data.get("current_cycle_completion") + if not isinstance(completion, dict) or not completion.get("completed"): + if review_data.get("review_completed_at"): + return {"state": "done", "reason": None} + return { + "state": "awaiting_contributor_response", + "reason": "completion_missing", + "anchor_timestamp": latest_reviewer_response.get("timestamp") if isinstance(latest_reviewer_response, dict) else None, + } + return {"state": "done", "reason": None} + + pull_request_result = live_review_support.read_pull_request_result(bot, issue_number, pull_request) + if not pull_request_result.get("ok"): + return {"state": "projection_failed", "reason": str(pull_request_result.get("reason"))} + pull_request = pull_request_result["pull_request"] + head = pull_request.get("head") + current_head = head.get("sha") if isinstance(head, dict) else None + if not isinstance(current_head, str) or not current_head.strip(): + return {"state": "projection_failed", "reason": "pull_request_head_unavailable"} + + if not reviewer_comment and not reviewer_review: + reviews_result = live_review_support.read_pull_request_reviews_result(bot, issue_number, reviews_data) + if not reviews_result.get("ok"): + return {"state": "projection_failed", "reason": str(reviews_result.get("reason"))} + reviews_data = reviews_result["reviews"] + preferred_live_review = reviewer_review_helpers.get_preferred_current_reviewer_review_for_cycle( + bot, + issue_number, + review_data, + pull_request=pull_request, + reviews=reviews_data, + ) + if preferred_live_review is not None: + reviewer_review = reviewer_review_helpers.build_reviewer_review_record_from_live_review( + preferred_live_review, + actor=current_reviewer, + ) + else: + return { + "state": "awaiting_reviewer_response", + "reason": "no_reviewer_activity", + "anchor_timestamp": _legacy_initial_reviewer_anchor(review_data), + "reviewer_comment": reviewer_comment, + "reviewer_review": reviewer_review, + "contributor_comment": contributor_comment, + "contributor_handoff": None, + } + + stored_review_head = reviewer_review.get("reviewed_head_sha") if isinstance(reviewer_review, dict) else None + refresh_live_review = reviews_data is not None or reviewer_review is None + if not refresh_live_review: + refresh_live_review = not isinstance(stored_review_head, str) or stored_review_head != current_head + + preferred_live_review = None + if refresh_live_review: + reviews_result = live_review_support.read_pull_request_reviews_result(bot, issue_number, reviews_data) + if not reviews_result.get("ok"): + return {"state": "projection_failed", "reason": str(reviews_result.get("reason"))} + reviews_data = reviews_result["reviews"] + preferred_live_review = reviewer_review_helpers.get_preferred_current_reviewer_review_for_cycle( + bot, + issue_number, + review_data, + pull_request=pull_request, + reviews=reviews_data, + ) + if preferred_live_review is not None: + reviewer_review = reviewer_review_helpers.build_reviewer_review_record_from_live_review( + preferred_live_review, + actor=current_reviewer, + ) + elif refresh_live_review: + reviewer_review = None + + latest_reviewer_response = reviewer_comment + if reviewer_review_helpers.compare_records( + reviewer_review, + latest_reviewer_response, + parse_timestamp=live_review_support.parse_github_timestamp, + ) > 0: + latest_reviewer_response = reviewer_review + + contributor_handoff = contributor_comment + contributor_revision = _legacy_contributor_revision_handoff_record( + review_data, + current_head, + reviewer_review if isinstance(reviewer_review, dict) else None, + ) + if reviewer_review_helpers.compare_records( + contributor_revision, + contributor_handoff, + parse_timestamp=live_review_support.parse_github_timestamp, + ) > 0: + contributor_handoff = contributor_revision + + if _legacy_compare_cross_channel_conversation(contributor_handoff, latest_reviewer_response) > 0: + reason = "contributor_comment_newer" + if isinstance(contributor_handoff, dict) and str(contributor_handoff.get("semantic_key", "")).startswith("pull_request_"): + reason = "contributor_revision_newer" + return { + "state": "awaiting_reviewer_response", + "reason": reason, + "anchor_timestamp": contributor_handoff.get("timestamp") if isinstance(contributor_handoff, dict) else None, + "current_head_sha": current_head, + "reviewer_comment": reviewer_comment, + "reviewer_review": reviewer_review, + "contributor_comment": contributor_comment, + "contributor_handoff": contributor_handoff, + } + + latest_review_head = reviewer_review.get("reviewed_head_sha") if isinstance(reviewer_review, dict) else None + if not isinstance(latest_review_head, str) or latest_review_head != current_head: + return { + "state": "awaiting_reviewer_response", + "reason": "review_head_stale", + "anchor_timestamp": contributor_handoff.get("timestamp") if isinstance(contributor_handoff, dict) else _legacy_initial_reviewer_anchor(review_data), + "current_head_sha": current_head, + "reviewer_comment": reviewer_comment, + "reviewer_review": reviewer_review, + "contributor_comment": contributor_comment, + "contributor_handoff": contributor_handoff, + } + + completion, write_approval, approval_failure = reviews.resolve_pr_approval_state( + bot, + issue_number, + review_data, + pull_request=pull_request, + reviews=reviews_data, + ) + if completion is None or write_approval is None: + return {"state": "projection_failed", "reason": approval_failure or "live_review_state_unknown"} + if not completion.get("completed"): + return { + "state": "awaiting_contributor_response", + "reason": "completion_missing", + "anchor_timestamp": latest_reviewer_response.get("timestamp") if isinstance(latest_reviewer_response, dict) else None, + "current_head_sha": current_head, + "reviewer_comment": reviewer_comment, + "reviewer_review": reviewer_review, + "contributor_comment": contributor_comment, + "contributor_handoff": contributor_handoff, + } + if not write_approval.get("has_write_approval"): + return { + "state": "awaiting_write_approval", + "reason": "write_approval_missing", + "anchor_timestamp": latest_reviewer_response.get("timestamp") if isinstance(latest_reviewer_response, dict) else None, + "current_head_sha": current_head, + "reviewer_comment": reviewer_comment, + "reviewer_review": reviewer_review, + "contributor_comment": contributor_comment, + "contributor_handoff": contributor_handoff, + } + return { + "state": "done", + "reason": "write_approval_present", + "anchor_timestamp": latest_reviewer_response.get("timestamp") if isinstance(latest_reviewer_response, dict) else None, + "current_head_sha": current_head, + "reviewer_comment": reviewer_comment, + "reviewer_review": reviewer_review, + "contributor_comment": contributor_comment, + "contributor_handoff": contributor_handoff, + } + + +def _legacy_record_timestamp(record: dict | None): + if not isinstance(record, dict): + return None + return reviews.parse_github_timestamp(record.get("timestamp")) + + +def _legacy_compare_cross_channel_conversation(contributor: dict | None, reviewer: dict | None) -> int: + contributor_time = _legacy_record_timestamp(contributor) or datetime.min.replace(tzinfo=timezone.utc) + reviewer_time = _legacy_record_timestamp(reviewer) or datetime.min.replace(tzinfo=timezone.utc) + contributor_key = str((contributor or {}).get("semantic_key", "")) + reviewer_key = str((reviewer or {}).get("semantic_key", "")) + if (contributor_time, contributor_key) == (reviewer_time, reviewer_key): + return 0 + if contributor_time > reviewer_time: + return 1 + if contributor_time < reviewer_time: + return -1 + if contributor_key >= reviewer_key: + return 1 + return -1 + + +def _legacy_initial_reviewer_anchor(review_data: dict) -> str | None: + for field in ("active_cycle_started_at", "cycle_started_at", "assigned_at"): + value = review_data.get(field) + if isinstance(value, str) and value: + return value + return None + + +def _legacy_contributor_revision_handoff_record(review_data: dict, current_head: str | None, reviewer_review: dict | None) -> dict | None: + contributor_revision = review_data.get("contributor_revision", {}).get("accepted") + if not isinstance(contributor_revision, dict): + return None + revision_head = contributor_revision.get("reviewed_head_sha") + if not isinstance(revision_head, str) or not isinstance(current_head, str): + return None + if revision_head != current_head: + return None + reviewer_head = reviewer_review.get("reviewed_head_sha") if isinstance(reviewer_review, dict) else None + if isinstance(reviewer_head, str) and reviewer_head == current_head: + return None + return contributor_revision + + +def test_reviewer_response_derivation_supports_pure_inputs_without_live_reads(): + state = make_state() + review = make_tracked_review_state(state, 42, reviewer="alice", active_cycle_started_at="2026-03-17T09:00:00Z") + accept_reviewer_review( + review, + semantic_key="pull_request_review:10", + timestamp="2026-03-17T10:01:00Z", + actor="alice", + reviewed_head_sha="head-1", + source_precedence=1, + ) + + result = reviewer_response_policy.derive_reviewer_response_state( + review, + issue_is_pull_request=True, + current_head="head-1", + approval_result={ + "ok": True, + "completion": {"completed": False}, + "write_approval": {"has_write_approval": False}, + }, + ) + + assert result["state"] == "awaiting_contributor_response" + assert result["reason"] == "completion_missing" + assert result["reviewer_review"]["semantic_key"] == "pull_request_review:10" + + +def test_h1a_reviewer_response_scenario_matrix_matches_frozen_outputs(monkeypatch): + matrix = _load_matrix() + + assert matrix["harness_id"] == "H1a reviewer-response derivation equivalence" + + for scenario in matrix["scenarios"]: + with monkeypatch.context() as scenario_monkeypatch: + result = _run_scenario(scenario_monkeypatch, scenario["id"]) + assert result["state"] == scenario["state"], scenario["id"] + assert result["reason"] == scenario["reason"], scenario["id"] + + +def test_h1b_reviewer_response_policy_matches_legacy_derivation_for_frozen_matrix(monkeypatch): + matrix = _load_matrix() + + for scenario in matrix["scenarios"]: + with monkeypatch.context() as scenario_monkeypatch: + runtime, review = _build_scenario(scenario_monkeypatch, scenario["id"]) + legacy_result = _legacy_compute_reviewer_response_state(runtime, 42, review) + policy_result = reviewer_response_policy.compute_reviewer_response_state(runtime, 42, review) + assert policy_result == legacy_result, scenario["id"] + + +def test_h1b_reviews_module_delegates_reviewer_response_to_policy_owner(): + reviews_text = Path("scripts/reviewer_bot_lib/reviews.py").read_text(encoding="utf-8") + policy_text = Path("scripts/reviewer_bot_core/reviewer_response_policy.py").read_text(encoding="utf-8") + helper_text = Path("scripts/reviewer_bot_core/reviewer_review_helpers.py").read_text(encoding="utf-8") + + assert "return reviewer_response_policy.compute_reviewer_response_state(" in reviews_text + assert "def compute_reviewer_response_state(" in policy_text + assert "reviewer_review_helpers.get_preferred_current_reviewer_review_for_cycle(" in policy_text + assert "reviewer_review_helpers.build_reviewer_review_record_from_live_review(" in policy_text + assert "reviewer_review_helpers.compare_records(" in policy_text + assert "legacy_reviews.get_preferred_current_reviewer_review_for_cycle(" not in policy_text + assert "legacy_reviews.build_reviewer_review_record_from_live_review(" not in policy_text + assert "legacy_reviews._compare_records(" not in policy_text + assert "def get_preferred_current_reviewer_review_for_cycle(" in helper_text + assert "def build_reviewer_review_record_from_live_review(" in helper_text + assert "def compare_records(" in helper_text diff --git a/tests/unit/reviewer_bot/test_reviews_api_contract.py b/tests/unit/reviewer_bot/test_reviews_api_contract.py new file mode 100644 index 000000000..d9da94ff3 --- /dev/null +++ b/tests/unit/reviewer_bot/test_reviews_api_contract.py @@ -0,0 +1,60 @@ +import pytest + +from scripts.reviewer_bot_core import live_review_support +from scripts.reviewer_bot_lib import maintenance, review_state, reviews +from tests.fixtures.fake_runtime import FakeReviewerBotRuntime +from tests.fixtures.reviewer_bot import make_state +from tests.fixtures.reviewer_bot_fakes import RouteGitHubApi, github_result + + +def test_list_open_items_with_status_labels_fails_closed_on_unavailable(monkeypatch): + runtime = FakeReviewerBotRuntime(monkeypatch) + routes = RouteGitHubApi().add_request( + "GET", + "issues?state=open&labels=status%3A%20awaiting%20contributor%20response&per_page=100&page=1", + result=github_result(502, {"message": "bad gateway"}, retry_attempts=1), + ) + runtime.github.stub(routes) + + with pytest.raises(RuntimeError, match="server_error"): + reviews.list_open_items_with_status_labels(runtime) + + +def test_collect_status_projection_repair_items_uses_review_support_listing_not_runtime_bag(monkeypatch): + runtime = FakeReviewerBotRuntime(monkeypatch) + state = make_state() + review = review_state.ensure_review_entry(state, 42, create=True) + assert review is not None + review["current_reviewer"] = "alice" + + runtime.list_open_items_with_status_labels = lambda: pytest.fail( + "status projection repair collection must use review support, not runtime bag access" + ) + monkeypatch.setattr(maintenance.reviews, "list_open_items_with_status_labels", lambda bot: [99, 42]) + + assert maintenance.collect_status_projection_repair_items(runtime, state) == [42, 99] + + +def test_get_pull_request_reviews_delegates_to_live_review_support(monkeypatch): + runtime = FakeReviewerBotRuntime(monkeypatch) + observed = {} + + def fake_read(bot, issue_number, reviews=None): + observed.update({"bot": bot, "issue_number": issue_number, "reviews": reviews}) + return {"ok": True, "reviews": [{"id": 10}]} + + monkeypatch.setattr(live_review_support, "read_pull_request_reviews_result", fake_read) + + assert reviews.get_pull_request_reviews(runtime, 42) == [{"id": 10}] + assert observed == {"bot": runtime, "issue_number": 42, "reviews": None} + + +def test_get_pull_request_reviews_fails_closed_when_live_review_read_fails(monkeypatch): + runtime = FakeReviewerBotRuntime(monkeypatch) + monkeypatch.setattr( + live_review_support, + "read_pull_request_reviews_result", + lambda bot, issue_number, reviews=None: {"ok": False, "reason": "reviews_unavailable", "failure_kind": "server_error"}, + ) + + assert reviews.get_pull_request_reviews(runtime, 42) is None diff --git a/tests/unit/reviewer_bot/test_reviews_live_fetch.py b/tests/unit/reviewer_bot/test_reviews_live_fetch.py new file mode 100644 index 000000000..1dfee945e --- /dev/null +++ b/tests/unit/reviewer_bot/test_reviews_live_fetch.py @@ -0,0 +1,392 @@ +from types import SimpleNamespace + +from scripts.reviewer_bot_core import approval_policy +from scripts.reviewer_bot_lib import review_state, reviews +from scripts.reviewer_bot_lib.config import ( + STATUS_AWAITING_CONTRIBUTOR_RESPONSE_LABEL, + STATUS_AWAITING_REVIEWER_RESPONSE_LABEL, + STATUS_AWAITING_WRITE_APPROVAL_LABEL, +) +from tests.fixtures.fake_runtime import FakeReviewerBotRuntime +from tests.fixtures.reviewer_bot import ( + accept_contributor_revision, + accept_reviewer_comment, + accept_reviewer_review, + accepted_record, + issue_snapshot, + make_state, + make_tracked_review_state, + pull_request_payload, + review_payload, +) +from tests.fixtures.reviewer_bot_fakes import RouteGitHubApi, github_result + + +def _runtime(monkeypatch, routes=None): + runtime = FakeReviewerBotRuntime(monkeypatch) + runtime.get_issue_or_pr_snapshot = lambda issue_number: issue_snapshot(issue_number, state="open", is_pull_request=True) + runtime.get_user_permission_status = lambda username, required_permission="push": "granted" + if routes is not None: + runtime.github.stub(routes) + return runtime + + +def test_project_status_labels_uses_live_current_reviewer_review_when_channel_state_missing(monkeypatch): + state = make_state() + make_tracked_review_state(state, 42, reviewer="alice", active_cycle_started_at="2026-03-17T09:00:00Z") + routes = RouteGitHubApi().add_pull_request_snapshot(42, pull_request_payload(42, head_sha="head-1")).add_pull_request_reviews( + 42, + [review_payload(10, state="COMMENTED", submitted_at="2026-03-17T10:01:00Z", commit_id="head-1", author="alice")], + ) + runtime = _runtime(monkeypatch, routes) + + desired_labels, metadata = reviews.project_status_labels_for_item(runtime, 42, state) + + assert desired_labels == {STATUS_AWAITING_CONTRIBUTOR_RESPONSE_LABEL} + assert metadata["reason"] == "completion_missing" + + +def test_compute_reviewer_response_state_refreshes_stale_stored_review_from_live_current_head(monkeypatch): + state = make_state() + review = make_tracked_review_state(state, 42, reviewer="alice", active_cycle_started_at="2026-03-17T09:00:00Z") + accept_reviewer_review(review, semantic_key="pull_request_review:99", timestamp="2026-03-17T11:00:00Z", actor="alice", reviewed_head_sha="head-0", source_precedence=1) + routes = RouteGitHubApi().add_pull_request_snapshot(42, pull_request_payload(42, head_sha="head-1")).add_pull_request_reviews( + 42, + [ + review_payload(10, state="COMMENTED", submitted_at="2026-03-17T10:01:00Z", commit_id="head-1", author="alice"), + review_payload(99, state="COMMENTED", submitted_at="2026-03-17T11:00:00Z", commit_id="head-0", author="alice"), + ], + ) + runtime = _runtime(monkeypatch, routes) + monkeypatch.setattr(reviews, "rebuild_pr_approval_state", lambda bot, issue_number, review_data, **kwargs: ({"completed": False}, {"has_write_approval": False})) + + response_state = reviews.compute_reviewer_response_state(runtime, 42, review) + + assert response_state["state"] == "awaiting_contributor_response" + assert response_state["reason"] == "completion_missing" + assert response_state["reviewer_review"]["semantic_key"] == "pull_request_review:10" + assert response_state["reviewer_review"]["reviewed_head_sha"] == "head-1" + + +def test_repair_missing_reviewer_review_state_refreshes_to_preferred_current_head_review(monkeypatch): + state = make_state() + review = make_tracked_review_state(state, 42, reviewer="alice", active_cycle_started_at="2026-03-17T09:00:00Z") + accept_reviewer_review(review, semantic_key="pull_request_review:99", timestamp="2026-03-17T11:00:00Z", actor="alice", reviewed_head_sha="head-0", source_precedence=1) + routes = RouteGitHubApi().add_pull_request_snapshot(42, pull_request_payload(42, head_sha="head-1")).add_pull_request_reviews( + 42, + [ + review_payload(10, state="COMMENTED", submitted_at="2026-03-17T10:00:00Z", commit_id="head-1", author="alice"), + review_payload(99, state="COMMENTED", submitted_at="2026-03-17T11:00:00Z", commit_id="head-0", author="alice"), + ], + ) + runtime = _runtime(monkeypatch, routes) + + assert review_state.repair_missing_reviewer_review_state(runtime, 42, review) is True + accepted = review["reviewer_review"]["accepted"] + assert accepted["semantic_key"] == "pull_request_review:10" + assert accepted["reviewed_head_sha"] == "head-1" + + +def test_refresh_reviewer_review_from_live_preferred_review_returns_true_for_activity_only_change(monkeypatch): + review = make_tracked_review_state(make_state(), 42, reviewer="alice", active_cycle_started_at="2026-03-17T09:00:00Z") + review["reviewer_review"] = { + "accepted": { + **accepted_record( + semantic_key="pull_request_review:10", + timestamp="2026-03-17T10:01:00Z", + actor="alice", + reviewed_head_sha="head-1", + ), + "source_precedence": 1, + "payload": {}, + }, + "seen_keys": ["pull_request_review:10"], + } + review["last_reviewer_activity"] = "2026-03-17T09:00:00Z" + review["transition_warning_sent"] = "2026-03-18T00:00:00Z" + review["transition_notice_sent_at"] = "2026-03-25T00:00:00Z" + routes = RouteGitHubApi().add_pull_request_snapshot(42, pull_request_payload(42, head_sha="head-1")).add_pull_request_reviews( + 42, + [review_payload(10, state="COMMENTED", submitted_at="2026-03-17T10:01:00Z", commit_id="head-1", author="alice")], + ) + runtime = _runtime(monkeypatch, routes) + + changed, preferred_review = review_state.refresh_reviewer_review_from_live_preferred_review(runtime, 42, review) + + assert changed is True + assert preferred_review is not None + assert review["last_reviewer_activity"] == "2026-03-17T10:01:00Z" + assert review["transition_warning_sent"] is None + assert review["transition_notice_sent_at"] is None + + +def test_accept_reviewer_review_from_live_review_accepts_matching_current_reviewer_review_payload(monkeypatch): + review = make_tracked_review_state(make_state(), 42, reviewer="alice", active_cycle_started_at="2026-03-17T09:00:00Z") + live_review = review_payload( + 10, + state="COMMENTED", + submitted_at="2026-03-17T10:01:00Z", + commit_id="head-1", + author="alice", + ) + + changed = review_state.accept_reviewer_review_from_live_review(review, live_review) + + assert changed is True + assert review["reviewer_review"]["accepted"]["semantic_key"] == "pull_request_review:10" + assert review["reviewer_review"]["accepted"]["reviewed_head_sha"] == "head-1" + + +def test_refresh_reviewer_review_from_live_preferred_review_returns_false_for_reviewer_mismatch(monkeypatch): + review = make_tracked_review_state(make_state(), 42, reviewer="alice", active_cycle_started_at="2026-03-17T09:00:00Z") + routes = RouteGitHubApi().add_pull_request_snapshot(42, pull_request_payload(42, head_sha="head-1")).add_pull_request_reviews( + 42, + [review_payload(10, state="COMMENTED", submitted_at="2026-03-17T10:01:00Z", commit_id="head-1", author="bob")], + ) + runtime = _runtime(monkeypatch, routes) + + changed, preferred_review = review_state.refresh_reviewer_review_from_live_preferred_review(runtime, 42, review) + + assert changed is False + assert preferred_review is None + assert review["reviewer_review"]["accepted"] is None + + +def test_refresh_reviewer_review_from_live_preferred_review_returns_false_when_no_preferred_review_found(monkeypatch): + review = make_tracked_review_state(make_state(), 42, reviewer="alice", active_cycle_started_at="2026-03-17T09:00:00Z") + routes = RouteGitHubApi().add_pull_request_snapshot(42, pull_request_payload(42, head_sha="head-1")).add_pull_request_reviews(42, []) + runtime = _runtime(monkeypatch, routes) + + changed, preferred_review = review_state.refresh_reviewer_review_from_live_preferred_review(runtime, 42, review) + + assert changed is False + assert preferred_review is None + + +def test_repair_missing_reviewer_review_state_is_noop_when_preferred_review_is_already_stored(monkeypatch): + review = make_tracked_review_state(make_state(), 42, reviewer="alice", active_cycle_started_at="2026-03-17T09:00:00Z") + review["reviewer_review"] = { + "accepted": { + **accepted_record( + semantic_key="pull_request_review:10", + timestamp="2026-03-17T10:01:00Z", + actor="alice", + reviewed_head_sha="head-1", + ), + "source_precedence": 1, + "payload": {}, + }, + "seen_keys": ["pull_request_review:10"], + } + review["last_reviewer_activity"] = "2026-03-17T10:01:00Z" + routes = RouteGitHubApi().add_pull_request_snapshot(42, pull_request_payload(42, head_sha="head-1")).add_pull_request_reviews( + 42, + [review_payload(10, state="COMMENTED", submitted_at="2026-03-17T10:01:00Z", commit_id="head-1", author="alice")], + ) + runtime = _runtime(monkeypatch, routes) + + before = { + "reviewer_review": review["reviewer_review"].copy(), + "last_reviewer_activity": review["last_reviewer_activity"], + "transition_warning_sent": review.get("transition_warning_sent"), + "transition_notice_sent_at": review.get("transition_notice_sent_at"), + } + + assert review_state.repair_missing_reviewer_review_state(runtime, 42, review) is False + assert review["reviewer_review"] == before["reviewer_review"] + assert review["last_reviewer_activity"] == before["last_reviewer_activity"] + assert review.get("transition_warning_sent") == before["transition_warning_sent"] + assert review.get("transition_notice_sent_at") == before["transition_notice_sent_at"] + + +def test_project_status_labels_uses_commit_id_and_comment_freshness(monkeypatch): + state = make_state() + review = make_tracked_review_state(state, 42, reviewer="alice", active_cycle_started_at="2026-03-17T09:00:00Z") + accept_reviewer_comment(review, semantic_key="issue_comment:1", timestamp="2026-03-17T10:00:00Z", actor="alice") + accept_reviewer_review(review, semantic_key="pull_request_review:10", timestamp="2026-03-17T10:01:00Z", actor="alice", reviewed_head_sha="head-1", source_precedence=1) + routes = RouteGitHubApi().add_pull_request_snapshot(42, pull_request_payload(42, head_sha="head-2")).add_pull_request_reviews(42, []) + runtime = _runtime(monkeypatch, routes) + + desired_labels, metadata = reviews.project_status_labels_for_item(runtime, 42, state) + + assert desired_labels == {STATUS_AWAITING_REVIEWER_RESPONSE_LABEL} + assert metadata["reason"] == "review_head_stale" + + +def test_compute_reviewer_response_state_reports_review_head_stale_when_current_head_has_no_matching_review(monkeypatch): + state = make_state() + review = make_tracked_review_state(state, 42, reviewer="alice", active_cycle_started_at="2026-03-17T09:00:00Z") + accept_reviewer_review(review, semantic_key="pull_request_review:10", timestamp="2026-03-17T10:01:00Z", actor="alice", reviewed_head_sha="head-1", source_precedence=1) + routes = RouteGitHubApi().add_pull_request_snapshot(42, pull_request_payload(42, head_sha="head-2")).add_pull_request_reviews(42, []) + runtime = _runtime(monkeypatch, routes) + + response_state = reviews.compute_reviewer_response_state(runtime, 42, review) + + assert response_state["state"] == "awaiting_reviewer_response" + assert response_state["reason"] == "review_head_stale" + + +def test_compute_reviewer_response_state_reports_awaiting_write_approval_after_completion(monkeypatch): + state = make_state() + review = make_tracked_review_state(state, 42, reviewer="alice", active_cycle_started_at="2026-03-17T09:00:00Z") + accept_reviewer_review(review, semantic_key="pull_request_review:10", timestamp="2026-03-17T10:01:00Z", actor="alice", reviewed_head_sha="head-1", source_precedence=1) + routes = RouteGitHubApi().add_pull_request_snapshot(42, pull_request_payload(42, head_sha="head-1")).add_pull_request_reviews( + 42, + [review_payload(10, state="APPROVED", submitted_at="2026-03-17T10:01:00Z", commit_id="head-1", author="bob")], + ) + runtime = _runtime(monkeypatch, routes) + runtime.get_user_permission_status = lambda username, required_permission="triage": "denied" + + response_state = reviews.compute_reviewer_response_state(runtime, 42, review) + + assert response_state["state"] == "awaiting_write_approval" + assert response_state["reason"] == "write_approval_missing" + + +def test_compute_reviewer_response_state_keeps_contributor_handoff_when_stored_review_is_stale(monkeypatch): + state = make_state() + review = make_tracked_review_state(state, 42, reviewer="alice", active_cycle_started_at="2026-03-17T09:00:00Z") + accept_reviewer_review(review, semantic_key="pull_request_review:99", timestamp="2026-03-17T11:00:00Z", actor="alice", reviewed_head_sha="head-0", source_precedence=1) + accept_contributor_revision(review, semantic_key="pull_request_sync:42:head-1", timestamp="2026-03-17T12:00:00Z", actor="alice", head_sha="head-1") + routes = RouteGitHubApi().add_pull_request_snapshot(42, pull_request_payload(42, head_sha="head-1")).add_pull_request_reviews( + 42, + [review_payload(99, state="COMMENTED", submitted_at="2026-03-17T11:00:00Z", commit_id="head-0", author="alice")], + ) + runtime = _runtime(monkeypatch, routes) + + response_state = reviews.compute_reviewer_response_state(runtime, 42, review) + + assert response_state["state"] == "awaiting_reviewer_response" + assert response_state["reason"] == "contributor_revision_newer" + + +def test_project_status_labels_emits_awaiting_write_approval_only_after_completion(monkeypatch): + state = make_state() + review = make_tracked_review_state(state, 42, reviewer="alice", active_cycle_started_at="2026-03-17T09:00:00Z") + accept_reviewer_comment(review, semantic_key="issue_comment:1", timestamp="2026-03-17T10:00:00Z", actor="alice") + accept_reviewer_review(review, semantic_key="pull_request_review:10", timestamp="2026-03-17T10:01:00Z", actor="alice", reviewed_head_sha="head-1", source_precedence=1) + routes = RouteGitHubApi().add_pull_request_snapshot(42, pull_request_payload(42, head_sha="head-1")).add_pull_request_reviews( + 42, + [review_payload(10, state="APPROVED", submitted_at="2026-03-17T10:01:00Z", commit_id="head-1", author="bob")], + ) + runtime = _runtime(monkeypatch, routes) + runtime.get_user_permission_status = lambda username, required_permission="triage": "denied" + + desired_labels, metadata = reviews.project_status_labels_for_item(runtime, 42, state) + + assert desired_labels == {STATUS_AWAITING_WRITE_APPROVAL_LABEL} + assert metadata["state"] == "awaiting_write_approval" + review["mandatory_approver_required"] = True + desired_labels_again, _ = reviews.project_status_labels_for_item(runtime, 42, state) + assert desired_labels_again == {STATUS_AWAITING_WRITE_APPROVAL_LABEL} + + +def test_compute_reviewer_response_state_reports_pull_request_unavailable(monkeypatch): + state = make_state() + review = make_tracked_review_state(state, 42, reviewer="alice", active_cycle_started_at="2026-03-17T09:00:00Z") + routes = RouteGitHubApi().add_request("GET", "pulls/42", result=github_result(502, {"message": "bad gateway"}, retry_attempts=1)) + runtime = _runtime(monkeypatch, routes) + + response_state = reviews.compute_reviewer_response_state(runtime, 42, review) + + assert response_state["state"] == "projection_failed" + assert response_state["reason"] == "pull_request_unavailable" + + +def test_compute_reviewer_response_state_fails_closed_without_stored_activity_when_pr_head_invalid(monkeypatch): + state = make_state() + review = make_tracked_review_state(state, 42, reviewer="alice", active_cycle_started_at="2026-03-17T09:00:00Z") + routes = RouteGitHubApi().add_request("GET", "pulls/42", status_code=200, payload={"state": "open", "head": {}}) + runtime = _runtime(monkeypatch, routes) + + response_state = reviews.compute_reviewer_response_state(runtime, 42, review) + + assert response_state["state"] == "projection_failed" + assert response_state["reason"] == "pull_request_head_unavailable" + + +def test_compute_reviewer_response_state_reports_permission_unavailable(monkeypatch): + state = make_state() + review = make_tracked_review_state(state, 42, reviewer="alice", active_cycle_started_at="2026-03-17T09:00:00Z") + accept_reviewer_review(review, semantic_key="pull_request_review:10", timestamp="2026-03-17T10:01:00Z", actor="alice", reviewed_head_sha="head-1", source_precedence=1) + routes = RouteGitHubApi().add_pull_request_snapshot(42, pull_request_payload(42, head_sha="head-1")).add_pull_request_reviews( + 42, + [review_payload(10, state="APPROVED", submitted_at="2026-03-17T10:01:00Z", commit_id="head-1", author="alice")], + ) + runtime = _runtime(monkeypatch, routes) + runtime.get_user_permission_status = lambda username, required_permission="triage": "unavailable" + + response_state = reviews.compute_reviewer_response_state(runtime, 42, review) + + assert response_state["state"] == "projection_failed" + assert response_state["reason"] == "live_review_state_unknown" + + +def test_trigger_mandatory_approver_escalation_sets_required_label_and_ping(monkeypatch): + state = make_state() + review = make_tracked_review_state(state, 42, reviewer="alice") + comments = [] + labels = [] + runtime = SimpleNamespace( + github=SimpleNamespace( + ensure_label_exists=lambda label: True, + post_comment=lambda issue_number, body: comments.append((issue_number, body)) or True, + ), + add_label_with_status=lambda issue_number, label: labels.append((issue_number, label)) or True, + logger=SimpleNamespace(event=lambda *args, **kwargs: None), + ) + monkeypatch.setattr(reviews, "_now_iso", lambda: "2026-03-21T10:00:00+00:00") + + changed = reviews.trigger_mandatory_approver_escalation(runtime, state, 42) + + assert changed is True + assert review["mandatory_approver_required"] is True + assert review["mandatory_approver_label_applied_at"] == "2026-03-21T10:00:00+00:00" + assert review["mandatory_approver_pinged_at"] == "2026-03-21T10:00:00+00:00" + assert labels == [(42, reviews.MANDATORY_TRIAGE_APPROVER_LABEL)] + assert comments == [(42, reviews.MANDATORY_TRIAGE_ESCALATION_TEMPLATE)] + + +def test_rebuild_pr_approval_state_applies_core_result_as_retained_execution_support(monkeypatch): + state = make_state() + review = make_tracked_review_state(state, 42, reviewer="alice") + monkeypatch.setattr( + approval_policy, + "compute_pr_approval_state_result", + lambda bot, issue_number, review_data, **kwargs: { + "ok": True, + "completion": {"completed": True}, + "write_approval": {"has_write_approval": True}, + "current_head_sha": "head-1", + }, + ) + + completion, write_approval = reviews.rebuild_pr_approval_state(SimpleNamespace(), 42, review) + + assert completion == {"completed": True} + assert write_approval == {"has_write_approval": True} + assert review["active_head_sha"] == "head-1" + assert review["review_completion_source"] == "live_review_rebuild" + + +def test_satisfy_mandatory_approver_requirement_clears_required_and_records_satisfaction(monkeypatch): + state = make_state() + review = make_tracked_review_state(state, 42, reviewer="alice") + review["mandatory_approver_required"] = True + removed = [] + comments = [] + runtime = SimpleNamespace( + remove_label_with_status=lambda issue_number, label: removed.append((issue_number, label)) or True, + github=SimpleNamespace(post_comment=lambda issue_number, body: comments.append((issue_number, body)) or True), + logger=SimpleNamespace(event=lambda *args, **kwargs: None), + ) + monkeypatch.setattr(reviews, "_now_iso", lambda: "2026-03-21T11:00:00+00:00") + + changed = reviews.satisfy_mandatory_approver_requirement(runtime, state, 42, "carol") + + assert changed is True + assert review["mandatory_approver_required"] is False + assert review["mandatory_approver_satisfied_by"] == "carol" + assert review["mandatory_approver_satisfied_at"] == "2026-03-21T11:00:00+00:00" + assert removed == [(42, reviews.MANDATORY_TRIAGE_APPROVER_LABEL)] + assert comments == [(42, reviews.MANDATORY_TRIAGE_SATISFIED_TEMPLATE.format(approver="carol"))] diff --git a/tests/unit/reviewer_bot/test_reviews_projection.py b/tests/unit/reviewer_bot/test_reviews_projection.py new file mode 100644 index 000000000..ba441f09c --- /dev/null +++ b/tests/unit/reviewer_bot/test_reviews_projection.py @@ -0,0 +1,263 @@ +import json +from pathlib import Path +from types import SimpleNamespace + +from scripts.reviewer_bot_core import approval_policy, live_review_support +from scripts.reviewer_bot_lib import reviews +from scripts.reviewer_bot_lib.config import GitHubApiResult +from tests.fixtures.reviewer_bot import ( + make_state, + make_tracked_review_state, + pull_request_payload, + review_payload, +) + + +def _bot(**overrides): + github = SimpleNamespace( + get_pull_request_reviews=lambda issue_number: [], + get_issue_or_pr_snapshot=lambda issue_number: {"number": issue_number, "state": "open", "pull_request": {}, "labels": []}, + get_user_permission_status=lambda username, required_permission="push": "granted", + ) + bot = SimpleNamespace( + github_api_request=lambda method, endpoint, data=None, extra_headers=None, **kwargs: GitHubApiResult(200, {}, {}, "ok", True, None, 0, None), + github_api=lambda method, endpoint, data=None: {}, + github=github, + parse_github_timestamp=reviews.parse_github_timestamp, + parse_iso8601_timestamp=reviews.parse_github_timestamp, + ensure_review_entry=lambda state, issue_number, create=False: None, + ) + for key, value in overrides.items(): + setattr(bot, key, value) + return bot + + +def test_compute_reviewer_response_state_is_pure_for_pr_projection(): + state = make_state() + review = make_tracked_review_state( + state, + 42, + reviewer="alice", + active_cycle_started_at="2026-03-17T09:00:00Z", + ) + before = json.loads(json.dumps(review)) + bot = _bot( + github_api_request=lambda method, endpoint, data=None, extra_headers=None, **kwargs: GitHubApiResult( + 200, + pull_request_payload(42, head_sha="head-1") if endpoint == "pulls/42" else [], + {}, + "ok", + True, + None, + 0, + None, + ) + ) + + response_state = reviews.compute_reviewer_response_state(bot, 42, review) + + assert response_state["state"] == "awaiting_reviewer_response" + assert review == before + + +def test_compute_reviewer_response_state_keeps_mutable_approval_rebuild_support_out_of_derivation(): + review = make_tracked_review_state( + make_state(), + 42, + reviewer="alice", + active_cycle_started_at="2026-03-17T09:00:00Z", + ) + review["reviewer_review"]["accepted"] = { + "semantic_key": "pull_request_review:10", + "timestamp": "2026-03-17T10:01:00Z", + "actor": "alice", + "reviewed_head_sha": "head-1", + "source_precedence": 1, + "payload": {}, + } + review["reviewer_review"]["seen_keys"] = ["pull_request_review:10"] + before = json.loads(json.dumps(review)) + bot = _bot( + github_api_request=lambda method, endpoint, data=None, extra_headers=None, **kwargs: GitHubApiResult( + 200, + pull_request_payload(42, head_sha="head-1") + if endpoint == "pulls/42" + else [ + review_payload( + 11, + state="APPROVED", + submitted_at="2026-03-17T10:05:00Z", + commit_id="head-1", + author="bob", + ) + ], + {}, + "ok", + True, + None, + 0, + None, + ) + ) + bot.github.get_user_permission_status = lambda username, required_permission="push": "denied" + + response_state = reviews.compute_reviewer_response_state(bot, 42, review) + + assert response_state["state"] == "awaiting_write_approval" + assert response_state["reason"] == "write_approval_missing" + assert review == before + + +def test_compute_pr_approval_state_result_is_pure(): + review = make_tracked_review_state( + make_state(), + 42, + reviewer="alice", + active_cycle_started_at="2026-03-17T09:00:00Z", + ) + before = json.loads(json.dumps(review)) + bot = _bot( + github_api_request=lambda method, endpoint, data=None, extra_headers=None, **kwargs: GitHubApiResult( + 200, + pull_request_payload(42, head_sha="head-1") + if endpoint == "pulls/42" + else [ + review_payload( + 10, + state="APPROVED", + submitted_at="2026-03-17T10:01:00Z", + commit_id="head-1", + author="alice", + ) + ], + {}, + "ok", + True, + None, + 0, + None, + ) + ) + + result = approval_policy.compute_pr_approval_state_result(bot, 42, review) + + assert result["ok"] is True + assert result["completion"]["completed"] is True + assert review == before + + +def test_apply_pr_approval_state_mutates_expected_fields(): + review = make_tracked_review_state(make_state(), 42) + + reviews.apply_pr_approval_state( + review, + completion={"completed": True, "current_head_sha": "head-1", "qualifying_review_ids": [10]}, + write_approval={"has_write_approval": True, "write_approvers": ["alice"], "current_head_sha": "head-1"}, + current_head_sha="head-1", + ) + + assert review["active_head_sha"] == "head-1" + assert review["current_cycle_completion"]["completed"] is True + assert review["current_cycle_write_approval"]["has_write_approval"] is True + assert review["review_completion_source"] == "live_review_rebuild" + + +def test_compute_pr_approval_state_from_reviews_is_pure(): + survivors = { + "alice": { + "id": 10, + "state": "APPROVED", + "submitted_at": reviews.parse_github_timestamp("2026-03-17T10:01:00Z"), + "commit_id": "head-1", + "user": {"login": "alice"}, + } + } + before = json.loads(json.dumps({"survivors": {"alice": {"id": 10, "state": "APPROVED", "commit_id": "head-1", "user": {"login": "alice"}}}})) + + result = approval_policy.compute_pr_approval_state_from_reviews( + survivors, + current_head="head-1", + permission_statuses={"alice": "granted"}, + ) + + assert result["ok"] is True + assert result["completion"]["completed"] is True + assert before["survivors"]["alice"]["id"] == 10 + + +def test_normalize_reviews_with_parsed_timestamps_is_pure(): + review_items = [ + { + "id": 10, + "state": "APPROVED", + "submitted_at": "2026-03-17T10:01:00Z", + "commit_id": "head-1", + "user": {"login": "alice"}, + } + ] + before = json.loads(json.dumps(review_items)) + + normalized = live_review_support.normalize_reviews_with_parsed_timestamps( + review_items, + parse_timestamp=reviews.parse_github_timestamp, + ) + + assert normalized[0]["submitted_at"] == reviews.parse_github_timestamp("2026-03-17T10:01:00Z") + assert review_items == before + + +def test_collect_permission_statuses_deduplicates_authors(): + survivors = { + "alice": {"user": {"login": "alice"}}, + "alice-2": {"user": {"login": "alice"}}, + "bob": {"user": {"login": "bob"}}, + } + observed = [] + + statuses = live_review_support.collect_permission_statuses( + survivors, + permission_status=lambda author: observed.append(author) or "granted", + ) + + assert statuses == {"alice": "granted", "bob": "granted"} + assert observed == ["alice", "bob"] + + +def test_approval_policy_classification_table_marks_support_helpers_as_moved_out_of_projection_module(): + table = Path("tests/fixtures/equivalence/approval_policy/function_classification_table.md").read_text( + encoding="utf-8" + ) + + for line in [ + "Moves to `live_review_support.py`", + "- `filter_current_head_reviews_for_cycle`", + "- `normalize_reviews_with_parsed_timestamps`", + "- `collect_permission_statuses`", + "Moves to `approval_policy.py`", + "- `compute_pr_approval_state_from_reviews`", + "- `desired_labels_from_response_state`", + ]: + assert line in table + + +def test_h1a_reviewer_response_matrix_fixture_exists_and_stays_reviewer_response_only(): + matrix = json.loads( + Path("tests/fixtures/equivalence/reviewer_response/scenario_matrix.json").read_text(encoding="utf-8") + ) + + assert matrix["harness_id"] == "H1a reviewer-response derivation equivalence" + assert matrix["owner"] == "scripts.reviewer_bot_core.reviewer_response_policy.compute_reviewer_response_state" + assert matrix["out_of_scope"] == [ + "mandatory approver escalation", + "label writes", + ] + assert [scenario["id"] for scenario in matrix["scenarios"]] == [ + "awaiting_reviewer_response_no_reviewer_activity", + "awaiting_reviewer_response_review_head_stale", + "awaiting_reviewer_response_contributor_revision_newer", + "awaiting_contributor_response_completion_missing", + "awaiting_write_approval_write_approval_missing", + "projection_failed_pull_request_unavailable", + "projection_failed_pull_request_head_unavailable", + "projection_failed_live_review_state_unknown", + ] diff --git a/tests/unit/reviewer_bot/test_state_store.py b/tests/unit/reviewer_bot/test_state_store.py new file mode 100644 index 000000000..66a40ef88 --- /dev/null +++ b/tests/unit/reviewer_bot/test_state_store.py @@ -0,0 +1,186 @@ +from scripts.reviewer_bot_lib import review_state, state_store +from scripts.reviewer_bot_lib.config import ( + FRESHNESS_RUNTIME_EPOCH_LEGACY, + STATE_SCHEMA_VERSION, + GitHubApiResult, + StateIssueSnapshot, +) +from tests.fixtures.fake_clock import FakeClock +from tests.fixtures.fake_jitter import DeterministicJitter +from tests.fixtures.fake_runtime import FakeReviewerBotRuntime +from tests.fixtures.fake_sleeper import RecordingSleeper +from tests.fixtures.recording_logger import RecordingLogger +from tests.fixtures.reviewer_bot import make_state + + +def _bot(monkeypatch, **overrides): + bot = FakeReviewerBotRuntime(monkeypatch) + for key, value in overrides.items(): + setattr(bot, key, value) + return bot + + +def test_load_state_sets_schema_and_epoch_defaults(monkeypatch): + bot = _bot(monkeypatch, get_state_issue=lambda: {"body": "queue: []\n"}) + + state = state_store.load_state(bot) + + assert state["schema_version"] == STATE_SCHEMA_VERSION + assert state["freshness_runtime_epoch"] == FRESHNESS_RUNTIME_EPOCH_LEGACY + + +def test_load_state_materializes_missing_top_level_persisted_keys(monkeypatch): + body = state_store.render_state_issue_body({"schema_version": 5}) + bot = _bot(monkeypatch, get_state_issue=lambda: {"body": body}) + + state = state_store.load_state(bot) + + assert state["schema_version"] == 5 + assert state["status_projection_epoch"] is None + assert state["last_updated"] is None + assert state["current_index"] == 0 + assert state["queue"] == [] + assert state["pass_until"] == [] + assert state["recent_assignments"] == [] + assert state["active_reviews"] == {} + + +def test_load_state_repairs_non_dict_active_reviews_fail_closed_to_empty_mapping(monkeypatch): + bot = _bot(monkeypatch, get_state_issue=lambda: {"body": "active_reviews: []\n"}) + + state = state_store.load_state(bot) + + assert state["active_reviews"] == {} + + +def test_get_state_issue_snapshot_uses_retry_aware_read(monkeypatch): + observed = {} + + def fake_request(method, endpoint, data=None, extra_headers=None, **kwargs): + observed["retry_policy"] = kwargs.get("retry_policy") + return GitHubApiResult( + status_code=200, + payload={"body": "state: ok", "html_url": "https://example.com/state/1"}, + headers={"etag": '"abc"'}, + text="ok", + ok=True, + failure_kind=None, + retry_attempts=1, + transport_error=None, + ) + + bot = _bot(monkeypatch, github_api_request=fake_request) + bot.state_issue_number = lambda: 1 + bot.get_config_value = lambda name, default="": default + + snapshot = state_store.get_state_issue_snapshot(bot) + + assert snapshot is not None + assert snapshot.etag == '"abc"' + assert observed["retry_policy"] == "idempotent_read" + + +def test_conditional_patch_state_issue_sends_if_match_header(monkeypatch): + observed = {} + + def fake_request(method, endpoint, data=None, extra_headers=None, **kwargs): + observed["extra_headers"] = extra_headers + return GitHubApiResult( + status_code=200, + payload={"body": data["body"]}, + headers={}, + text="ok", + ok=True, + failure_kind=None, + retry_attempts=0, + transport_error=None, + ) + + bot = _bot(monkeypatch, STATE_ISSUE_NUMBER=1, github_api_request=fake_request) + + state_store.conditional_patch_state_issue(bot, "updated", '"etag-1"') + + assert observed["extra_headers"] == {"If-Match": '"etag-1"'} + + +def test_conditional_patch_state_issue_omits_if_match_when_etag_missing(monkeypatch): + observed = {} + + def fake_request(method, endpoint, data=None, extra_headers=None, **kwargs): + observed["extra_headers"] = extra_headers + return GitHubApiResult(200, {"body": data["body"]}, {}, "ok", True, None, 0, None) + + bot = _bot(monkeypatch, STATE_ISSUE_NUMBER=1, github_api_request=fake_request) + + state_store.conditional_patch_state_issue(bot, "updated", None) + + assert observed["extra_headers"] is None + + +def test_save_state_retries_precondition_failed_conflict_uses_injected_time_services(monkeypatch): + state = make_state() + snapshot = StateIssueSnapshot( + body="body", + etag='"etag"', + html_url="https://example.com/state/1", + ) + responses = iter( + [ + GitHubApiResult(412, {"message": "precondition failed"}, {}, "precondition failed", False, None, 0, None), + GitHubApiResult(200, {"body": "updated"}, {}, "ok", True, None, 0, None), + ] + ) + + clock = FakeClock() + sleeper = RecordingSleeper() + jitter = DeterministicJitter(0.25) + logger = RecordingLogger() + + bot = _bot(monkeypatch, clock=clock, sleeper=sleeper, jitter=jitter, logger=logger) + bot.set_config_value("STATE_ISSUE_NUMBER", 1) + bot.ACTIVE_LEASE_CONTEXT = object() + bot.locks.stub(refresh=lambda: True) + bot.get_state_issue_snapshot = lambda: snapshot + bot.parse_lock_metadata_from_issue_body = lambda body: {} + bot.render_state_issue_body = lambda state_obj, base_body: "updated" + bot.conditional_patch_state_issue = lambda body, etag=None: next(responses) + + assert state_store.save_state(bot, state) is True + assert state["last_updated"] == clock.now().isoformat() + assert sleeper.calls == [2.25] + assert jitter.calls == [(0, 2.0)] + assert logger.records[0]["level"] == "warning" + assert logger.records[-1]["level"] == "info" + + +def test_get_state_issue_snapshot_builds_html_url_from_runtime_config_when_missing(monkeypatch): + def fake_request(method, endpoint, data=None, extra_headers=None, **kwargs): + return GitHubApiResult( + status_code=200, + payload={"body": "state: ok"}, + headers={"etag": '"abc"'}, + text="ok", + ok=True, + failure_kind=None, + retry_attempts=1, + transport_error=None, + ) + + config = {"REPO_OWNER": "rustfoundation", "REPO_NAME": "safety-critical-rust-coding-guidelines"} + bot = _bot(monkeypatch, github_api_request=fake_request) + bot.state_issue_number = lambda: 1 + bot.get_config_value = lambda name, default="": config.get(name, default) + + snapshot = state_store.get_state_issue_snapshot(bot) + + assert snapshot is not None + assert snapshot.html_url == "https://github.com/rustfoundation/safety-critical-rust-coding-guidelines/issues/1" + + +def test_loaded_state_fails_closed_for_invalid_legacy_review_entry_shape(monkeypatch): + bot = _bot(monkeypatch, get_state_issue=lambda: {"body": "active_reviews:\n '42': ['alice']\n"}) + + state = state_store.load_state(bot) + review = review_state.ensure_review_entry(state, 42, create=False) + + assert review is None diff --git a/tests/unit/reviewer_bot/test_sweeper_downloads.py b/tests/unit/reviewer_bot/test_sweeper_downloads.py new file mode 100644 index 000000000..ed9232bb5 --- /dev/null +++ b/tests/unit/reviewer_bot/test_sweeper_downloads.py @@ -0,0 +1,117 @@ +from pathlib import Path + +from scripts.reviewer_bot_lib import sweeper_observer_correlation +from tests.fixtures.fake_jitter import DeterministicJitter +from tests.fixtures.fake_runtime import FakeReviewerBotRuntime +from tests.fixtures.fake_sleeper import RecordingSleeper +from tests.fixtures.http_responses import FakeGitHubResponse +from tests.fixtures.reviewer_bot import make_zip_payload + + +def test_download_artifact_payload_retries_429_then_succeeds(monkeypatch): + payload = {"source_event_key": "issue_comment:100"} + responses = iter([FakeGitHubResponse(429, {"message": "slow down"}, "slow down")]) + success_response = FakeGitHubResponse(200, None, "") + success_response.content = make_zip_payload("deferred-comment.json", payload) + + runtime = FakeReviewerBotRuntime(monkeypatch) + runtime.set_config_value("GITHUB_TOKEN", "token") + runtime.get_github_token = lambda: "token" + runtime.jitter = DeterministicJitter(0.5) + runtime.sleeper = RecordingSleeper() + runtime.artifact_download_transport.stub_sequence([*responses, success_response]) + + status, artifact_payloads = sweeper_observer_correlation._download_artifact_payloads( + runtime, + {"archive_download_url": "https://example.com/artifact.zip", "expired": False}, + ) + + assert status == "ok" + assert artifact_payloads == [payload] + assert runtime.sleeper.calls == [2.5] + assert runtime.artifact_download_transport.calls[0]["url"] == "https://example.com/artifact.zip" + + +def test_download_retry_delay_uses_shared_backoff_with_jitter(monkeypatch): + runtime = FakeReviewerBotRuntime(monkeypatch) + runtime.jitter = DeterministicJitter([0.25, 0.5, 0.75]) + + assert sweeper_observer_correlation._download_retry_delay(runtime, 1) == 2.25 + assert sweeper_observer_correlation._download_retry_delay(runtime, 2) == 4.5 + assert sweeper_observer_correlation._download_retry_delay(runtime, 3) == 8.75 + + +def test_download_artifact_payload_reports_request_exception_unavailable(monkeypatch): + runtime = FakeReviewerBotRuntime(monkeypatch) + runtime.set_config_value("GITHUB_TOKEN", "token") + runtime.get_github_token = lambda: "token" + runtime.sleeper = RecordingSleeper() + runtime.artifact_download_transport.stub_sequence([RuntimeError("timeout")]) + + status, payload = sweeper_observer_correlation._download_artifact_payloads( + runtime, + {"archive_download_url": "https://example.com/artifact.zip", "expired": False}, + ) + + assert status == "download_unavailable" + assert payload is None + + +def test_download_artifact_payload_reports_retry_exhaustion_unavailable(monkeypatch): + runtime = FakeReviewerBotRuntime(monkeypatch) + runtime.set_config_value("GITHUB_TOKEN", "token") + runtime.get_github_token = lambda: "token" + runtime.sleeper = RecordingSleeper() + runtime.artifact_download_transport.stub_sequence([FakeGitHubResponse(429, {"message": "slow down"}, "slow down")] * 6) + + status, payload = sweeper_observer_correlation._download_artifact_payloads( + runtime, + {"archive_download_url": "https://example.com/artifact.zip", "expired": False}, + ) + + assert status == "download_unavailable" + assert payload is None + assert len(runtime.sleeper.calls) == 5 + + +def test_list_run_artifacts_returns_none_when_api_payload_unavailable(monkeypatch): + runtime = FakeReviewerBotRuntime(monkeypatch) + runtime.github_api_request = lambda method, endpoint, **kwargs: runtime.GitHubApiResult( + status_code=502, + payload={"message": "bad gateway"}, + headers={}, + text="bad gateway", + ok=False, + failure_kind="server_error", + retry_attempts=1, + transport_error=None, + ) + + assert sweeper_observer_correlation._list_run_artifacts(runtime, 42) is None + + +def test_list_run_artifacts_consumes_retry_aware_success(monkeypatch): + runtime = FakeReviewerBotRuntime(monkeypatch) + runtime.github_api_request = lambda method, endpoint, **kwargs: runtime.GitHubApiResult( + status_code=200, + payload={"artifacts": [{"id": 1, "name": "artifact"}]}, + headers={}, + text="ok", + ok=True, + failure_kind=None, + retry_attempts=1, + transport_error=None, + ) + + assert sweeper_observer_correlation._list_run_artifacts(runtime, 10) == [{"id": 1, "name": "artifact"}] + + +def test_sweeper_artifact_correlation_surface_stays_thin_after_diagnosis_cutover(): + sweeper_text = Path("scripts/reviewer_bot_lib/sweeper.py").read_text(encoding="utf-8") + observer_text = Path("scripts/reviewer_bot_lib/sweeper_observer_correlation.py").read_text(encoding="utf-8") + + assert "deferred_gap_diagnosis.correlate_run_artifacts_exact(" in observer_text + assert "deferred_gap_diagnosis.correlate_candidate_observer_runs(" not in observer_text + assert "inspect_run_artifact_payloads = observer_correlation.inspect_run_artifact_payloads" in sweeper_text + assert "def correlate_run_artifacts_exact(" not in sweeper_text + assert "def correlate_candidate_observer_runs(" not in sweeper_text diff --git a/tests/unit/reviewer_bot/test_sweeper_logic.py b/tests/unit/reviewer_bot/test_sweeper_logic.py new file mode 100644 index 000000000..f7574aa56 --- /dev/null +++ b/tests/unit/reviewer_bot/test_sweeper_logic.py @@ -0,0 +1,554 @@ +import json +from pathlib import Path + +import pytest + +from scripts.reviewer_bot_core import deferred_gap_diagnosis +from scripts.reviewer_bot_lib import review_state, sweeper +from tests.fixtures.fake_runtime import FakeReviewerBotRuntime +from tests.fixtures.reviewer_bot import make_state +from tests.fixtures.reviewer_bot_fakes import RouteGitHubApi, github_result +from tests.fixtures.reviewer_bot_sweeper_builders import ( + artifact_payload, + issue_comment_event, + pull_request_review_event, + review_comment_event, + workflow_run, +) + + +@pytest.fixture +def freeze_sweeper_now(monkeypatch): + def apply(timestamp: str) -> None: + monkeypatch.setattr(sweeper, "_now", lambda: sweeper.parse_timestamp(timestamp)) + + return apply + + +def _runtime(monkeypatch): + return FakeReviewerBotRuntime(monkeypatch) + + +def test_sweeper_creates_keyed_deferred_gaps_for_visible_comments_reviews_and_dismissals(monkeypatch, freeze_sweeper_now): + freeze_sweeper_now("2026-03-25T12:30:00Z") + runtime = _runtime(monkeypatch) + state = make_state() + review = review_state.ensure_review_entry(state, 42, create=True) + assert review is not None + review["current_reviewer"] = "alice" + routes = ( + RouteGitHubApi() + .add_api("GET", "pulls/42", {"state": "open", "head": {"sha": "head-1"}}) + .add_request("GET", "pulls/42", status_code=200, payload={"state": "open", "head": {"sha": "head-1"}}) + .add_request( + "GET", + "issues/42/comments?per_page=100&page=1", + status_code=200, + payload=[issue_comment_event(101, created_at="2026-03-25T10:00:00Z")], + ) + .add_request( + "GET", + "actions/workflows/.github%2Fworkflows%2Freviewer-bot-pr-review-submitted-observer.yml/runs?event=pull_request_review&per_page=100&page=1", + status_code=200, + payload={"workflow_runs": []}, + ) + .add_request( + "GET", + "actions/workflows/.github%2Fworkflows%2Freviewer-bot-pr-comment-observer.yml/runs?event=issue_comment&per_page=100&page=1", + status_code=200, + payload={"workflow_runs": []}, + ) + .add_request( + "GET", + "actions/workflows/.github%2Fworkflows%2Freviewer-bot-pr-review-dismissed-observer.yml/runs?event=pull_request_review&per_page=100&page=1", + status_code=200, + payload={"workflow_runs": []}, + ) + .add_request("GET", "pulls/42/comments?per_page=100&page=1", status_code=200, payload=[]) + ) + runtime.github.stub(routes) + runtime.get_pull_request_reviews = lambda issue_number: [ + pull_request_review_event(202, submitted_at="2026-03-25T11:00:00Z", state="APPROVED"), + pull_request_review_event(303, submitted_at="2026-03-25T09:00:00Z", updated_at="2026-03-25T12:00:00Z", state="DISMISSED"), + ] + + assert sweeper.sweep_deferred_gaps(runtime, state) is True + gaps = state["active_reviews"]["42"]["sidecars"]["deferred_gaps"] + assert "issue_comment:101" in gaps + assert "pull_request_review:202" in gaps + assert "pull_request_review_dismissed:303" in gaps + assert gaps["pull_request_review_dismissed:303"]["source_event_kind"] == "pull_request_review:dismissed" + + +def test_sweeper_creates_keyed_deferred_gap_for_visible_review_comments(monkeypatch, freeze_sweeper_now): + freeze_sweeper_now("2026-03-25T12:30:00Z") + runtime = _runtime(monkeypatch) + state = make_state() + review = review_state.ensure_review_entry(state, 42, create=True) + assert review is not None + review["current_reviewer"] = "alice" + routes = ( + RouteGitHubApi() + .add_api("GET", "pulls/42", {"state": "open", "head": {"sha": "head-1"}}) + .add_request("GET", "pulls/42", status_code=200, payload={"state": "open", "head": {"sha": "head-1"}}) + .add_request("GET", "issues/42/comments?per_page=100&page=1", status_code=200, payload=[]) + .add_request( + "GET", + "issues/42/comments?per_page=100&page=2", + status_code=200, + payload=[], + ) + .add_request( + "GET", + "pulls/42/comments?per_page=100&page=1", + status_code=200, + payload=[review_comment_event(404, created_at="2026-03-25T10:30:00Z", login="dana")], + ) + .add_request( + "GET", + "actions/workflows/.github%2Fworkflows%2Freviewer-bot-pr-review-comment-observer.yml/runs?event=pull_request_review_comment&per_page=100&page=1", + status_code=200, + payload={"workflow_runs": []}, + ) + ) + runtime.github.stub(routes) + runtime.get_pull_request_reviews = lambda issue_number: [] + + assert sweeper.sweep_deferred_gaps(runtime, state) is True + gaps = state["active_reviews"]["42"]["sidecars"]["deferred_gaps"] + assert "pull_request_review_comment:404" in gaps + assert gaps["pull_request_review_comment:404"]["source_event_kind"] == "pull_request_review_comment:created" + + +def test_discover_visible_review_comment_events_paginates_past_page_one(monkeypatch, freeze_sweeper_now): + freeze_sweeper_now("2026-03-25T12:30:00Z") + runtime = _runtime(monkeypatch) + review = review_state.ensure_review_entry(make_state(), 42, create=True) + assert review is not None + routes = ( + RouteGitHubApi() + .add_request( + "GET", + "pulls/42/comments?per_page=100&page=1", + status_code=200, + payload=[review_comment_event(index, created_at="2026-03-25T10:30:00Z", login="dana") for index in range(1, 101)], + ) + .add_request( + "GET", + "pulls/42/comments?per_page=100&page=2", + status_code=200, + payload=[review_comment_event(404, created_at="2026-03-25T10:31:00Z", login="dana")], + ) + ) + runtime.github.stub(routes) + + discovered, complete = sweeper._discover_visible_review_comment_events(runtime, 42, review) + + assert complete is True + assert discovered is not None + assert len(discovered) == 101 + assert discovered[-1]["source_event_key"] == "pull_request_review_comment:404" + + +def test_review_comment_surface_marks_complete_only_after_full_pagination_succeeds(monkeypatch, freeze_sweeper_now): + freeze_sweeper_now("2026-03-25T12:30:00Z") + runtime = _runtime(monkeypatch) + review = review_state.ensure_review_entry(make_state(), 42, create=True) + assert review is not None + routes = ( + RouteGitHubApi() + .add_request( + "GET", + "pulls/42/comments?per_page=100&page=1", + status_code=200, + payload=[review_comment_event(index, created_at="2026-03-25T10:30:00Z", login="dana") for index in range(1, 101)], + ) + .add_request( + "GET", + "pulls/42/comments?per_page=100&page=2", + result=github_result(502, {"message": "bad gateway"}, retry_attempts=1), + ) + ) + runtime.github.stub(routes) + + discovered, complete = sweeper._discover_visible_review_comment_events(runtime, 42, review) + + assert discovered is None + assert complete is False + watermark = review["sidecars"]["observer_discovery_watermarks"]["review_comments"] + assert watermark["last_scan_started_at"] is not None + assert watermark["last_scan_completed_at"] is None + + +def test_sweeper_skips_dismissed_reviews_already_reconciled_by_source_event_key(monkeypatch, freeze_sweeper_now): + freeze_sweeper_now("2026-03-17T12:30:00Z") + runtime = _runtime(monkeypatch) + state = make_state() + review = review_state.ensure_review_entry(state, 42, create=True) + assert review is not None + review["current_reviewer"] = "alice" + review["sidecars"]["reconciled_source_events"] = { + "pull_request_review_dismissed:303": { + "source_event_key": "pull_request_review_dismissed:303", + "reconciled_at": None, + } + } + routes = ( + RouteGitHubApi() + .add_api("GET", "pulls/42", {"state": "open", "head": {"sha": "head-1"}}) + .add_request("GET", "pulls/42", status_code=200, payload={"state": "open", "head": {"sha": "head-1"}}) + .add_request("GET", "issues/42/comments?per_page=100&page=1", status_code=200, payload=[]) + .add_request("GET", "issues/42/comments?per_page=100&page=2", status_code=200, payload=[]) + .add_request("GET", "pulls/42/comments?per_page=100&page=1", status_code=200, payload=[]) + ) + runtime.github.stub(routes) + runtime.get_pull_request_reviews = lambda issue_number: [pull_request_review_event(303, submitted_at="2026-03-17T09:00:00Z", updated_at="2026-03-17T12:00:00Z", state="DISMISSED")] + + assert sweeper.sweep_deferred_gaps(runtime, state) is False + assert state["active_reviews"]["42"]["sidecars"]["deferred_gaps"] == {} + + +def test_sweeper_skips_events_already_reconciled_by_source_event_key(monkeypatch, freeze_sweeper_now): + freeze_sweeper_now("2026-03-17T12:30:00Z") + runtime = _runtime(monkeypatch) + state = make_state() + review = review_state.ensure_review_entry(state, 42, create=True) + assert review is not None + review["current_reviewer"] = "alice" + review["sidecars"]["reconciled_source_events"] = { + "issue_comment:101": {"source_event_key": "issue_comment:101", "reconciled_at": None}, + "pull_request_review:202": {"source_event_key": "pull_request_review:202", "reconciled_at": None}, + } + routes = ( + RouteGitHubApi() + .add_api("GET", "pulls/42", {"state": "open", "head": {"sha": "head-1"}}) + .add_request("GET", "pulls/42", status_code=200, payload={"state": "open", "head": {"sha": "head-1"}}) + .add_request( + "GET", + "issues/42/comments?per_page=100&page=1", + status_code=200, + payload=[issue_comment_event(101, created_at="2026-03-17T10:00:00Z")], + ) + .add_request("GET", "issues/42/comments?per_page=100&page=2", status_code=200, payload=[]) + .add_request( + "GET", + "actions/workflows/.github%2Fworkflows%2Freviewer-bot-pr-review-submitted-observer.yml/runs?event=pull_request_review&per_page=100&page=1", + status_code=200, + payload={"workflow_runs": []}, + ) + .add_request("GET", "pulls/42/comments?per_page=100&page=1", status_code=200, payload=[]) + ) + runtime.github.stub(routes) + runtime.get_pull_request_reviews = lambda issue_number: [pull_request_review_event(202, submitted_at="2026-03-17T11:00:00Z", state="APPROVED")] + + assert sweeper.sweep_deferred_gaps(runtime, state) is False + assert state["active_reviews"]["42"]["sidecars"]["deferred_gaps"] == {} + + +def test_discover_visible_comment_events_skips_github_actions_and_bot_comments(monkeypatch, freeze_sweeper_now): + freeze_sweeper_now("2026-03-25T12:30:00Z") + runtime = _runtime(monkeypatch) + state = make_state() + review = review_state.ensure_review_entry(state, 42, create=True) + assert review is not None + routes = RouteGitHubApi().add_request( + "GET", + "issues/42/comments?per_page=100&page=1", + status_code=200, + payload=[ + issue_comment_event(100, created_at="2026-03-25T10:00:00Z", login="github-actions[bot]", user_type="Bot"), + issue_comment_event(101, created_at="2026-03-25T11:00:00Z", login="alice"), + ], + ) + routes.add_request("GET", "issues/42/comments?per_page=100&page=2", status_code=200, payload=[]) + runtime.github.stub(routes) + + discovered, complete = sweeper._discover_visible_comment_events(runtime, 42, review) + + assert complete is True + assert [item["source_event_key"] for item in discovered] == ["issue_comment:101"] + + +def test_sweeper_visible_review_repair_refreshes_current_reviewer_activity_without_artifact(monkeypatch, freeze_sweeper_now): + freeze_sweeper_now("2026-03-25T12:30:00Z") + runtime = _runtime(monkeypatch) + state = make_state() + review = review_state.ensure_review_entry(state, 42, create=True) + assert review is not None + review["current_reviewer"] = "alice" + review["active_cycle_started_at"] = "2026-03-17T09:00:00Z" + review["transition_warning_sent"] = "2026-03-18T00:00:00Z" + review["transition_notice_sent_at"] = "2026-03-25T00:00:00Z" + review["sidecars"]["deferred_gaps"]["pull_request_review:202"] = {"reason": "artifact_missing"} + routes = ( + RouteGitHubApi() + .add_api("GET", "pulls/42", {"state": "open", "head": {"sha": "head-1"}}) + .add_request("GET", "pulls/42", status_code=200, payload={"state": "open", "head": {"sha": "head-1"}}) + .add_request( + "GET", + "actions/workflows/.github%2Fworkflows%2Freviewer-bot-pr-review-submitted-observer.yml/runs?event=pull_request_review&per_page=100&page=1", + status_code=200, + payload={"workflow_runs": []}, + ) + .add_request("GET", "issues/42/comments?per_page=100&page=1", status_code=200, payload=[]) + .add_request("GET", "pulls/42/reviews?per_page=100&page=1", status_code=200, payload=[]) + .add_request("GET", "pulls/42/comments?per_page=100&page=1", status_code=200, payload=[]) + ) + runtime.github.stub(routes) + runtime.get_pull_request_reviews = lambda issue_number: [pull_request_review_event(202, submitted_at="2026-03-25T11:00:00Z", state="COMMENTED", commit_id="head-1")] + + assert sweeper.sweep_deferred_gaps(runtime, state) is True + assert review["last_reviewer_activity"] == "2026-03-25T11:00:00Z" + assert review["transition_warning_sent"] is None + assert review["transition_notice_sent_at"] is None + assert "pull_request_review:202" not in review["sidecars"]["deferred_gaps"] + assert "pull_request_review:202" in review["sidecars"]["reconciled_source_events"] + + +def test_visible_review_repair_does_not_clear_transition_warning_for_stale_replayed_review(monkeypatch, freeze_sweeper_now): + freeze_sweeper_now("2026-03-25T12:30:00Z") + runtime = _runtime(monkeypatch) + state = make_state() + review = review_state.ensure_review_entry(state, 42, create=True) + assert review is not None + review["current_reviewer"] = "alice" + review["active_cycle_started_at"] = "2026-03-17T09:00:00Z" + review["last_reviewer_activity"] = "2026-03-25T11:00:00Z" + review["transition_warning_sent"] = "2026-04-01T12:12:04Z" + review["transition_notice_sent_at"] = "2026-04-15T12:12:04Z" + review["sidecars"]["deferred_gaps"]["pull_request_review:202"] = {"reason": "artifact_missing"} + routes = ( + RouteGitHubApi() + .add_api("GET", "pulls/42", {"state": "open", "head": {"sha": "head-1"}}) + .add_request("GET", "pulls/42", status_code=200, payload={"state": "open", "head": {"sha": "head-1"}}) + .add_request( + "GET", + "actions/workflows/.github%2Fworkflows%2Freviewer-bot-pr-review-submitted-observer.yml/runs?event=pull_request_review&per_page=100&page=1", + status_code=200, + payload={"workflow_runs": []}, + ) + .add_request("GET", "issues/42/comments?per_page=100&page=1", status_code=200, payload=[]) + .add_request("GET", "pulls/42/reviews?per_page=100&page=1", status_code=200, payload=[]) + .add_request("GET", "pulls/42/comments?per_page=100&page=1", status_code=200, payload=[]) + ) + runtime.github.stub(routes) + runtime.get_pull_request_reviews = lambda issue_number: [pull_request_review_event(202, submitted_at="2026-03-25T11:00:00Z", state="COMMENTED", commit_id="head-1")] + + assert sweeper.sweep_deferred_gaps(runtime, state) is True + assert review["last_reviewer_activity"] == "2026-03-25T11:00:00Z" + assert review["transition_warning_sent"] == "2026-04-01T12:12:04Z" + assert review["transition_notice_sent_at"] == "2026-04-15T12:12:04Z" + + +def test_repair_visible_review_gap_returns_true_for_bookkeeping_only_mutations(monkeypatch): + runtime = _runtime(monkeypatch) + review = review_state.ensure_review_entry(make_state(), 42, create=True) + assert review is not None + review["current_reviewer"] = "alice" + review["active_cycle_started_at"] = "2026-03-17T09:00:00Z" + review["sidecars"]["deferred_gaps"]["pull_request_review:303"] = {"reason": "artifact_missing"} + monkeypatch.setattr(sweeper, "accept_reviewer_review_from_live_review", lambda review_data, live_review, actor=None: False) + monkeypatch.setattr(sweeper, "refresh_reviewer_review_from_live_preferred_review", lambda bot, issue_number, review_data, actor=None: (False, None)) + monkeypatch.setattr(sweeper, "rebuild_pr_approval_state", lambda bot, issue_number, review_data: (None, None)) + + changed = sweeper._repair_visible_review_gap( + runtime, + review, + 42, + "pull_request_review:303", + pull_request_review_event(303, submitted_at="2026-03-25T11:00:00Z", state="COMMENTED", commit_id="head-1"), + ) + + assert changed is True + assert "pull_request_review:303" in review["sidecars"]["reconciled_source_events"] + assert "pull_request_review:303" not in review["sidecars"]["deferred_gaps"] + + +def test_load_surface_watermark_lazily_materializes_missing_surface_state(monkeypatch): + review = review_state.ensure_review_entry(make_state(), 42, create=True) + assert review is not None + + watermark = sweeper._load_surface_watermark(review, "reviewer_comment") + + assert watermark == { + "last_scan_started_at": None, + "last_scan_completed_at": None, + "last_safe_event_time": None, + "last_safe_event_id": None, + "lookback_seconds": None, + "bootstrap_window_seconds": None, + "bootstrap_completed_at": None, + } + assert review["sidecars"]["observer_discovery_watermarks"]["reviewer_comment"] == watermark + + +def test_observer_run_reason_mapping_and_near_miss_signature(): + signature = {"status": "waiting", "conclusion": None, "name": "approval_pending"} + assert deferred_gap_diagnosis.observer_run_reason_from_details({"status": "waiting", "conclusion": None, "name": "approval_pending"}, signature) == "awaiting_observer_approval" + assert deferred_gap_diagnosis.observer_run_reason_from_details({"status": "waiting", "conclusion": None, "name": "almost"}, signature) == "observer_state_unknown" + + +def test_approval_pending_signature_is_loaded_from_runbook(): + assert sweeper._approval_pending_signature_from_runbook() == { + "status": "waiting", + "conclusion": None, + "name": "approval_pending", + } + + +def test_negative_missing_run_requires_full_scan_and_recheck(): + gap = { + "source_event_created_at": "2026-03-15T00:00:00Z", + "full_scan_complete": True, + "later_recheck_complete": True, + "correlated_run_found": False, + "approval_pending_evidence_retained": False, + } + assert deferred_gap_diagnosis.can_mark_observer_run_missing(gap) is True + gap["later_recheck_complete"] = False + assert deferred_gap_diagnosis.can_mark_observer_run_missing(gap) is False + + +def test_sweeper_delegates_diagnosis_and_narrow_recommendation_to_core_owner(): + module_text = Path("scripts/reviewer_bot_lib/sweeper.py").read_text(encoding="utf-8") + + assert "from scripts.reviewer_bot_core import deferred_gap_diagnosis" in module_text + assert "def observer_run_reason_from_details(" not in module_text + assert "def evaluate_deferred_gap_state(" not in module_text + assert "def _can_repair_visible_review(" not in module_text + assert "deferred_gap_diagnosis.evaluate_deferred_gap_state(" in module_text + assert "deferred_gap_diagnosis.recommend_review_submission_gap_repair(" in module_text + + +def test_h4a_review_submission_gap_fixture_stays_narrow_and_explicit(): + matrix = json.loads( + Path("tests/fixtures/equivalence/review_submission_gap_repair/scenario_matrix.json").read_text(encoding="utf-8") + ) + + assert matrix["harness_id"] == "H4a review-submitted gap repair flow equivalence" + assert matrix["out_of_scope"] == [ + "production cutover", + "other sweeper repair flows", + ] + assert [scenario["scenario_id"] for scenario in matrix["scenarios"]] == [ + "submitted_review_visible_without_exact_artifact", + ] + + +def test_stage_a_candidate_run_correlation_is_exact_to_workflow_event_pr_and_window(monkeypatch): + runtime = _runtime(monkeypatch) + runtime.set_config_value("GITHUB_REPOSITORY", "rustfoundation/safety-critical-rust-coding-guidelines") + result = deferred_gap_diagnosis.correlate_candidate_observer_runs( + "issue_comment:101", + source_event_kind="issue_comment:created", + source_event_created_at="2026-03-17T10:00:00Z", + pr_number=42, + workflow_file=".github/workflows/reviewer-bot-pr-comment-observer.yml", + workflow_runs=[ + workflow_run(1, event="issue_comment", path=".github/workflows/reviewer-bot-pr-comment-observer.yml", created_at="2026-03-17T10:05:00Z"), + workflow_run(2, event="issue_comment", path=".github/workflows/reviewer-bot-pr-comment-observer.yml", created_at="2026-03-17T10:40:00Z"), + ], + github_repository="rustfoundation/safety-critical-rust-coding-guidelines", + ) + assert result["candidate_run_ids"] == [1] + + +def test_stage_b_artifact_correlation_rejects_ambiguous_exact_matches(): + result = deferred_gap_diagnosis.correlate_run_artifacts_exact( + { + 10: [artifact_payload(source_event_key="issue_comment:101", source_run_id=10)], + 11: [artifact_payload(source_event_key="issue_comment:101", source_run_id=11)], + }, + "issue_comment:101", + pr_number=42, + ) + assert result["status"] == "observer_state_unknown" + assert result["reason"] == "ambiguous_exact_artifact_matches" + + +def test_evaluate_gap_state_treats_artifact_download_unavailable_as_unknown(): + reason, diagnostic = deferred_gap_diagnosis.evaluate_deferred_gap_state( + {"source_event_created_at": "2026-03-17T00:00:00Z"}, + {"status": "candidate_runs_found", "correlated_run": 10}, + {"status": "completed", "conclusion": "success"}, + {"status": "no_exact_artifact_match", "artifact_scan_outcomes": {10: "download_unavailable"}}, + runbook_signature=None, + ) + assert reason == "observer_state_unknown" + assert diagnostic == "artifact_download_unavailable" + + +def test_evaluate_gap_state_only_emits_missing_after_negative_inference_contract(): + reason, diagnostic = deferred_gap_diagnosis.evaluate_deferred_gap_state( + { + "source_event_created_at": "2026-03-15T00:00:00Z", + "full_scan_complete": True, + "later_recheck_complete": True, + "correlated_run_found": False, + "approval_pending_evidence_retained": False, + }, + { + "status": "no_candidate_runs", + "full_scan_complete": True, + "later_recheck_complete": True, + "correlated_run": None, + }, + None, + None, + runbook_signature=None, + ) + assert reason == "observer_run_missing" + assert diagnostic == "negative_inference_satisfied" + + +def test_evaluate_gap_state_completed_success_without_exact_artifact_is_artifact_missing(): + reason, diagnostic = deferred_gap_diagnosis.evaluate_deferred_gap_state( + {"source_event_created_at": "2026-03-17T00:00:00Z"}, + {"status": "candidate_runs_found", "correlated_run": 10}, + {"status": "completed", "conclusion": "success"}, + {"status": "no_exact_artifact_match", "reason": "no_exact_source_event_key_match"}, + runbook_signature=None, + ) + assert reason == "artifact_missing" + assert diagnostic == "no_exact_source_event_key_match" + + +def test_evaluate_gap_state_completed_success_with_expired_artifact_marks_artifact_expired(): + reason, diagnostic = deferred_gap_diagnosis.evaluate_deferred_gap_state( + {"source_event_created_at": "2026-03-17T00:00:00Z"}, + {"status": "candidate_runs_found", "correlated_run": 10}, + {"status": "completed", "conclusion": "success"}, + {"status": "no_exact_artifact_match", "artifact_scan_outcomes": {10: "expired"}}, + runbook_signature=None, + ) + assert reason == "artifact_expired" + assert diagnostic == "prior_visibility_or_retention_proof_required" + + +def test_artifact_gap_reason_requires_prior_visibility_or_documented_retention(): + expired = {"artifact_seen_at": "2026-03-10T00:00:00Z", "run_created_at": "2026-03-10T00:00:00Z"} + assert deferred_gap_diagnosis.classify_artifact_gap_reason(expired) == "artifact_expired" + missing = {"artifact_inspection_complete": True, "run_created_at": "2026-03-17T00:00:00Z"} + assert deferred_gap_diagnosis.classify_artifact_gap_reason(missing) == "artifact_missing" + + +def test_artifact_gap_reason_uses_passed_retention_days(): + gap = { + "run_created_at": "2026-03-01T00:00:00Z", + "retention_window_documented": True, + } + + assert deferred_gap_diagnosis.classify_artifact_gap_reason( + gap, + now=deferred_gap_diagnosis.parse_timestamp("2026-03-05T00:00:00Z"), + retention_days=3, + ) == "artifact_expired" + + +def test_sweeper_fetches_single_candidate_run_detail_without_exact_artifact_match(monkeypatch): + runtime = _runtime(monkeypatch) + run_correlation = {"candidate_run_ids": [123], "correlated_run": None, "correlated_run_found": False} + monkeypatch.setattr(sweeper, "_fetch_run_detail", lambda bot, run_id: {"id": run_id, "status": "completed", "conclusion": "action_required"}) + detail = sweeper._maybe_fetch_single_candidate_run_detail(runtime, run_correlation, {"status": "no_exact_artifact_match"}) + assert detail == {"id": 123, "status": "completed", "conclusion": "action_required"} + assert run_correlation["correlated_run"] == 123