Skip to content

Commit 90d8f1e

Browse files
committed
implemeted global const define and genric bug fix
1 parent 0f923b1 commit 90d8f1e

26 files changed

Lines changed: 837 additions & 54 deletions

File tree

crates/doo_analysis/src/semantic/exhaustiveness.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,7 @@ impl<'a> ExhaustivenessChecker<'a> {
198198
/// Check a single item.
199199
fn check_item(&mut self, item: &HirItem) {
200200
match item {
201+
HirItem::Const(_) => {}
201202
HirItem::Function(func) => self.check_function(func),
202203
HirItem::Struct(_) | HirItem::Enum(_) | HirItem::Import(_) | HirItem::Policy(_) | HirItem::Interface(_) => {}
203204
}

crates/doo_analysis/src/semantic/scope.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ pub enum SymbolKind {
4141
Import,
4242
/// Loop variable (for-in).
4343
LoopVar,
44+
/// Compile-time constant.
45+
Const,
4446
}
4547

4648
/// A single scope with its symbols.

crates/doo_analysis/src/semantic/type_check.rs

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ use doo_core::{
1313
Span,
1414
};
1515
use doo_hir::{
16-
HirBinOp, HirExpr, HirExprKind, HirFunction, HirItem, HirMatchPattern, HirProgram, HirStmt,
17-
HirStmtKind,
16+
ConstValue, HirBinOp, HirExpr, HirExprKind, HirFunction, HirItem, HirMatchPattern,
17+
HirProgram, HirStmt, HirStmtKind,
1818
};
1919

2020
/// Type checking error.
@@ -198,6 +198,38 @@ impl TypeChecker {
198198
self.scopes.enter_scope(super::scope::ScopeKind::Global);
199199
for item in &program.items {
200200
match item {
201+
HirItem::Const(c) => {
202+
// Detect duplicate const names
203+
if self.scopes.lookup(&c.name).is_some() {
204+
self.errors.push(TypeError {
205+
kind: TypeErrorKind::InvalidOp(format!(
206+
"constant '{}' is already defined",
207+
c.name
208+
)),
209+
span: c.span,
210+
});
211+
} else {
212+
// Validate that the value is a compile-time constant expression
213+
if !is_const_evaluable(&c.value_expr) {
214+
self.errors.push(TypeError {
215+
kind: TypeErrorKind::InvalidOp(format!(
216+
"const '{}' must be assigned a compile-time constant value \
217+
(literal, const arithmetic, array/map of literals)",
218+
c.name
219+
)),
220+
span: c.span,
221+
});
222+
}
223+
self.define_symbol(Symbol {
224+
name: c.name.clone(),
225+
kind: SymbolKind::Const,
226+
type_id: Some(c.type_id),
227+
mutable: false,
228+
span: c.span,
229+
used: false,
230+
});
231+
}
232+
}
201233
HirItem::Function(func) => {
202234
let return_type = func.return_type.unwrap_or(builtin::VOID);
203235

@@ -2356,3 +2388,27 @@ impl TypeChecker {
23562388
expected_type
23572389
}
23582390
}
2391+
2392+
/// Check whether a HIR expression is a valid compile-time constant expression.
2393+
///
2394+
/// Allowed:
2395+
/// - Literal values (Int, Float, Bool, Str, Nil)
2396+
/// - Negation of a literal
2397+
/// - Arithmetic/comparison of two const-evaluable expressions
2398+
/// - Arrays where all elements are const-evaluable
2399+
/// - Maps where all keys and values are const-evaluable
2400+
/// - Const references (already inlined by HIR lowering, appear as Const nodes)
2401+
fn is_const_evaluable(expr: &HirExpr) -> bool {
2402+
match &expr.kind {
2403+
HirExprKind::Const(_) => true,
2404+
HirExprKind::UnaryOp { operand, .. } => is_const_evaluable(operand),
2405+
HirExprKind::BinOp { lhs, rhs, .. } => {
2406+
is_const_evaluable(lhs) && is_const_evaluable(rhs)
2407+
}
2408+
HirExprKind::Array(elements) => elements.iter().all(is_const_evaluable),
2409+
HirExprKind::Map(pairs) => pairs
2410+
.iter()
2411+
.all(|(k, v)| is_const_evaluable(k) && is_const_evaluable(v)),
2412+
_ => false,
2413+
}
2414+
}

crates/doo_codegen/src/builder.rs

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use doo_core::constants::ffi_names::{self, derive_ffi_symbol};
99
use doo_core::doo_debug;
1010
use doo_core::types::{builtin, TypeKind, TypeRegistry};
1111
use doo_mir::sym::resolve;
12-
use doo_mir::{MirConst, MirFunction, MirOperand, MirProgram, MirTerminator};
12+
use doo_mir::{MirConst, MirFunction, MirGlobal, MirOperand, MirProgram, MirTerminator};
1313
use inkwell::basic_block::BasicBlock;
1414
use inkwell::context::Context;
1515
use inkwell::module::{Linkage, Module};
@@ -458,6 +458,11 @@ impl<'ctx> CodegenBuilder<'ctx> {
458458
}
459459
}
460460

461+
// Emit LLVM global constants from MirGlobal entries (primitive consts)
462+
for global in &mir.globals {
463+
self.emit_global(&mut ctx, global);
464+
}
465+
461466
// First pass: declare all functions
462467
for func in &mir.functions {
463468
self.declare_function(&mut ctx, func);
@@ -471,6 +476,57 @@ impl<'ctx> CodegenBuilder<'ctx> {
471476
ctx.module
472477
}
473478

479+
/// Emit a single LLVM global constant from a `MirGlobal`.
480+
///
481+
/// Primitive consts (Int, Float, Bool, Str) become LLVM global constants with
482+
/// internal linkage. The value is stored in the context locals map so that
483+
/// `operand_to_value` can resolve `MirOperand::Global(name)` if needed.
484+
fn emit_global(&self, ctx: &mut CodegenContext<'ctx>, global: &MirGlobal) {
485+
use inkwell::module::Linkage;
486+
487+
let name = resolve(global.name);
488+
let value = match &global.value {
489+
Some(v) => v,
490+
None => return,
491+
};
492+
493+
match value {
494+
MirConst::Int(v) => {
495+
// Store the i64 constant value directly as a temp — no LLVM global needed
496+
// since constants are inlined. The global is for cross-module visibility.
497+
let ty = ctx.context.i64_type();
498+
let g = ctx.module.add_global(ty, None, &name);
499+
g.set_initializer(&ty.const_int(*v as u64, true));
500+
g.set_constant(true);
501+
g.set_linkage(Linkage::Internal);
502+
// Also cache the raw i64 value so operand_to_value returns it directly
503+
ctx.set_temp(&name, ty.const_int(*v as u64, true).into());
504+
}
505+
MirConst::Float(v) => {
506+
let ty = ctx.context.f64_type();
507+
let g = ctx.module.add_global(ty, None, &name);
508+
g.set_initializer(&ty.const_float(*v));
509+
g.set_constant(true);
510+
g.set_linkage(Linkage::Internal);
511+
ctx.set_temp(&name, ty.const_float(*v).into());
512+
}
513+
MirConst::Bool(v) => {
514+
let ty = ctx.context.i8_type();
515+
let g = ctx.module.add_global(ty, None, &name);
516+
g.set_initializer(&ty.const_int(*v as u64, false));
517+
g.set_constant(true);
518+
g.set_linkage(Linkage::Internal);
519+
ctx.set_temp(&name, ty.const_int(*v as u64, false).into());
520+
}
521+
MirConst::Str(s) => {
522+
if let Ok(g) = ctx.builder.build_global_string_ptr(s, &name) {
523+
ctx.set_temp(&name, g.as_pointer_value().into());
524+
}
525+
}
526+
MirConst::Nil => {}
527+
}
528+
}
529+
474530
/// Pre-declare all struct types from the type registry.
475531
/// This ensures struct types are cached before we try to access their fields.
476532
fn declare_struct_types(&self, ctx: &mut CodegenContext<'ctx>) {

crates/doo_core/src/errors/codes.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ pub enum ErrorCode {
4545
ExpectedExprAfterOp, // E0023
4646
InvalidMatchSyntax, // E0024
4747
InvalidForSyntax, // E0025
48+
InvalidConstExpr, // E0026 — const must be a compile-time literal expression
4849

4950
// === Type Errors (E0100-E0199) ===
5051
TypeMismatch, // E0100
@@ -105,6 +106,7 @@ pub enum ErrorCode {
105106
ContinueOutsideLoop, // E0419
106107
ReturnOutsideFunction, // E0420
107108
DuplicateMethod, // E0421 — duplicate method in interface
109+
DuplicateConst, // E0422 — duplicate const declaration
108110

109111
// === Import Errors (E0500-E0599) ===
110112
ModuleNotFound, // E0500
@@ -177,6 +179,7 @@ impl ErrorCode {
177179
Self::ExpectedExprAfterOp => "E0023",
178180
Self::InvalidMatchSyntax => "E0024",
179181
Self::InvalidForSyntax => "E0025",
182+
Self::InvalidConstExpr => "E0026",
180183

181184
Self::TypeMismatch => "E0100",
182185
Self::UnknownType => "E0101",
@@ -232,6 +235,7 @@ impl ErrorCode {
232235
Self::ContinueOutsideLoop => "E0419",
233236
Self::ReturnOutsideFunction => "E0420",
234237
Self::DuplicateMethod => "E0421",
238+
Self::DuplicateConst => "E0422",
235239

236240
Self::ModuleNotFound => "E0500",
237241
Self::ImportNotFound => "E0501",
@@ -299,6 +303,7 @@ impl ErrorCode {
299303
"E0023" => Some(Self::ExpectedExprAfterOp),
300304
"E0024" => Some(Self::InvalidMatchSyntax),
301305
"E0025" => Some(Self::InvalidForSyntax),
306+
"E0026" => Some(Self::InvalidConstExpr),
302307

303308
"E0100" => Some(Self::TypeMismatch),
304309
"E0101" => Some(Self::UnknownType),
@@ -354,6 +359,7 @@ impl ErrorCode {
354359
"E0419" => Some(Self::ContinueOutsideLoop),
355360
"E0420" => Some(Self::ReturnOutsideFunction),
356361
"E0421" => Some(Self::DuplicateMethod),
362+
"E0422" => Some(Self::DuplicateConst),
357363

358364
"E0500" => Some(Self::ModuleNotFound),
359365
"E0501" => Some(Self::ImportNotFound),
@@ -423,6 +429,7 @@ impl ErrorCode {
423429
Self::ExpectedExprAfterOp => "EXPECTED EXPRESSION",
424430
Self::InvalidMatchSyntax => "INVALID MATCH",
425431
Self::InvalidForSyntax => "INVALID FOR",
432+
Self::InvalidConstExpr => "INVALID CONST EXPR",
426433

427434
Self::TypeMismatch => "TYPE MISMATCH",
428435
Self::UnknownType => "UNKNOWN TYPE",
@@ -460,6 +467,7 @@ impl ErrorCode {
460467
Self::DuplicateField => "DUPLICATE FIELD",
461468
Self::DuplicateVariant => "DUPLICATE VARIANT",
462469
Self::DuplicateMethod => "DUPLICATE METHOD",
470+
Self::DuplicateConst => "DUPLICATE CONST",
463471
Self::InvalidSignature => "INVALID SIGNATURE",
464472
Self::MissingReturn => "MISSING RETURN",
465473
Self::UnreachableCode => "UNREACHABLE CODE",

crates/doo_driver/src/compile.rs

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -329,19 +329,29 @@ pub fn compile_project(opts: CompileOptions) -> Result<CompileResult, String> {
329329
doo_frontend::ast::Item::Enum(e) => doo_debug!("DEBUG", " Enum: {}", e.name),
330330
doo_frontend::ast::Item::Import(i) => doo_debug!("DEBUG", " Import: {:?}", i.path),
331331
doo_frontend::ast::Item::Statement(_) => doo_debug!("DEBUG", " Statement"),
332-
doo_frontend::ast::Item::Policy(p) => doo_debug!("DEBUG", " Policy for {}", p.for_struct),
333-
doo_frontend::ast::Item::Interface(i) => doo_debug!("DEBUG", " Interface: {}", i.name),
332+
doo_frontend::ast::Item::Const(c) => doo_debug!("DEBUG", " Const: {}", c.name),
333+
doo_frontend::ast::Item::Policy(p) => {
334+
doo_debug!("DEBUG", " Policy for {}", p.for_struct)
335+
}
336+
doo_frontend::ast::Item::Interface(i) => {
337+
doo_debug!("DEBUG", " Interface: {}", i.name)
338+
}
334339
}
335340
}
336341
doo_debug!("DEBUG", "HIR items: {}", hir.items.len());
337342
for item in &hir.items {
338343
match item {
344+
doo_hir::HirItem::Const(c) => doo_debug!("DEBUG", " HIR Const: {}", c.name),
339345
doo_hir::HirItem::Function(f) => doo_debug!("DEBUG", " HIR Function: {}", f.name),
340346
doo_hir::HirItem::Struct(s) => doo_debug!("DEBUG", " HIR Struct: {}", s.name),
341347
doo_hir::HirItem::Enum(e) => doo_debug!("DEBUG", " HIR Enum: {}", e.name),
342348
doo_hir::HirItem::Import(_) => doo_debug!("DEBUG", " HIR Import"),
343-
doo_hir::HirItem::Policy(p) => doo_debug!("DEBUG", " HIR Policy for {}", p.for_struct),
344-
doo_hir::HirItem::Interface(i) => doo_debug!("DEBUG", " HIR Interface: {}", i.name),
349+
doo_hir::HirItem::Policy(p) => {
350+
doo_debug!("DEBUG", " HIR Policy for {}", p.for_struct)
351+
}
352+
doo_hir::HirItem::Interface(i) => {
353+
doo_debug!("DEBUG", " HIR Interface: {}", i.name)
354+
}
345355
}
346356
}
347357
}

crates/doo_driver/src/loader.rs

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -693,6 +693,22 @@ pub fn resolve_imports(
693693
result.items.push(item.clone());
694694
}
695695
}
696+
Item::Const(c) => {
697+
let is_public = c
698+
.name
699+
.chars()
700+
.next()
701+
.map(|ch| ch.is_uppercase())
702+
.unwrap_or(false);
703+
let is_wanted = import_all || requested.contains_key(&c.name);
704+
if is_public && is_wanted && !imported_names.contains(&c.name) {
705+
if debug {
706+
doo_debug!("LOADER", " Importing const: {}", c.name);
707+
}
708+
imported_names.insert(c.name.clone());
709+
result.items.push(item.clone());
710+
}
711+
}
696712
Item::Import(_) | Item::Statement(_) | Item::Policy(_) | Item::Interface(_) => {
697713
// Don't re-export
698714
}
@@ -1104,6 +1120,21 @@ pub fn resolve_imports(
11041120
result.items.push(item.clone());
11051121
}
11061122
}
1123+
Item::Const(c) => {
1124+
let is_public = c
1125+
.name
1126+
.chars()
1127+
.next()
1128+
.map(|ch| ch.is_uppercase())
1129+
.unwrap_or(false);
1130+
if is_public && !imported_names.contains(&c.name) {
1131+
if debug {
1132+
doo_debug!("LOADER", " Importing local const: {}", c.name);
1133+
}
1134+
imported_names.insert(c.name.clone());
1135+
result.items.push(item.clone());
1136+
}
1137+
}
11071138
Item::Import(_) | Item::Statement(_) | Item::Policy(_) | Item::Interface(_) => {
11081139
// Don't re-export
11091140
}
@@ -1258,7 +1289,24 @@ pub fn resolve_imports(
12581289
result.items.push(item.clone());
12591290
}
12601291
}
1261-
Item::Import(_) | Item::Statement(_) | Item::Policy(_) | Item::Interface(_) => {}
1292+
Item::Const(c) => {
1293+
let is_public = c
1294+
.name
1295+
.chars()
1296+
.next()
1297+
.map(|ch| ch.is_uppercase())
1298+
.unwrap_or(false);
1299+
let is_wanted = import_all || requested.contains_key(&c.name);
1300+
if is_public && is_wanted && !imported_names.contains(&c.name) {
1301+
if debug {
1302+
doo_debug!("LOADER", " Importing nested-std const: {}", c.name);
1303+
}
1304+
imported_names.insert(c.name.clone());
1305+
result.items.push(item.clone());
1306+
}
1307+
}
1308+
Item::Import(_) | Item::Statement(_) | Item::Policy(_) | Item::Interface(_) => {
1309+
}
12621310
}
12631311
}
12641312
}

crates/doo_frontend/src/ast/decl.rs

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,35 @@
11
//! Declaration AST nodes.
22
//!
3-
//! Top-level declarations: functions, structs, enums, imports.
3+
//! Top-level declarations: functions, structs, enums, imports, consts.
44
5-
use super::{Stmt, TypeExpr};
5+
use super::{Expr, Stmt, TypeExpr};
66
use doo_core::Span;
77

8+
// ============================================================================
9+
// Const Declaration
10+
// ============================================================================
11+
12+
/// A compile-time constant declaration: `const Name = expr`
13+
///
14+
/// - PascalCase name → public const (accessible via import)
15+
/// - camelCase name → private const (module-internal)
16+
/// - Value must be a compile-time literal expression (no function calls, no structs)
17+
/// - Can hold: primitives (Int, Float, Bool, Str), arrays of primitives, maps of primitives
18+
#[derive(Debug, Clone)]
19+
pub struct ConstDecl {
20+
pub name: String,
21+
pub is_public: bool,
22+
pub value: Expr,
23+
pub span: Span,
24+
}
25+
26+
impl ConstDecl {
27+
pub fn new(name: String, value: Expr, span: Span) -> Self {
28+
let is_public = name.chars().next().map(|c| c.is_uppercase()).unwrap_or(false);
29+
Self { name, is_public, value, span }
30+
}
31+
}
32+
833
// ============================================================================
934
// Generic Type Parameters
1035
// ============================================================================

crates/doo_frontend/src/ast/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ impl Program {
3535
/// Top-level items in a program.
3636
#[derive(Debug, Clone)]
3737
pub enum Item {
38+
/// Compile-time constant declaration
39+
Const(ConstDecl),
3840
/// Function declaration
3941
Function(FunctionDecl),
4042
/// Struct declaration
@@ -54,6 +56,7 @@ pub enum Item {
5456
impl Item {
5557
pub fn span(&self) -> Span {
5658
match self {
59+
Self::Const(c) => c.span,
5760
Self::Function(f) => f.span,
5861
Self::Struct(s) => s.span,
5962
Self::Enum(e) => e.span,

0 commit comments

Comments
 (0)