- RoutingDecision の missing_caps を Vec<CapabilityTag> に変更(型安全化) - error_tags は to_tag() メソッドで自動生成 - 全 callsite を enum variant に修正 - capability_tags モジュール(文字列定数群)を完全削除 - 全テスト PASS(型安全性向上を確認) - フォーマット適用
439 lines
15 KiB
Rust
439 lines
15 KiB
Rust
/*!
|
||
* CompilationContext - Compilation state management for MirBuilder
|
||
*
|
||
* Phase 136 follow-up (Step 7/7): Extract compilation-related fields from MirBuilder
|
||
* to consolidate box compilation state, type information, and analysis metadata.
|
||
*
|
||
* Consolidates:
|
||
* - compilation_context: Box compilation context (BoxCompilationContext)
|
||
* - current_static_box: Current static box being compiled
|
||
* - user_defined_boxes: User-defined box names registry
|
||
* - reserved_value_ids: Reserved ValueIds for PHI instructions
|
||
* - fn_body_ast: Function body AST for capture analysis
|
||
* - weak_fields_by_box: Weak field registry
|
||
* - property_getters_by_box: Property getter registry
|
||
* - field_origin_class: Field origin tracking
|
||
* - field_origin_by_box: Class-level field origin
|
||
* - static_method_index: Static method index
|
||
* - method_tail_index: Method tail index
|
||
* - method_tail_index_source_len: Source length snapshot
|
||
* - type_registry: Type registry box
|
||
* - current_slot_registry: Function scope slot registry
|
||
* - plugin_method_sigs: Plugin method signatures
|
||
*/
|
||
|
||
use crate::ast::ASTNode;
|
||
use crate::mir::region::function_slot_registry::FunctionSlotRegistry;
|
||
use crate::mir::{MirType, ValueId};
|
||
use std::collections::{BTreeMap, HashMap, HashSet};
|
||
|
||
use super::context::BoxCompilationContext;
|
||
use super::type_registry::TypeRegistry;
|
||
use super::PropertyKind;
|
||
|
||
/// Compilation state context for MIR builder
|
||
///
|
||
/// Consolidates all compilation-related state including box compilation context,
|
||
/// type information, analysis metadata, and method resolution indices.
|
||
#[derive(Debug)]
|
||
pub(crate) struct CompilationContext {
|
||
/// Box compilation context (for static box compilation isolation)
|
||
/// Some(ctx) during static box compilation, None for traditional mode
|
||
pub compilation_context: Option<BoxCompilationContext>,
|
||
|
||
/// Current static box name when lowering a static box body (e.g., "Main")
|
||
pub current_static_box: Option<String>,
|
||
|
||
/// Names of user-defined boxes declared in the current module
|
||
pub user_defined_boxes: HashSet<String>,
|
||
|
||
/// Phase 201-A: Reserved ValueIds that must not be allocated
|
||
/// These are PHI dst ValueIds created by LoopHeaderPhiBuilder.
|
||
/// When next_value_id() encounters a reserved ID, it skips to the next.
|
||
/// Cleared after JoinIR merge completes.
|
||
pub reserved_value_ids: HashSet<ValueId>,
|
||
|
||
/// Phase 200-C: Original function body AST for capture analysis
|
||
/// Stored temporarily during function lowering to support FunctionScopeCaptureAnalyzer.
|
||
/// None when not lowering a function, or when fn_body is not available.
|
||
pub fn_body_ast: Option<Vec<ASTNode>>,
|
||
|
||
/// Weak field registry: BoxName -> {weak field names}
|
||
pub weak_fields_by_box: HashMap<String, HashSet<String>>,
|
||
|
||
/// Unified members: BoxName -> {propName -> Kind}
|
||
pub property_getters_by_box: HashMap<String, HashMap<String, PropertyKind>>,
|
||
|
||
/// Remember class of object fields after assignments: (base_id, field) -> class_name
|
||
pub field_origin_class: HashMap<(ValueId, String), String>,
|
||
|
||
/// Class-level field origin (cross-function heuristic): (BaseBoxName, field) -> FieldBoxName
|
||
pub field_origin_by_box: HashMap<(String, String), String>,
|
||
|
||
/// Index of static methods seen during lowering: name -> [(BoxName, arity)]
|
||
pub static_method_index: HashMap<String, Vec<(String, usize)>>,
|
||
|
||
/// Fast lookup: method+arity tail → candidate function names (e.g., ".str/0" → ["JsonNode.str/0", ...])
|
||
pub method_tail_index: HashMap<String, Vec<String>>,
|
||
|
||
/// Source size snapshot to detect when to rebuild the tail index
|
||
pub method_tail_index_source_len: usize,
|
||
|
||
/// 🎯 箱理論: 型情報管理の一元化(TypeRegistryBox)
|
||
/// NYASH_USE_TYPE_REGISTRY=1 で有効化(段階的移行用)
|
||
pub type_registry: TypeRegistry,
|
||
|
||
/// 関数スコープの SlotRegistry(観測専用)
|
||
/// - current_function と同じライフサイクルを持つよ。
|
||
/// - 既存の variable_map/SSA には影響しない(メタデータのみ)。
|
||
pub current_slot_registry: Option<FunctionSlotRegistry>,
|
||
|
||
/// Plugin method return type signatures loaded from nyash_box.toml
|
||
pub plugin_method_sigs: HashMap<(String, String), MirType>,
|
||
}
|
||
|
||
impl CompilationContext {
|
||
/// Create a new CompilationContext with default-initialized state
|
||
pub fn new() -> Self {
|
||
Self {
|
||
compilation_context: None,
|
||
current_static_box: None,
|
||
user_defined_boxes: HashSet::new(),
|
||
reserved_value_ids: HashSet::new(),
|
||
fn_body_ast: None,
|
||
weak_fields_by_box: HashMap::new(),
|
||
property_getters_by_box: HashMap::new(),
|
||
field_origin_class: HashMap::new(),
|
||
field_origin_by_box: HashMap::new(),
|
||
static_method_index: HashMap::new(),
|
||
method_tail_index: HashMap::new(),
|
||
method_tail_index_source_len: 0,
|
||
type_registry: TypeRegistry::new(),
|
||
current_slot_registry: None,
|
||
plugin_method_sigs: HashMap::new(),
|
||
}
|
||
}
|
||
|
||
/// Create a new CompilationContext with plugin method signatures
|
||
pub fn with_plugin_sigs(plugin_method_sigs: HashMap<(String, String), MirType>) -> Self {
|
||
Self {
|
||
plugin_method_sigs,
|
||
..Self::new()
|
||
}
|
||
}
|
||
|
||
/// Check if a box is user-defined
|
||
pub fn is_user_defined_box(&self, name: &str) -> bool {
|
||
self.user_defined_boxes.contains(name)
|
||
}
|
||
|
||
/// Register a user-defined box
|
||
pub fn register_user_box(&mut self, name: String) {
|
||
self.user_defined_boxes.insert(name);
|
||
}
|
||
|
||
/// Check if a ValueId is reserved
|
||
pub fn is_reserved_value_id(&self, id: ValueId) -> bool {
|
||
self.reserved_value_ids.contains(&id)
|
||
}
|
||
|
||
/// Reserve a ValueId (for PHI instructions)
|
||
pub fn reserve_value_id(&mut self, id: ValueId) {
|
||
self.reserved_value_ids.insert(id);
|
||
}
|
||
|
||
/// Clear all reserved ValueIds (after JoinIR merge)
|
||
pub fn clear_reserved_value_ids(&mut self) {
|
||
self.reserved_value_ids.clear();
|
||
}
|
||
|
||
/// Enter static box compilation mode
|
||
pub fn enter_static_box(&mut self, name: String) {
|
||
self.current_static_box = Some(name);
|
||
}
|
||
|
||
/// Exit static box compilation mode
|
||
pub fn exit_static_box(&mut self) {
|
||
self.current_static_box = None;
|
||
}
|
||
|
||
/// Get current static box name
|
||
pub fn current_static_box(&self) -> Option<&str> {
|
||
self.current_static_box.as_deref()
|
||
}
|
||
|
||
/// Check if currently compiling a static box
|
||
pub fn is_in_static_box(&self) -> bool {
|
||
self.current_static_box.is_some()
|
||
}
|
||
|
||
/// Store function body AST for capture analysis
|
||
pub fn set_fn_body_ast(&mut self, ast: Vec<ASTNode>) {
|
||
self.fn_body_ast = Some(ast);
|
||
}
|
||
|
||
/// Take function body AST (consumes it)
|
||
pub fn take_fn_body_ast(&mut self) -> Option<Vec<ASTNode>> {
|
||
self.fn_body_ast.take()
|
||
}
|
||
|
||
/// Clear function body AST
|
||
pub fn clear_fn_body_ast(&mut self) {
|
||
self.fn_body_ast = None;
|
||
}
|
||
|
||
/// Check if a field is weak for a box
|
||
pub fn is_weak_field(&self, box_name: &str, field_name: &str) -> bool {
|
||
self.weak_fields_by_box
|
||
.get(box_name)
|
||
.map_or(false, |fields| fields.contains(field_name))
|
||
}
|
||
|
||
/// Register a weak field for a box
|
||
pub fn register_weak_field(&mut self, box_name: String, field_name: String) {
|
||
self.weak_fields_by_box
|
||
.entry(box_name)
|
||
.or_insert_with(HashSet::new)
|
||
.insert(field_name);
|
||
}
|
||
|
||
/// Get property kind for a box member
|
||
pub fn get_property_kind(&self, box_name: &str, prop_name: &str) -> Option<&PropertyKind> {
|
||
self.property_getters_by_box
|
||
.get(box_name)
|
||
.and_then(|props| props.get(prop_name))
|
||
}
|
||
|
||
/// Register a property getter for a box
|
||
pub fn register_property_getter(
|
||
&mut self,
|
||
box_name: String,
|
||
prop_name: String,
|
||
kind: PropertyKind,
|
||
) {
|
||
self.property_getters_by_box
|
||
.entry(box_name)
|
||
.or_insert_with(HashMap::new)
|
||
.insert(prop_name, kind);
|
||
}
|
||
|
||
/// Get field origin class for a value's field
|
||
pub fn get_field_origin_class(&self, base_id: ValueId, field: &str) -> Option<&str> {
|
||
self.field_origin_class
|
||
.get(&(base_id, field.to_string()))
|
||
.map(|s| s.as_str())
|
||
}
|
||
|
||
/// Set field origin class for a value's field
|
||
pub fn set_field_origin_class(&mut self, base_id: ValueId, field: String, class: String) {
|
||
self.field_origin_class.insert((base_id, field), class);
|
||
}
|
||
|
||
/// Get field origin by box (class-level)
|
||
pub fn get_field_origin_by_box(&self, base_box: &str, field: &str) -> Option<&str> {
|
||
self.field_origin_by_box
|
||
.get(&(base_box.to_string(), field.to_string()))
|
||
.map(|s| s.as_str())
|
||
}
|
||
|
||
/// Set field origin by box (class-level)
|
||
pub fn set_field_origin_by_box(&mut self, base_box: String, field: String, origin: String) {
|
||
self.field_origin_by_box.insert((base_box, field), origin);
|
||
}
|
||
|
||
/// Register a static method
|
||
pub fn register_static_method(&mut self, method_name: String, box_name: String, arity: usize) {
|
||
self.static_method_index
|
||
.entry(method_name)
|
||
.or_insert_with(Vec::new)
|
||
.push((box_name, arity));
|
||
}
|
||
|
||
/// Get static method candidates
|
||
pub fn get_static_method_candidates(&self, method_name: &str) -> Option<&[(String, usize)]> {
|
||
self.static_method_index
|
||
.get(method_name)
|
||
.map(|v| v.as_slice())
|
||
}
|
||
|
||
/// Get method tail index candidates
|
||
pub fn get_method_tail_candidates(&self, tail: &str) -> Option<&[String]> {
|
||
self.method_tail_index.get(tail).map(|v| v.as_slice())
|
||
}
|
||
|
||
/// Rebuild method tail index if needed
|
||
pub fn maybe_rebuild_method_tail_index(&mut self, current_source_len: usize) -> bool {
|
||
if self.method_tail_index_source_len != current_source_len {
|
||
self.method_tail_index_source_len = current_source_len;
|
||
true
|
||
} else {
|
||
false
|
||
}
|
||
}
|
||
|
||
/// Add method tail index entry
|
||
pub fn add_method_tail_entry(&mut self, tail: String, full_name: String) {
|
||
self.method_tail_index
|
||
.entry(tail)
|
||
.or_insert_with(Vec::new)
|
||
.push(full_name);
|
||
}
|
||
|
||
/// Clear method tail index
|
||
pub fn clear_method_tail_index(&mut self) {
|
||
self.method_tail_index.clear();
|
||
}
|
||
|
||
/// Get plugin method signature
|
||
pub fn get_plugin_method_sig(&self, box_name: &str, method_name: &str) -> Option<&MirType> {
|
||
self.plugin_method_sigs
|
||
.get(&(box_name.to_string(), method_name.to_string()))
|
||
}
|
||
|
||
/// Set current slot registry
|
||
pub fn set_slot_registry(&mut self, registry: FunctionSlotRegistry) {
|
||
self.current_slot_registry = Some(registry);
|
||
}
|
||
|
||
/// Take current slot registry (consumes it)
|
||
pub fn take_slot_registry(&mut self) -> Option<FunctionSlotRegistry> {
|
||
self.current_slot_registry.take()
|
||
}
|
||
|
||
/// Clear current slot registry
|
||
pub fn clear_slot_registry(&mut self) {
|
||
self.current_slot_registry = None;
|
||
}
|
||
}
|
||
|
||
impl Default for CompilationContext {
|
||
fn default() -> Self {
|
||
Self::new()
|
||
}
|
||
}
|
||
|
||
#[cfg(test)]
|
||
mod tests {
|
||
use super::*;
|
||
|
||
#[test]
|
||
fn test_compilation_context_creation() {
|
||
let ctx = CompilationContext::new();
|
||
assert!(ctx.current_static_box.is_none());
|
||
assert!(ctx.user_defined_boxes.is_empty());
|
||
assert!(ctx.reserved_value_ids.is_empty());
|
||
}
|
||
|
||
#[test]
|
||
fn test_user_defined_box() {
|
||
let mut ctx = CompilationContext::new();
|
||
assert!(!ctx.is_user_defined_box("MyBox"));
|
||
|
||
ctx.register_user_box("MyBox".to_string());
|
||
assert!(ctx.is_user_defined_box("MyBox"));
|
||
}
|
||
|
||
#[test]
|
||
fn test_reserved_value_ids() {
|
||
let mut ctx = CompilationContext::new();
|
||
let id = ValueId::new(42);
|
||
|
||
assert!(!ctx.is_reserved_value_id(id));
|
||
|
||
ctx.reserve_value_id(id);
|
||
assert!(ctx.is_reserved_value_id(id));
|
||
|
||
ctx.clear_reserved_value_ids();
|
||
assert!(!ctx.is_reserved_value_id(id));
|
||
}
|
||
|
||
#[test]
|
||
fn test_static_box_mode() {
|
||
let mut ctx = CompilationContext::new();
|
||
assert!(!ctx.is_in_static_box());
|
||
|
||
ctx.enter_static_box("Main".to_string());
|
||
assert!(ctx.is_in_static_box());
|
||
assert_eq!(ctx.current_static_box(), Some("Main"));
|
||
|
||
ctx.exit_static_box();
|
||
assert!(!ctx.is_in_static_box());
|
||
assert_eq!(ctx.current_static_box(), None);
|
||
}
|
||
|
||
#[test]
|
||
fn test_weak_field_registry() {
|
||
let mut ctx = CompilationContext::new();
|
||
|
||
ctx.register_weak_field("MyBox".to_string(), "weakField".to_string());
|
||
assert!(ctx.is_weak_field("MyBox", "weakField"));
|
||
assert!(!ctx.is_weak_field("MyBox", "strongField"));
|
||
assert!(!ctx.is_weak_field("OtherBox", "weakField"));
|
||
}
|
||
|
||
#[test]
|
||
fn test_property_getter_registry() {
|
||
let mut ctx = CompilationContext::new();
|
||
|
||
ctx.register_property_getter(
|
||
"MyBox".to_string(),
|
||
"computed".to_string(),
|
||
PropertyKind::Computed,
|
||
);
|
||
|
||
assert_eq!(
|
||
ctx.get_property_kind("MyBox", "computed"),
|
||
Some(&PropertyKind::Computed)
|
||
);
|
||
assert_eq!(ctx.get_property_kind("MyBox", "other"), None);
|
||
}
|
||
|
||
#[test]
|
||
fn test_field_origin_tracking() {
|
||
let mut ctx = CompilationContext::new();
|
||
let base_id = ValueId::new(10);
|
||
|
||
ctx.set_field_origin_class(base_id, "name".to_string(), "StringBox".to_string());
|
||
assert_eq!(
|
||
ctx.get_field_origin_class(base_id, "name"),
|
||
Some("StringBox")
|
||
);
|
||
assert_eq!(ctx.get_field_origin_class(base_id, "other"), None);
|
||
}
|
||
|
||
#[test]
|
||
fn test_static_method_index() {
|
||
let mut ctx = CompilationContext::new();
|
||
|
||
ctx.register_static_method("parse".to_string(), "JsonBox".to_string(), 1);
|
||
ctx.register_static_method("parse".to_string(), "XmlBox".to_string(), 1);
|
||
|
||
let candidates = ctx.get_static_method_candidates("parse").unwrap();
|
||
assert_eq!(candidates.len(), 2);
|
||
assert!(candidates.contains(&("JsonBox".to_string(), 1)));
|
||
assert!(candidates.contains(&("XmlBox".to_string(), 1)));
|
||
}
|
||
|
||
#[test]
|
||
fn test_method_tail_index() {
|
||
let mut ctx = CompilationContext::new();
|
||
|
||
ctx.add_method_tail_entry(".str/0".to_string(), "JsonNode.str/0".to_string());
|
||
ctx.add_method_tail_entry(".str/0".to_string(), "XmlNode.str/0".to_string());
|
||
|
||
let candidates = ctx.get_method_tail_candidates(".str/0").unwrap();
|
||
assert_eq!(candidates.len(), 2);
|
||
assert!(candidates.contains(&"JsonNode.str/0".to_string()));
|
||
assert!(candidates.contains(&"XmlNode.str/0".to_string()));
|
||
}
|
||
|
||
#[test]
|
||
fn test_method_tail_index_rebuild() {
|
||
let mut ctx = CompilationContext::new();
|
||
|
||
assert!(ctx.maybe_rebuild_method_tail_index(100));
|
||
assert!(!ctx.maybe_rebuild_method_tail_index(100));
|
||
assert!(ctx.maybe_rebuild_method_tail_index(200));
|
||
}
|
||
}
|