Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
- **عنوان المقترح:** فصل التحذيرات عن الأخطاء في بانِي SIR (إنهاء البناء الناجح الصامت خطأً)
- **النطاق:** لغة `text/` (المترجم `sadc` — الواجهة الأماميّة/بانِي SIR)
- **تاريخ البدء:** 2026-07-06
- **رقم الـ RFC:** (يُترك فارغاً حتى الدمج)
- **الحالة:** مقترَح (PR مفتوح)
- **Issue التتبُّع:** (يُترك فارغاً حتى القبول)

# ملخّص

يخلط بانِي SIR (`SIRBuilder`) في المترجم اليوم **التحذيرات والأخطاء في وعاء واحد**
(`errors_`)، بينما تُرجِع `buildModule` وحدةً **غير فارغة** حتّى حين يمتلئ هذا الوعاء
بأخطاء حقيقيّة. فيفحص سائق المترجم `!sir_module_` فقط، فتُهمَل أخطاء بناء SIR **صمتًا**
ويُعَدّ البناء ناجحًا خطأً (`exit 0`). يقترح هذا الـRFC **فصل قناتَي التشخيص**
(أخطاء قاتلة ≠ تحذيرات غير قاتلة) بحيث يفشل البناء عند وجود خطأ واحد، ولا يفشل قطّ
بسبب تحذير — منهيًا صنفًا كاملًا من «النجاح الكاذب».

# الدافع (Motivation)

المستخدم يتوقّع أنّ برنامجًا خاطئًا **لا يُترجَم**. اليوم قد يُترجَم ويُعلَن نجاحه.

مثال ملموس رُصِد أثناء تنفيذ P0 لتجربة محراب: برنامج يستدعي دالّة مضمَّنة بعدد وسائط
خاطئ:

```sad
اطبع(طول()) # طول تتطلّب وسيطًا واحدًا؛ هنا صفر
```

المفسّر يرفضه برسالة خطأ ودودة (نظام الأخطاء ناضج). أمّا المترجم فيبني الوحدة، ويدفع
الخطأ إلى `errors_`، ثمّ **يُرجع الوحدة غير الفارغة** فيمرّ الفحص `!sir_module_`
ويُنتِج ثنائيًّا و`exit 0`. النتيجة: **تباعد صامت بين المحرّكين** وثقة زائفة بالبناء.

الجذر البنيويّ (مؤكَّد في مصدر `dev`):

- `compiler/src/frontend/builders/statement_match.cpp:546–560` يبني نصّ
«`Warning: Non-exhaustive match on enum …`» ويدفعه إلى **`b_.errors_`** — أي أنّ
**تحذيرًا** يسكن وعاء **الأخطاء**.
- `compiler/src/frontend/sir_builder_module.cpp:1689` ينهي `buildModule` بـ
`return module_;` بصرف النظر عن محتوى `errors_` (يُرجِع `nullptr` فقط لبرنامج فارغ،
السطر 205).
- `tools/compiler/compiler_driver_analysis.cpp:541` يفحص `!sir_module_` فقط.

هذا الخلط يجعل الإصلاح الساذج (فشل البناء عند `hasErrors()`) **انحدارًا**: إذ سيحوّل
تحذير «المطابقة غير الشاملة» إلى فشل بناء. لذا يلزم فصلٌ بنيويّ، لا فحص أعمى.

> ملاحظة سياق: PR اللغة #155 طبّق **إصلاحًا آمنًا مؤقّتًا** — عند `!sir_module_` يُفرِّغ
> `getErrors()` بدل رسالة عامّة صمّاء — ووثّق هذا الحدّ صراحةً كـ«يستحقّ RFC». هذا هو ذاك الـRFC.

# الشرح التوجيهي (Guide-level explanation)

بعد التنفيذ، يميّز المترجم بين مستويَي خطورة:

- **خطأ (Error):** يمنع إنتاج ثنائيّ. البناء يفشل بحالة خروج غير صفريّة ويطبع السبب.
- **تحذير (Warning):** لا يمنع البناء. يُطبَع على `stderr` ويستمرّ البناء.

فالبرنامج `اطبع(طول())` يصير:

```
خطأ: الدالّة «طول» تتطلّب وسيطًا واحدًا، لكن مُرِّر صفر.
→ لم يُنتَج ثنائيّ. (حالة الخروج ≠ 0)
```

بينما مطابقة غير شاملة على تعداد:

```
تحذير: مطابقة غير شاملة على التعداد «لون» — ناقص: أزرق.
→ أُنتِج الثنائيّ. (تحذير لا يمنع البناء)
```

يوائم هذا سلوك المفسّر ويجعل «يُترجَم» تعني «صحيح بنيويًّا» فعلًا، لا مجرّد «لم ينهَر البانِي».

# الشرح المرجعي (Reference-level explanation)

## قناة تشخيص موسومة بالخطورة

يُستبدَل `std::vector<std::string> errors_` في `SIRBuilder` بقناة موسومة. الشكل المفضّل:

```cpp
enum class DiagSeverity { Error, Warning };
struct SIRDiagnostic {
DiagSeverity severity;
std::string message; // أو رمز كتالوج (انظر أدناه)
SourceLocation location; // موقع المصدر (إن توفّر)
};
std::vector<SIRDiagnostic> diagnostics_;
```

- `hasErrors()` تُعيد `true` فقط إن وُجِد تشخيص بخطورة `Error` (لا تحذير).
- تُضاف `warnings()`/`errors()` كمرشِّحات مشتقّة؛ يبقى `getErrors()` متوافقًا لكنّه يُرجِع
الأخطاء فقط (لا التحذيرات) — تغيير دلاليّ مقصود لمستهلكيه.
- **قرار `buildModule`:** الخيار الأنظف أن يبقى التوقيع كما هو ويُرجِع الوحدة، ويحكم
**السائق** على النجاح عبر `hasErrors()` (أخطاء فقط). بديلًا: يُرجِع `nullptr` عند وجود
خطأ — لكنّه يفقد التشخيص الجزئيّ ويصعّب الاستمرار لجمع أخطاء متعدّدة. **نوصي بالأوّل.**

## مواقع الاستدعاء (migration)

كلّ `errors_.push_back(...)` الحاليّة تُصنَّف صراحةً:

- `statement_match.cpp:48` «Failed to build match expression» ⇒ **Error**.
- `statement_match.cpp:560` «Non-exhaustive match …» ⇒ **Warning** (نقلة الخطورة الجوهريّة).
- `sir_builder_module.cpp:204` «Null program AST» ⇒ **Error** (يبقى `nullptr`).
- بقيّة المواقع تُراجَع فرادى (لا تحويل جماعيّ أعمى).

## السائق (`compiler_driver_analysis.cpp`)

بعد `buildModule`:

```cpp
for (const auto& w : sir_builder_->warnings())
diagnostics_.report_warning(w, file); // يُطبَع، لا يُفشِل
if (sir_builder_->hasErrors()) { // أخطاء فقط
for (const auto& e : sir_builder_->errors())
diagnostics_.report_error(e, file);
return false; // فشل حقيقيّ
}
// وإلّا: تابِع توليد الشيفرة حتّى لو وُجِدت تحذيرات
```

يُلغى فحص `!sir_module_` كبوّابة وحيدة (يبقى تحقّقًا دفاعيًّا من null فقط).

## الأنظمة المتشابكة

- **مصدر الحقيقة (`language-truth/`):** لا كلمات/أنواع جديدة. لكن رسائل SIR الحاليّة سلاسل
إنجليزيّة خام («Error: …», «Warning: …») تخالف مبدأ الكتالوج. **يُفضَّل** ربطها بكتالوج
الأخطاء (`language-truth/errors/`) برموز جديدة (خطأ لعدد الوسائط، تحذير للمطابقة غير
الشاملة) — يوائم [[0002-فرض-كل-الأخطاء-عبر-الكتالوج]] ونمط
`buildBilingualMessage`. يجوز فصله كعمل تابع كي لا يتضخّم هذا الـRFC.
- **Lexer/Parser/AST:** لا تغيير.
- **المفسّر:** لا تغيير (سلوكه صحيح أصلًا — هو المرجع).
- **المترجم (sadc):** جوهر التغيير (بانِي SIR + السائق). لا أثر على LLVM IR ذاته.
- **نظام الأخطاء:** إدخال مفهوم «الخطورة» في قناة SIR؛ ربط اختياريّ بالكتالوج.
- **الأدوات:** LSP قد يستهلك التحذيرات مستقبلًا كتشخيصات صفراء (تحسين تابع، خارج النطاق).
- **التوافق الخلفي:** برامج **صحيحة** لا تتأثّر. برامج **خاطئة تُترجَم اليوم خطأً**
ستفشل صوابًا (تصحيح مقصود، لا كسر). التحذيرات القائمة تبقى غير قاتلة (لا انحدار).

## اختبارات منع الانحدار

- حالة `@expect_error` للمترجم على `اطبع(طول())` — يجب أن يفشل البناء (يكشف الثغرة).
⚠️ فخّ معروف: harness الاختبار الحاليّ لـ`@expect_error` يتوقّف عند خطأ المفسّر ولا
يشغّل المترجم — يلزم توسيعه ليؤكّد فشل المترجم أيضًا، وإلّا يبقى التباعد خفيًّا.
- حالة إيجابيّة: برنامج بمطابقة غير شاملة **يُترجَم بنجاح** مع طبع التحذير (يمنع انحدار
تحويل التحذير إلى فشل).
- `tests/system/builtin_errors/` تبقى خضراء.

# السلبيات (Drawbacks)

- لمسة على كلّ مواقع `errors_.push_back` (تصنيف يدويّ) — جهد لمرّة واحدة.
- برامج كانت «تُبنى» ستتوقّف عن البناء؛ قد يفاجئ من اعتمد على السلوك الخاطئ (لكنّه خلل يُصحَّح).
- إدخال الخطورة يزيد سطح واجهة `SIRBuilder` قليلًا.

# المبرّرات والبدائل (Rationale and alternatives)

- **لماذا الفصل بالخطورة؟** يعالج الجذر (خلط القناتين) لا العَرَض، ويتجنّب انحدار
تحويل التحذيرات إلى أخطاء.
- **بديل: فشل على أيّ `errors_`.** مرفوض — يحوّل تحذير المطابقة غير الشاملة إلى فشل بناء.
- **بديل: `buildModule` يُرجِع `nullptr` عند خطأ.** مرفوض كخيار أساس — يفقد التشخيص الجزئيّ
ويعيق جمع أخطاء متعدّدة في مرور واحد.
- **أثر عدم الفعل:** يبقى «النجاح الصامت خطأً» وتباعد المحرّكين — تآكل ثقة جوهريّ في المترجم.

# أعمال سابقة (Prior art)

- **Rust/Clang/GCC:** تشخيصات موسومة بالخطورة (`error`/`warning`/`note`)؛ الأخطاء وحدها
تمنع الإنتاج، والتحذيرات قابلة للترقية (`-Werror`) اختياريًّا — نموذج مطابق لما نقترحه.
- **Go:** يرفض الشيفرة غير الصحيحة صراحةً ولا «ينجح صامتًا».
- الدرس: **قناة تشخيص واحدة موسومة بالخطورة** هي المعيار الصناعيّ؛ خلط القناتين شذوذ.

# توجّهات مستقبلية (Future possibilities)

- **ترقية التحذيرات إلى أخطاء** (نظير `-Werror`): علمٌ يجعل التحذيرات قاتلة للبناءات الصارمة.
- **ربط كامل بكتالوج الأخطاء:** تحويل كلّ رسائل SIR الخام إلى رموز كتالوج ثنائيّة اللغة بمواقع
مصدر — يوائم [[0002-فرض-كل-الأخطاء-عبر-الكتالوج]].
- **تشخيصات LSP الصفراء:** استهلاك قناة التحذيرات في خادم اللغة لعرضها كتسطير أصفر حيّ في محراب.
- **جمع أخطاء متعدّدة في مرور واحد:** الاستفادة من عدم إجهاض `buildModule` لعرض كلّ الأخطاء دفعةً.

# أسئلة غير محسومة (Unresolved questions)

- هل نربط رسائل SIR بكتالوج الأخطاء ضمن هذا الـRFC، أم كعمل تابع مباشرة بعده؟
- هل نضيف مبكّرًا مفهوم «ترقية التحذير إلى خطأ» (نظير `-Werror`) أم نؤجّله؟
- الصيغة الدقيقة لتوسيع harness `@expect_error` ليغطّي مسار المترجم (يُحسَم مع تنفيذ الاختبار).
Loading