docs: ドキュメント配置ルール(SSOT)確立
## 追加内容 - CLAUDE.md にドキュメント配置ルール(SSOT)セクション追加 - DOCS_LAYOUT.md (SSOT): 置き場所ルール定義 - phases/README.md: Phase ドキュメント説明 - design/README.md: 設計図ドキュメント説明 - investigations/README.md: 調査ログ説明 ## ルール概要 1. **Phase 文書** → phases/phase-<N>/ 2. **設計図** → design/ 3. **調査ログ** → investigations/ (結論を 10-Now/20-Decisions に反映) ## 導線 - CLAUDE.md で概要説明 - DOCS_LAYOUT.md で詳細定義(SSOT) - 各フォルダ README で参照方法 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
@ -138,18 +138,25 @@ def lower_binop(
|
||||
# except Exception:
|
||||
# pass
|
||||
|
||||
# tagged string handles?(どちらかが string-ish のとき)
|
||||
# Phase 196: TypeFacts SSOT - Only check for actual string types (not use-site demands)
|
||||
# Check if BOTH operands are known to be strings from their definition
|
||||
any_tagged = False
|
||||
try:
|
||||
if resolver is not None:
|
||||
if hasattr(resolver, 'is_stringish'):
|
||||
any_tagged = resolver.is_stringish(lhs) or resolver.is_stringish(rhs)
|
||||
# literal strings are tracked separately
|
||||
if not any_tagged and hasattr(resolver, 'string_literals'):
|
||||
# Only check string_literals (TypeFacts), NOT is_stringish (TypeDemands)
|
||||
if hasattr(resolver, 'string_literals'):
|
||||
any_tagged = (lhs in resolver.string_literals) or (rhs in resolver.string_literals)
|
||||
# Check if resolver has explicit type information (MirType::String or StringBox)
|
||||
if not any_tagged and hasattr(resolver, 'value_types'):
|
||||
lhs_ty = resolver.value_types.get(lhs)
|
||||
rhs_ty = resolver.value_types.get(rhs)
|
||||
lhs_str = lhs_ty and (lhs_ty.get('kind') == 'string' or
|
||||
(lhs_ty.get('kind') == 'handle' and lhs_ty.get('box_type') == 'StringBox'))
|
||||
rhs_str = rhs_ty and (rhs_ty.get('kind') == 'string' or
|
||||
(rhs_ty.get('kind') == 'handle' and rhs_ty.get('box_type') == 'StringBox'))
|
||||
any_tagged = lhs_str or rhs_str
|
||||
except Exception:
|
||||
pass
|
||||
# Phase 131-6: Removed force_string from this check
|
||||
is_str = is_ptr_side or any_tagged
|
||||
|
||||
# Phase 131-6 DEBUG
|
||||
@ -184,12 +191,25 @@ def lower_binop(
|
||||
return val
|
||||
return ir.Constant(i64, 0)
|
||||
|
||||
# Decide route: handle+handle when both sides are string-ish; otherwise pointer+int route.
|
||||
# Phase 196: TypeFacts SSOT - Use handle+handle only when BOTH are strings
|
||||
lhs_tag = False; rhs_tag = False
|
||||
try:
|
||||
if resolver is not None and hasattr(resolver, 'is_stringish'):
|
||||
lhs_tag = resolver.is_stringish(lhs)
|
||||
rhs_tag = resolver.is_stringish(rhs)
|
||||
if resolver is not None:
|
||||
# Check string_literals (actual string constants)
|
||||
if hasattr(resolver, 'string_literals'):
|
||||
lhs_tag = lhs in resolver.string_literals
|
||||
rhs_tag = rhs in resolver.string_literals
|
||||
# Check value_types for String/StringBox types
|
||||
if not lhs_tag and hasattr(resolver, 'value_types'):
|
||||
lhs_ty = resolver.value_types.get(lhs)
|
||||
if lhs_ty and (lhs_ty.get('kind') == 'string' or
|
||||
(lhs_ty.get('kind') == 'handle' and lhs_ty.get('box_type') == 'StringBox')):
|
||||
lhs_tag = True
|
||||
if not rhs_tag and hasattr(resolver, 'value_types'):
|
||||
rhs_ty = resolver.value_types.get(rhs)
|
||||
if rhs_ty and (rhs_ty.get('kind') == 'string' or
|
||||
(rhs_ty.get('kind') == 'handle' and rhs_ty.get('box_type') == 'StringBox')):
|
||||
rhs_tag = True
|
||||
except Exception:
|
||||
pass
|
||||
if lhs_tag and rhs_tag:
|
||||
|
||||
@ -67,7 +67,7 @@ impl super::MirBuilder {
|
||||
super::builder_calls::CallTarget::Global(name),
|
||||
vec![lhs, rhs],
|
||||
)?;
|
||||
// 型注釈(Phase 3-B: value_origin_newbox もチェック&両方登録)
|
||||
// Phase 196: TypeFacts SSOT - AddOperator call type annotation
|
||||
let lhs_is_str = match self.value_types.get(&lhs) {
|
||||
Some(MirType::String) => true,
|
||||
Some(MirType::Box(bt)) if bt == "StringBox" => true,
|
||||
@ -86,14 +86,17 @@ impl super::MirBuilder {
|
||||
.map(|s| s == "StringBox")
|
||||
.unwrap_or(false),
|
||||
};
|
||||
if lhs_is_str || rhs_is_str {
|
||||
if lhs_is_str && rhs_is_str {
|
||||
// BOTH are strings: result is string
|
||||
self.value_types
|
||||
.insert(dst, MirType::Box("StringBox".to_string()));
|
||||
self.value_origin_newbox
|
||||
.insert(dst, "StringBox".to_string());
|
||||
} else {
|
||||
} else if !lhs_is_str && !rhs_is_str {
|
||||
// NEITHER is a string: numeric addition
|
||||
self.value_types.insert(dst, MirType::Integer);
|
||||
}
|
||||
// else: Mixed - leave Unknown for use-site coercion
|
||||
} else if all_call {
|
||||
// Lower other arithmetic ops to operator boxes under ALL flag
|
||||
let (name, guard_prefix) = match op {
|
||||
@ -152,8 +155,10 @@ impl super::MirBuilder {
|
||||
} else {
|
||||
self.emit_instruction(MirInstruction::BinOp { dst, op, lhs, rhs })?;
|
||||
}
|
||||
// Phase 196: TypeFacts SSOT - BinOp type is determined by operands only
|
||||
// String concatenation is handled at use-site in LLVM lowering
|
||||
if matches!(op, crate::mir::BinaryOp::Add) {
|
||||
// Phase 3-B: value_origin_newbox もチェック&両方登録
|
||||
// Check if BOTH operands are known to be strings (TypeFacts)
|
||||
let lhs_is_str = match self.value_types.get(&lhs) {
|
||||
Some(MirType::String) => true,
|
||||
Some(MirType::Box(bt)) if bt == "StringBox" => true,
|
||||
@ -172,14 +177,18 @@ impl super::MirBuilder {
|
||||
.map(|s| s == "StringBox")
|
||||
.unwrap_or(false),
|
||||
};
|
||||
if lhs_is_str || rhs_is_str {
|
||||
if lhs_is_str && rhs_is_str {
|
||||
// BOTH are strings: result is definitely a string
|
||||
self.value_types
|
||||
.insert(dst, MirType::Box("StringBox".to_string()));
|
||||
self.value_origin_newbox
|
||||
.insert(dst, "StringBox".to_string());
|
||||
} else {
|
||||
} else if !lhs_is_str && !rhs_is_str {
|
||||
// NEITHER is a string: numeric addition
|
||||
self.value_types.insert(dst, MirType::Integer);
|
||||
}
|
||||
// else: Mixed types (string + int or int + string)
|
||||
// Leave dst type as Unknown - LLVM will handle coercion at use-site
|
||||
} else {
|
||||
self.value_types.insert(dst, MirType::Integer);
|
||||
}
|
||||
@ -195,8 +204,10 @@ impl super::MirBuilder {
|
||||
} else {
|
||||
self.emit_instruction(MirInstruction::BinOp { dst, op, lhs, rhs })?;
|
||||
}
|
||||
// Phase 196: TypeFacts SSOT - BinOp type is determined by operands only
|
||||
// String concatenation is handled at use-site in LLVM lowering
|
||||
if matches!(op, crate::mir::BinaryOp::Add) {
|
||||
// Phase 3-B: value_origin_newbox もチェック&両方登録
|
||||
// Check if BOTH operands are known to be strings (TypeFacts)
|
||||
let lhs_is_str = match self.value_types.get(&lhs) {
|
||||
Some(MirType::String) => true,
|
||||
Some(MirType::Box(bt)) if bt == "StringBox" => true,
|
||||
@ -215,14 +226,18 @@ impl super::MirBuilder {
|
||||
.map(|s| s == "StringBox")
|
||||
.unwrap_or(false),
|
||||
};
|
||||
if lhs_is_str || rhs_is_str {
|
||||
if lhs_is_str && rhs_is_str {
|
||||
// BOTH are strings: result is definitely a string
|
||||
self.value_types
|
||||
.insert(dst, MirType::Box("StringBox".to_string()));
|
||||
self.value_origin_newbox
|
||||
.insert(dst, "StringBox".to_string());
|
||||
} else {
|
||||
} else if !lhs_is_str && !rhs_is_str {
|
||||
// NEITHER is a string: numeric addition
|
||||
self.value_types.insert(dst, MirType::Integer);
|
||||
}
|
||||
// else: Mixed types (string + int or int + string)
|
||||
// Leave dst type as Unknown - LLVM will handle coercion at use-site
|
||||
} else {
|
||||
self.value_types.insert(dst, MirType::Integer);
|
||||
}
|
||||
|
||||
@ -343,7 +343,8 @@ pub fn emit_mir_json_for_harness(
|
||||
B::Or => "|",
|
||||
};
|
||||
let mut obj = json!({"op":"binop","operation": op_s, "lhs": lhs.as_u32(), "rhs": rhs.as_u32(), "dst": dst.as_u32()});
|
||||
// dst_type hint for string concatenation: if either side is String-ish and op is '+', mark result as String handle
|
||||
// dst_type hint for string concatenation: ONLY if BOTH sides are explicitly String-ish and op is '+', mark result as String handle
|
||||
// Option C: Unknown/None types default to Integer arithmetic (conservative)
|
||||
if matches!(op, B::Add) {
|
||||
let lhs_is_str = match f.metadata.value_types.get(lhs) {
|
||||
Some(MirType::String) => true,
|
||||
@ -355,7 +356,9 @@ pub fn emit_mir_json_for_harness(
|
||||
Some(MirType::Box(bt)) if bt == "StringBox" => true,
|
||||
_ => false,
|
||||
};
|
||||
if lhs_is_str || rhs_is_str {
|
||||
// Changed: require BOTH to be explicitly String (lhs_is_str && rhs_is_str)
|
||||
// Default: Unknown → Integer arithmetic
|
||||
if lhs_is_str && rhs_is_str {
|
||||
obj["dst_type"] =
|
||||
json!({"kind":"handle","box_type":"StringBox"});
|
||||
}
|
||||
@ -733,6 +736,7 @@ pub fn emit_mir_json_for_harness_bin(
|
||||
B::Or => "|",
|
||||
};
|
||||
let mut obj = json!({"op":"binop","operation": op_s, "lhs": lhs.as_u32(), "rhs": rhs.as_u32(), "dst": dst.as_u32()});
|
||||
// Option C: Unknown/None types default to Integer arithmetic (conservative)
|
||||
if matches!(op, B::Add) {
|
||||
let lhs_is_str = match f.metadata.value_types.get(lhs) {
|
||||
Some(MirType::String) => true,
|
||||
@ -744,7 +748,9 @@ pub fn emit_mir_json_for_harness_bin(
|
||||
Some(MirType::Box(bt)) if bt == "StringBox" => true,
|
||||
_ => false,
|
||||
};
|
||||
if lhs_is_str || rhs_is_str {
|
||||
// Changed: require BOTH to be explicitly String (lhs_is_str && rhs_is_str)
|
||||
// Default: Unknown → Integer arithmetic
|
||||
if lhs_is_str && rhs_is_str {
|
||||
obj["dst_type"] =
|
||||
json!({"kind":"handle","box_type":"StringBox"});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user