Step 2完了: peek→match完全統一 + 重大PHI命令バグ発見

## 🎉 Step 2: peek→match完全統一アーキテクチャクリーンアップ完了
-  15ファイルで PeekExpr → MatchExpr 一括置換完了
-  lowering/peek.rs → match_expr.rs 完全移行
-  AI理解性・コードベース一貫性・保守性大幅向上

## 🔍 Step 3: 複数行パース問題調査完了
-  Task先生による根本原因特定完了
- 原因: オブジェクトリテラルパーサーの改行スキップ不足
- 修正: src/parser/expr/primary.rs の skip_newlines() 追加

## 🚨 重大発見: PHI命令処理バグ
- 問題: gemini_test_case.nyash で期待値2→実際0
- 原因: フェーズM+M.2のPHI統一作業でループ後変数マージに回帰バグ
- 詳細: PHI命令は正常だが、print時に間違ったPHI参照
- 影響: Phase 15セルフホスティング基盤の重大バグ

## 📝 CLAUDE.md更新
- 全進捗状況の詳細記録
- 次のアクション: ChatGPT相談でMIRビルダー修正戦略立案

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Selfhosting Dev
2025-09-23 09:00:07 +09:00
parent f469b80f0a
commit fc4c866151
27 changed files with 122 additions and 84 deletions

View File

@ -276,18 +276,32 @@ NYASH_DISABLE_PLUGINS=1 ./target/release/nyash program.nyash
NYASH_LLVM_USE_HARNESS=1 ./target/release/nyash program.nyash
```
## 📝 Update (2025-09-23) ✅ Step 1完了peek→match統一 Step 2開始
## 📝 Update (2025-09-23) 🚨 重大PHI命令バグ発見Step 4改行処理も課題
-**フェーズM+M.2完全達成!** PHI統一革命でcollect_prints問題根本解決
-**Step 1完全達成** match式オブジェクトリテラル判定修正完了
- **修正完了**: `src/parser/expr/match_expr.rs``is_object_literal()` メソッド追加
- **副作用修正**: `match_token()``current_token()` で副作用除去
- **動作確認**: 単行match式 + オブジェクトリテラル ✅ 完全動作
- **発見**: 複数行パース未対応(後回し決定)
- 🚀 **Step 2開始準備** peek→match完全統一でアーキテクチャクリーンアップ
- **対象ファイル**: `json_v0_bridge/ast.rs`, `lowering/peek.rs``match_expr.rs`
- **統一効果**: AI理解向上・ドキュメント整合性・保守性向上・ソースコード美化
- **作業順序**: claude.md更新→todo調整→commit→peek構造体→match構造体完全移行
- 🎯 **アーキテクチャ美化**: ややこしいpeek/match混在状況の完全解消へ
- **Step 2完全達成** peek→match完全統一でアーキテクチャクリーンアップ完了
- **統一完了**: 15ファイルで `PeekExpr``MatchExpr` 一括置換完了
- **ファイル移行**: `lowering/peek.rs``match_expr.rs` 完全移行
- **コンパイル成功**: エラーゼロで正常ビルド完了
- **動作確認**: match式テスト正常動作 ✅
- **効果**: AI理解性向上・コードベース一貫性・保守性大幅向上
-**Step 3完全達成** Task先生による複数行パース問題根本原因特定完了
- **原因特定**: オブジェクトリテラルパーサーの改行スキップ不足(`src/parser/expr/primary.rs`
- **修正箇所**: L49、L77-79に `skip_newlines()` 追加で解決可能
- **工数**: 30分以内の簡単修正
- 🤔 **Step 4課題発見** 改行処理アーキテクチャの美しさ問題
- **問題**: `skip_newlines()` を各所に散りばめる方法は美しくない
- **課題**: 保守性・一貫性・見落としリスク・設計の美しさ
- **方針**: Gemini 3相談でアーキテクチャ根本改善検討
- 🚨 **重大発見!** PHI命令処理バグgemini_test_case: 期待値2→実際0
- **問題**: フェーズM+M.2のPHI統一作業でループ後変数マージに回帰バグ
- **詳細**: PHI命令は正しく動作v8→2だが、print時に間違ったPHIv4→0参照
- **根本原因**: ループ脱出後の変数PHI接続が初期値を参照している
- **影響**: Phase 15セルフホスティング基盤の重大バグ
- **次のアクション**: ChatGPT相談でMIRビルダー修正戦略立案
## 📝 Update (2025-09-22) 🎯 Phase 15 JITアーカイブ完了デバッグ大進展
-**JIT/Craneliftアーカイブ完了** Phase 15集中開発のため全JIT機能を安全にアーカイブ

14
gemini_test_case.nyash Normal file
View File

@ -0,0 +1,14 @@
local cond1 = true
local cond2 = false
local x = 0
loop(true) {
if(cond1) {
if(cond2) {
x = 1
} else {
x = 2
}
break
}
}
print(x)

View File

@ -126,8 +126,8 @@ pub enum ExpressionNode {
MeExpression {
span: Span,
},
/// peek式: peek <expr> { lit => expr, ... else => expr }
PeekExpr {
/// match式: match <expr> { lit => expr, ... else => expr }
MatchExpr {
scrutinee: Box<ASTNode>,
arms: Vec<(LiteralValue, ASTNode)>,
else_expr: Box<ASTNode>,
@ -398,8 +398,8 @@ pub enum ASTNode {
span: Span,
},
/// peek式: peek <expr> { lit => expr, ... else => expr }
PeekExpr {
/// match式: match <expr> { lit => expr, ... else => expr }
MatchExpr {
scrutinee: Box<ASTNode>,
arms: Vec<(LiteralValue, ASTNode)>,
else_expr: Box<ASTNode>,

View File

@ -43,7 +43,7 @@ impl ASTNode {
ASTNode::Throw { .. } => "Throw",
ASTNode::AwaitExpression { .. } => "AwaitExpression",
ASTNode::QMarkPropagate { .. } => "QMarkPropagate",
ASTNode::PeekExpr { .. } => "PeekExpr",
ASTNode::MatchExpr { .. } => "MatchExpr",
ASTNode::Lambda { .. } => "Lambda",
ASTNode::ArrayLiteral { .. } => "ArrayLiteral",
ASTNode::MapLiteral { .. } => "MapLiteral",
@ -78,7 +78,7 @@ impl ASTNode {
ASTNode::FromCall { .. } => ASTNodeType::Expression,
ASTNode::ThisField { .. } => ASTNodeType::Expression,
ASTNode::MeField { .. } => ASTNodeType::Expression,
ASTNode::PeekExpr { .. } => ASTNodeType::Expression,
ASTNode::MatchExpr { .. } => ASTNodeType::Expression,
ASTNode::QMarkPropagate { .. } => ASTNodeType::Expression,
ASTNode::Lambda { .. } => ASTNodeType::Expression,
ASTNode::ArrayLiteral { .. } => ASTNodeType::Expression,
@ -303,7 +303,7 @@ impl ASTNode {
ASTNode::AwaitExpression { expression, .. } => {
format!("Await({:?})", expression)
}
ASTNode::PeekExpr { .. } => "PeekExpr".to_string(),
ASTNode::MatchExpr { .. } => "MatchExpr".to_string(),
ASTNode::QMarkPropagate { .. } => "QMarkPropagate".to_string(),
ASTNode::Lambda { params, body, .. } => {
format!("Lambda({} params, {} statements)", params.len(), body.len())
@ -356,7 +356,7 @@ impl ASTNode {
ASTNode::FunctionCall { span, .. } => *span,
ASTNode::Call { span, .. } => *span,
ASTNode::AwaitExpression { span, .. } => *span,
ASTNode::PeekExpr { span, .. } => *span,
ASTNode::MatchExpr { span, .. } => *span,
ASTNode::QMarkPropagate { span, .. } => *span,
ASTNode::Lambda { span, .. } => *span,
ASTNode::ArrayLiteral { span, .. } => *span,

View File

@ -66,24 +66,27 @@ def op_boxcall(owner, fn, inst: Dict[str, Any], regs: Dict[int, Any]) -> None:
return
# User-defined box: dispatch to lowered function if available (Box.method/N)
# Skip built-in boxes (ArrayBox, MapBox, etc.) to let them fall through to their implementations
if isinstance(recv, dict) and isinstance(method, str) and "__box__" in recv:
box_name = recv.get("__box__")
cand = f"{box_name}.{method}/{len(args)}"
callee = owner.functions.get(cand)
if callee is not None:
owner._dbg(f"[pyvm] boxcall dispatch -> {cand} args={args}")
out = owner._exec_function(callee, args)
owner._set(regs, inst.get("dst"), out)
return
else:
if owner._debug:
prefix = f"{box_name}.{method}/"
cands = sorted([k for k in owner.functions.keys() if k.startswith(prefix)])
if cands:
owner._dbg(f"[pyvm] boxcall unresolved: '{cand}' — available: {cands}")
else:
any_for_box = sorted([k for k in owner.functions.keys() if k.startswith(f"{box_name}.")])
owner._dbg(f"[pyvm] boxcall unresolved: '{cand}' — no candidates; methods for {box_name}: {any_for_box}")
# Skip built-in boxes - let them fall through to built-in implementations below
if box_name not in ("ArrayBox", "MapBox", "ConsoleBox", "FileBox", "PathBox", "JsonDocBox", "JsonNodeBox"):
cand = f"{box_name}.{method}/{len(args)}"
callee = owner.functions.get(cand)
if callee is not None:
owner._dbg(f"[pyvm] boxcall dispatch -> {cand} args={args}")
out = owner._exec_function(callee, args)
owner._set(regs, inst.get("dst"), out)
return
else:
if owner._debug:
prefix = f"{box_name}.{method}/"
cands = sorted([k for k in owner.functions.keys() if k.startswith(prefix)])
if cands:
owner._dbg(f"[pyvm] boxcall unresolved: '{cand}' — available: {cands}")
else:
any_for_box = sorted([k for k in owner.functions.keys() if k.startswith(f"{box_name}.")])
owner._dbg(f"[pyvm] boxcall unresolved: '{cand}' — no candidates; methods for {box_name}: {any_for_box}")
# ConsoleBox methods
if method in ("print", "println", "log") and owner._is_console(recv):

View File

@ -88,8 +88,8 @@ pub fn ast_to_json(ast: &ASTNode) -> Value {
"kind":"Map",
"entries": entries.into_iter().map(|(k,v)| json!({"k":k,"v":ast_to_json(&v)})).collect::<Vec<_>>()
}),
ASTNode::PeekExpr { scrutinee, arms, else_expr, .. } => json!({
"kind":"PeekExpr",
ASTNode::MatchExpr { scrutinee, arms, else_expr, .. } => json!({
"kind":"MatchExpr",
"scrutinee": ast_to_json(&scrutinee),
"arms": arms.into_iter().map(|(lit, body)| json!({
"literal": {
@ -147,7 +147,7 @@ pub fn json_to_ast(v: &Value) -> Option<ASTNode> {
"Map" => ASTNode::MapLiteral { entries: v.get("entries")?.as_array()?.iter().filter_map(|e| {
Some((e.get("k")?.as_str()?.to_string(), json_to_ast(e.get("v")?)?))
}).collect(), span: Span::unknown() },
"PeekExpr" => {
"MatchExpr" => {
let scr = json_to_ast(v.get("scrutinee")?)?;
let arms_json = v.get("arms")?.as_array()?.iter();
let mut arms = Vec::new();
@ -158,7 +158,7 @@ pub fn json_to_ast(v: &Value) -> Option<ASTNode> {
arms.push((lit, body));
}
let else_expr = json_to_ast(v.get("else")?)?;
ASTNode::PeekExpr {
ASTNode::MatchExpr {
scrutinee: Box::new(scr),
arms,
else_expr: Box::new(else_expr),

View File

@ -85,7 +85,7 @@ impl super::MirBuilder {
self.build_qmark_propagate_expression(*expression.clone())
}
ASTNode::PeekExpr {
ASTNode::MatchExpr {
scrutinee,
arms,
else_expr,

View File

@ -123,7 +123,7 @@ impl super::MirBuilder {
ASTNode::AwaitExpression { expression, .. } => {
collect_vars(expression, used, locals);
}
ASTNode::PeekExpr {
ASTNode::MatchExpr {
scrutinee,
arms,
else_expr,

View File

@ -1,6 +1,7 @@
use super::{ConstValue, Effect, EffectMask, MirInstruction, ValueId};
use crate::ast::{ASTNode, CallExpr};
use crate::mir::TypeOpKind;
use crate::mir::utils::is_current_block_terminated;
impl super::MirBuilder {
// Print statement: env.console.log(value) with early TypeOp handling
@ -141,7 +142,7 @@ impl super::MirBuilder {
last_value = Some(self.build_expression(statement)?);
// If the current block was terminated by this statement (e.g., return/throw),
// do not emit any further instructions for this block.
if self.is_current_block_terminated() {
if is_current_block_terminated(self)? {
break;
}
}

View File

@ -118,7 +118,7 @@ pub(super) fn collect_free_vars(
ASTNode::AwaitExpression { expression, .. } => {
collect_free_vars(expression, used, locals);
}
ASTNode::PeekExpr {
ASTNode::MatchExpr {
scrutinee,
arms,
else_expr,

View File

@ -455,20 +455,8 @@ impl<'a> LoopBuilder<'a> {
let mut last = None;
for s in statements.into_iter() {
last = Some(self.build_statement(s)?);
// Stop early if this block has been terminated (e.g., break/continue)
let cur_id = self.current_block()?;
let terminated = {
if let Some(ref fun_ro) = self.parent_builder.current_function {
if let Some(bb) = fun_ro.get_block(cur_id) {
bb.is_terminated()
} else {
false
}
} else {
false
}
};
if terminated {
// フェーズS修正統一終端検出ユーティリティ使用
if is_current_block_terminated(self.parent_builder)? {
break;
}
}

View File

@ -154,7 +154,7 @@ impl NyashParser {
};
// Wrap with peek: peek expr { null => null, else => access(expr) }
expr = ASTNode::PeekExpr {
expr = ASTNode::MatchExpr {
scrutinee: Box::new(expr.clone()),
arms: vec![(
crate::ast::LiteralValue::Null,

View File

@ -23,7 +23,7 @@ impl NyashParser {
self.advance();
let rhs = self.expr_parse_or()?;
let scr = expr;
expr = ASTNode::PeekExpr {
expr = ASTNode::MatchExpr {
scrutinee: Box::new(scr.clone()),
arms: vec![(crate::ast::LiteralValue::Null, rhs)],
else_expr: Box::new(scr),

View File

@ -193,7 +193,7 @@ impl NyashParser {
})?;
if !saw_type_arm && !saw_guard {
// 既存の Lower を活用するため PeekExpr に落とす(型パターンが無い場合のみ)
// 既存の Lower を活用するため MatchExpr に落とす(型パターンが無い場合のみ)
let mut lit_arms: Vec<(LiteralValue, ASTNode)> = Vec::new();
for arm in arms_any.into_iter() {
match arm {
@ -206,7 +206,7 @@ impl NyashParser {
MatchArm::Type { .. } => unreachable!(),
}
}
return Ok(ASTNode::PeekExpr {
return Ok(ASTNode::MatchExpr {
scrutinee: Box::new(scrutinee),
arms: lit_arms,
else_expr: Box::new(else_expr),

View File

@ -46,7 +46,9 @@ impl NyashParser {
let ident_key_on = std::env::var("NYASH_ENABLE_MAP_IDENT_KEY").ok().as_deref()
== Some("1")
|| sugar_level.as_deref() == Some("full");
self.skip_newlines();
while !self.match_token(&TokenType::RBRACE) && !self.is_at_end() {
self.skip_newlines();
let key = match &self.current_token().token_type {
TokenType::STRING(s) => {
let v = s.clone();
@ -76,8 +78,10 @@ impl NyashParser {
entries.push((key, value_expr));
if self.match_token(&TokenType::COMMA) {
self.advance();
self.skip_newlines();
}
}
self.skip_newlines();
self.consume(TokenType::RBRACE)?;
Ok(ASTNode::MapLiteral {
entries,

View File

@ -84,12 +84,12 @@ fn rewrite(ast: ASTNode) -> ASTNode {
operand: Box::new(rewrite(*operand)),
span,
},
ASTNode::PeekExpr {
ASTNode::MatchExpr {
scrutinee,
arms,
else_expr,
span,
} => ASTNode::PeekExpr {
} => ASTNode::MatchExpr {
scrutinee: Box::new(rewrite(*scrutinee)),
arms: arms.into_iter().map(|(l, e)| (l, rewrite(e))).collect(),
else_expr: Box::new(rewrite(*else_expr)),

View File

@ -58,7 +58,7 @@ pub(super) struct CatchV0 {
}
#[derive(Debug, Deserialize, Serialize, Clone)]
pub(super) struct PeekArmV0 {
pub(super) struct MatchArmV0 {
pub(super) label: String,
pub(super) expr: ExprV0,
}
@ -120,9 +120,9 @@ pub(super) enum ExprV0 {
#[serde(rename = "else")]
r#else: Box<ExprV0>,
},
Peek {
Match {
scrutinee: Box<ExprV0>,
arms: Vec<PeekArmV0>,
arms: Vec<MatchArmV0>,
#[serde(rename = "else")]
r#else: Box<ExprV0>,
},

View File

@ -14,7 +14,7 @@ pub(super) mod loop_;
pub(super) mod try_catch;
pub(super) mod expr;
pub(super) mod ternary; // placeholder (not wired)
pub(super) mod peek; // placeholder (not wired)
pub(super) mod match_expr; // placeholder (not wired)
pub(super) mod throw_ctx; // thread-local ctx for Result-mode throw routing
#[derive(Clone, Copy)]

View File

@ -1,7 +1,7 @@
use super::merge::new_block;
use super::BridgeEnv;
use super::ternary;
use super::peek;
use super::match_expr;
use crate::mir::{
BasicBlockId, BinaryOp, ConstValue, EffectMask, MirFunction, MirInstruction, ValueId,
};
@ -395,8 +395,8 @@ pub(super) fn lower_expr_with_scope<S: VarScope>(
}
ExprV0::Ternary { cond, then, r#else } =>
ternary::lower_ternary_expr_with_scope(env, f, cur_bb, cond, then, r#else, vars),
ExprV0::Peek { scrutinee, arms, r#else } =>
peek::lower_peek_expr_with_scope(env, f, cur_bb, scrutinee, arms, r#else, vars),
ExprV0::Match { scrutinee, arms, r#else } =>
match_expr::lower_match_expr_with_scope(env, f, cur_bb, scrutinee, arms, r#else, vars),
}
}

View File

@ -1,17 +1,17 @@
//! Peek/expr-block lowering for JSON v0 bridge.
//! Match/expr-block lowering for JSON v0 bridge.
use super::merge::new_block;
use super::BridgeEnv;
use crate::mir::{BasicBlockId, CompareOp, ConstValue, MirFunction, MirInstruction, ValueId};
use super::super::ast::{ExprV0, PeekArmV0};
use super::super::ast::{ExprV0, MatchArmV0};
use super::expr::{lower_expr_with_scope, VarScope};
pub(super) fn lower_peek_expr_with_scope<S: VarScope>(
pub(super) fn lower_match_expr_with_scope<S: VarScope>(
env: &BridgeEnv,
f: &mut MirFunction,
cur_bb: BasicBlockId,
scrutinee: &ExprV0,
arms: &[PeekArmV0],
arms: &[MatchArmV0],
else_expr: &ExprV0,
vars: &mut S,
) -> Result<(ValueId, BasicBlockId), String> {

View File

@ -42,7 +42,7 @@ pub(super) fn transform_postfix_handlers(ast: &nyash_rust::ASTNode) -> nyash_rus
}
// Core normalization pass used by runners (always-on when macros enabled).
// Order matters: for/foreach → match(PeekExpr) → loop tail alignment.
// Order matters: for/foreach → match(MatchExpr) → loop tail alignment.
pub fn normalize_core_pass(ast: &nyash_rust::ASTNode) -> nyash_rust::ASTNode {
let a1 = transform_for_foreach(ast);
let a2 = transform_peek_match_literal(&a1);

View File

@ -2,7 +2,7 @@ fn map_expr_to_stmt(e: nyash_rust::ASTNode) -> nyash_rust::ASTNode { e }
fn transform_peek_to_if_expr(peek: &nyash_rust::ASTNode) -> Option<nyash_rust::ASTNode> {
use nyash_rust::ast::{ASTNode as A, BinaryOperator, Span};
if let A::PeekExpr { scrutinee, arms, else_expr, .. } = peek {
if let A::MatchExpr { scrutinee, arms, else_expr, .. } = peek {
let mut conds_bodies: Vec<(nyash_rust::ast::LiteralValue, A)> = Vec::new();
for (lit, body) in arms { conds_bodies.push((lit.clone(), (*body).clone())); }
let mut current: A = *(*else_expr).clone();
@ -19,7 +19,7 @@ fn transform_peek_to_if_expr(peek: &nyash_rust::ASTNode) -> Option<nyash_rust::A
fn transform_peek_to_if_stmt_assign(peek: &nyash_rust::ASTNode, target: &nyash_rust::ASTNode) -> Option<nyash_rust::ASTNode> {
use nyash_rust::ast::{ASTNode as A, BinaryOperator, Span};
if let A::PeekExpr { scrutinee, arms, else_expr, .. } = peek {
if let A::MatchExpr { scrutinee, arms, else_expr, .. } = peek {
let mut pairs: Vec<(nyash_rust::ast::LiteralValue, A)> = Vec::new();
for (lit, body) in arms { pairs.push((lit.clone(), (*body).clone())); }
let mut current: A = *(*else_expr).clone();
@ -36,7 +36,7 @@ fn transform_peek_to_if_stmt_assign(peek: &nyash_rust::ASTNode, target: &nyash_r
fn transform_peek_to_if_stmt_return(peek: &nyash_rust::ASTNode) -> Option<nyash_rust::ASTNode> {
use nyash_rust::ast::{ASTNode as A, BinaryOperator, Span};
if let A::PeekExpr { scrutinee, arms, else_expr, .. } = peek {
if let A::MatchExpr { scrutinee, arms, else_expr, .. } = peek {
let mut pairs: Vec<(nyash_rust::ast::LiteralValue, A)> = Vec::new();
for (lit, body) in arms { pairs.push((lit.clone(), (*body).clone())); }
let mut current: A = *(*else_expr).clone();
@ -53,7 +53,7 @@ fn transform_peek_to_if_stmt_return(peek: &nyash_rust::ASTNode) -> Option<nyash_
fn transform_peek_to_if_stmt_print(peek: &nyash_rust::ASTNode) -> Option<nyash_rust::ASTNode> {
use nyash_rust::ast::{ASTNode as A, BinaryOperator, Span};
if let A::PeekExpr { scrutinee, arms, else_expr, .. } = peek {
if let A::MatchExpr { scrutinee, arms, else_expr, .. } = peek {
let mut pairs: Vec<(nyash_rust::ast::LiteralValue, A)> = Vec::new();
for (lit, body) in arms { pairs.push((lit.clone(), (*body).clone())); }
let mut current: A = *(*else_expr).clone();

View File

@ -4,7 +4,7 @@ use crate::ast::{ASTNode, LiteralValue, Span};
#[test]
fn mir_lowering_of_peek_expr() {
// Build AST: peek 2 { 1 => 10, 2 => 20, else => 30 }
let ast = ASTNode::PeekExpr {
let ast = ASTNode::MatchExpr {
scrutinee: Box::new(ASTNode::Literal { value: LiteralValue::Integer(2), span: Span::unknown() }),
arms: vec![
(LiteralValue::Integer(1), ASTNode::Literal { value: LiteralValue::Integer(10), span: Span::unknown() }),

View File

@ -11,10 +11,10 @@ fn parse_match_with_block_arm() {
}
"#;
let ast = NyashParser::parse_from_string(src).expect("parse ok");
// Quick structural check: ensure AST contains PeekExpr and Program nodes inside arms
// Quick structural check: ensure AST contains MatchExpr and Program nodes inside arms
fn find_peek(ast: &crate::ast::ASTNode) -> bool {
match ast {
crate::ast::ASTNode::PeekExpr { arms, else_expr, .. } => {
crate::ast::ASTNode::MatchExpr { arms, else_expr, .. } => {
// Expect at least one Program arm
let has_block = arms.iter().any(|(_, e)| matches!(e, crate::ast::ASTNode::Program { .. }));
let else_is_block = matches!(**else_expr, crate::ast::ASTNode::Program { .. });

View File

@ -21,7 +21,7 @@ fn coalesce_peek_rewrite() {
}
match assign.1.as_ref() {
ASTNode::PeekExpr {
ASTNode::MatchExpr {
scrutinee,
arms,
else_expr,
@ -42,6 +42,6 @@ fn coalesce_peek_rewrite() {
_ => panic!("else not a"),
}
}
other => panic!("expected PeekExpr, got {:?}", other),
other => panic!("expected MatchExpr, got {:?}", other),
}
}

View File

@ -16,7 +16,7 @@ fn safe_access_field_and_method() {
// a = user?.profile
match &program[0] {
ASTNode::Assignment { value, .. } => match value.as_ref() {
ASTNode::PeekExpr {
ASTNode::MatchExpr {
scrutinee,
else_expr,
..
@ -36,7 +36,7 @@ fn safe_access_field_and_method() {
other => panic!("else not field access, got {:?}", other),
}
}
other => panic!("expected PeekExpr, got {:?}", other),
other => panic!("expected MatchExpr, got {:?}", other),
},
other => panic!("expected assignment, got {:?}", other),
}
@ -44,7 +44,7 @@ fn safe_access_field_and_method() {
// b = user?.m(1)
match &program[1] {
ASTNode::Assignment { value, .. } => match value.as_ref() {
ASTNode::PeekExpr {
ASTNode::MatchExpr {
scrutinee,
else_expr,
..
@ -70,7 +70,7 @@ fn safe_access_field_and_method() {
other => panic!("else not method call, got {:?}", other),
}
}
other => panic!("expected PeekExpr, got {:?}", other),
other => panic!("expected MatchExpr, got {:?}", other),
},
other => panic!("expected assignment, got {:?}", other),
}

14
test.nyash Normal file
View File

@ -0,0 +1,14 @@
local cond1 = true
local cond2 = false
local x = 0
loop(true) {
if(cond1) {
if(cond2) {
x = 1
} else {
x = 2
}
break
}
}
print(x)