Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
1acaa21
Initial plan
Copilot Sep 21, 2025
767e437
Implement TypeCheckModule trait and update BinOp, UnOp, TypeOp implem…
Copilot Sep 21, 2025
05fbd42
Complete TypeCheckModule implementation for shorthand operations
Copilot Sep 21, 2025
eefbef1
Add type checking phase to compiler and implement TypeCheckModule for…
Copilot Sep 21, 2025
730867b
Fix clippy errors and warnings
Copilot Sep 26, 2025
6106b4e
Fix recursive type checking in nested syntax modules
Copilot Sep 26, 2025
3e8ade4
Merge branch 'staging' into copilot/fix-0c2171cd-1327-4a32-bc40-ad53e…
Ph0enixKM Sep 26, 2025
206876a
Address minor issues: move type checks, remove comments, fix imports,…
Copilot Sep 26, 2025
d0e962c
Simplify match statement in fail.rs and move comments to top in test …
Copilot Sep 26, 2025
166ab3c
Fix missing recursive typecheck calls in binop, unop, and typeop oper…
Copilot Sep 26, 2025
065ba34
Move type assignments from parse phase to typecheck phase
Copilot Sep 26, 2025
17bc1c4
Move handle_variable_reference calls to typecheck phase and fix array…
Copilot Sep 26, 2025
f88923a
Store token positions in module structures and remove moved-code comm…
Copilot Sep 26, 2025
fb79fe8
Remove unnecessary Option::is_some() checks in typecheck implementations
Copilot Sep 26, 2025
5873635
Merge branch 'staging' into copilot/fix-0c2171cd-1327-4a32-bc40-ad53e…
Ph0enixKM Sep 26, 2025
3963c7b
Fix variable reference handling by moving VariableGet logic to typech…
Copilot Sep 26, 2025
c1061be
Fix compilation errors in VariableGet by adding missing tok field
Copilot Sep 27, 2025
76f1cd3
Minor formatting
lens0021 Oct 8, 2025
eafc2b4
Print the array type state in an error message
lens0021 Oct 8, 2025
49992fa
fix: update types after typecheck
Ph0enixKM Oct 22, 2025
d2e614d
Merge commit 'refs/pull/770/head' of https://github.com/amber-lang/am…
Ph0enixKM Oct 22, 2025
31f7974
feat: refactor functions
Ph0enixKM Oct 26, 2025
2adc11c
feat: fix variables and arrays
Ph0enixKM Oct 26, 2025
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
5 changes: 0 additions & 5 deletions debug_test.ab

This file was deleted.

28 changes: 27 additions & 1 deletion src/compiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use crate::built_info;
use crate::docs::module::DocumentationModule;
use crate::modules::block::Block;
use crate::modules::prelude::{BlockFragment, FragmentRenderable};
use crate::modules::typecheck::TypeCheckModule;
use crate::optimizer::optimize_fragments;
use crate::translate::check_all_blocks;
use crate::translate::module::TranslateModule;
Expand Down Expand Up @@ -300,9 +301,33 @@ impl AmberCompiler {
}
}

pub fn typecheck(&self, mut block: Block, mut meta: ParserMetadata) -> Result<(Block, ParserMetadata), Message> {
let time = Instant::now();

// Initialize global scope for typechecking
meta.context.scopes.push(crate::utils::context::ScopeUnit::new());

// Perform type checking on the block
if let Err(failure) = block.typecheck(&mut meta) {
return Err(failure.unwrap_loud());
}

if Self::env_flag_set(AMBER_DEBUG_TIME) {
let pathname = self.path.clone().unwrap_or(String::from("unknown"));
println!(
"[{}]\tin\t{}ms\t{pathname}",
"Typecheck".green(),
time.elapsed().as_millis()
);
}

Ok((block, meta))
}

pub fn compile(&self) -> Result<(Vec<Message>, String), Message> {
let tokens = self.tokenize()?;
let (block, meta) = self.parse(tokens)?;
let (block, meta) = self.typecheck(block, meta)?;
let messages = meta.messages.clone();
let code = self.translate(block, meta)?;
Ok((messages, code))
Expand All @@ -326,7 +351,8 @@ impl AmberCompiler {

pub fn generate_docs(&self, output: Option<String>, usage: bool) -> Result<(), Message> {
let tokens = self.tokenize()?;
let (block, mut meta) = self.parse(tokens)?;
let (block, meta) = self.parse(tokens)?;
let (block, mut meta) = self.typecheck(block, meta)?;
meta.doc_usage = usage;
self.document(block, meta, output);
Ok(())
Expand Down
12 changes: 12 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,18 @@ mod translate;
mod utils;
mod optimizer;

/// Macro to implement a no-op TypeCheckModule for modules that don't need type checking
#[macro_export]
macro_rules! impl_noop_typecheck {
($type:ty) => {
impl $crate::modules::typecheck::TypeCheckModule for $type {
fn typecheck(&mut self, _meta: &mut $crate::utils::ParserMetadata) -> heraclitus_compiler::prelude::SyntaxResult {
Ok(())
}
}
};
}

pub mod built_info {
include!(concat!(env!("OUT_DIR"), "/built.rs"));
}
Expand Down
56 changes: 33 additions & 23 deletions src/modules/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,31 +63,41 @@ impl SyntaxModule<ParserMetadata> for Block {
false
};

meta.with_push_scope(|meta| {
while meta.get_current_token().is_some() {
// Handle the end of line
if token(meta, "\n").is_ok() {
continue;
}
// Handle block end
if !is_single_line && self.parses_syntax && token(meta, "}").is_ok() {
break;
}
let mut statement = Statement::new();
if let Err(failure) = statement.parse(meta) {
return match failure {
Failure::Quiet(pos) => error_pos!(meta, pos, "Unexpected token"),
Failure::Loud(err) => return Err(Failure::Loud(err))
}
}
self.statements.push(statement);
// Handle the semicolon
token(meta, ";").ok();
// Handle single line
if is_single_line {
break;
while meta.get_current_token().is_some() {
// Handle the end of line
if token(meta, "\n").is_ok() {
continue;
}
// Handle block end
if !is_single_line && self.parses_syntax && token(meta, "}").is_ok() {
break;
}
let mut statement = Statement::new();
if let Err(failure) = statement.parse(meta) {
return match failure {
Failure::Quiet(pos) => error_pos!(meta, pos, "Unexpected token"),
Failure::Loud(err) => return Err(Failure::Loud(err))
}
}
self.statements.push(statement);
// Handle the semicolon
token(meta, ";").ok();
// Handle single line
if is_single_line {
break;
}
}
Ok(())
}
}

impl TypeCheckModule for Block {
fn typecheck(&mut self, meta: &mut ParserMetadata) -> SyntaxResult {
meta.with_push_scope(self.parses_syntax, |meta| {
// Type check all statements in the block
for statement in &mut self.statements {
statement.typecheck(meta)?;
}
Ok(())
})
}
Expand Down
23 changes: 17 additions & 6 deletions src/modules/builtin/cd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,23 @@ impl SyntaxModule<ParserMetadata> for Cd {
fn parse(&mut self, meta: &mut ParserMetadata) -> SyntaxResult {
token(meta, "cd")?;
syntax(meta, &mut self.value)?;
Ok(())
}
}

impl TranslateModule for Cd {
fn translate(&self, meta: &mut TranslateMetadata) -> FragmentKind {
fragments!("cd ", self.value.translate(meta), " || exit")
}
}


impl TypeCheckModule for Cd {
fn typecheck(&mut self, meta: &mut ParserMetadata) -> SyntaxResult {
// First, type-check the nested expression
self.value.typecheck(meta)?;

// Then check if it's the correct type
let path_type = self.value.get_type();
if path_type != Type::Text {
let position = self.value.get_position(meta);
Expand All @@ -31,12 +48,6 @@ impl SyntaxModule<ParserMetadata> for Cd {
}
}

impl TranslateModule for Cd {
fn translate(&self, meta: &mut TranslateMetadata) -> FragmentKind {
fragments!("cd ", self.value.translate(meta), " || exit")
}
}

impl DocumentationModule for Cd {
fn document(&self, _meta: &ParserMetadata) -> String {
"".to_string()
Expand Down
7 changes: 7 additions & 0 deletions src/modules/builtin/echo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,13 @@ impl TranslateModule for Echo {
}
}


impl TypeCheckModule for Echo {
fn typecheck(&mut self, meta: &mut ParserMetadata) -> SyntaxResult {
self.value.typecheck(meta)
}
}

impl DocumentationModule for Echo {
fn document(&self, _meta: &ParserMetadata) -> String {
"".to_string()
Expand Down
31 changes: 20 additions & 11 deletions src/modules/builtin/exit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,6 @@ impl SyntaxModule<ParserMetadata> for Exit {
self.code = Some(code_expr);
}

if let Some(ref code_expr) = self.code {
let code_type = code_expr.get_type();
if code_type != Type::Int {
let position = code_expr.get_position(meta);
return error_pos!(meta, position => {
message: "Builtin function `exit` can only be used with values of type Int",
comment: format!("Given type: {}, expected type: {}", code_type, Type::Int)
});
}
}

Ok(())
}
}
Expand All @@ -48,6 +37,26 @@ impl TranslateModule for Exit {
}
}

impl TypeCheckModule for Exit {
fn typecheck(&mut self, meta: &mut ParserMetadata) -> SyntaxResult {
if let Some(ref mut code_expr) = self.code {
// First type-check the nested expression
code_expr.typecheck(meta)?;

// Then check if it's the correct type
let code_type = code_expr.get_type();
if code_type != Type::Int {
let position = code_expr.get_position(meta);
return error_pos!(meta, position => {
message: "Builtin function `exit` can only be used with values of type Int",
comment: format!("Given type: {}, expected type: {}", code_type, Type::Int)
});
}
}
Ok(())
}
}

impl DocumentationModule for Exit {
fn document(&self, _meta: &ParserMetadata) -> String {
"".to_string()
Expand Down
12 changes: 11 additions & 1 deletion src/modules/builtin/len.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use crate::modules::expression::expr::Expr;
use crate::modules::expression::unop::UnOp;
use crate::modules::prelude::*;
use crate::modules::types::{Type, Typed};
use crate::modules::typecheck::TypeCheckModule;
use crate::translate::module::TranslateModule;
use crate::utils::{ParserMetadata, TranslateMetadata};
use heraclitus_compiler::prelude::*;
Expand Down Expand Up @@ -37,7 +38,16 @@ impl SyntaxModule<ParserMetadata> for Len {
}
}

fn parse(&mut self, meta: &mut ParserMetadata) -> SyntaxResult {
fn parse(&mut self, _meta: &mut ParserMetadata) -> SyntaxResult {
Ok(())
}
}

impl TypeCheckModule for Len {
fn typecheck(&mut self, meta: &mut ParserMetadata) -> SyntaxResult {
// Typecheck the expression first
self.value.typecheck(meta)?;

if !matches!(self.value.get_type(), Type::Text | Type::Array(_)) {
let msg = self
.value
Expand Down
3 changes: 3 additions & 0 deletions src/modules/builtin/lines.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ impl LinesInvocation {
}
}


impl_noop_typecheck!(LinesInvocation);

impl DocumentationModule for LinesInvocation {
fn document(&self, _meta: &ParserMetadata) -> String {
"".to_string()
Expand Down
47 changes: 31 additions & 16 deletions src/modules/builtin/mv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,23 +33,7 @@ impl SyntaxModule<ParserMetadata> for Mv {
self.modifier.use_modifiers(meta, |_this, meta| {
token(meta, "mv")?;
syntax(meta, &mut *self.source)?;
let mut path_type = self.source.get_type();
if path_type != Type::Text {
let position = self.source.get_position(meta);
return error_pos!(meta, position => {
message: "Builtin function `mv` can only be used with values of type Text",
comment: format!("Given type: {}, expected type: {}", path_type, Type::Text)
});
}
syntax(meta, &mut *self.destination)?;
path_type = self.destination.get_type();
if path_type != Type::Text {
let position = self.destination.get_position(meta);
return error_pos!(meta, position => {
message: "Builtin function `mv` can only be used with values of type Text",
comment: format!("Given type: {}, expected type: {}", path_type, Type::Text)
});
}
syntax(meta, &mut self.failed)?;
Ok(())
})
Expand All @@ -72,6 +56,37 @@ impl TranslateModule for Mv {
}
}


impl TypeCheckModule for Mv {
fn typecheck(&mut self, meta: &mut ParserMetadata) -> SyntaxResult {
// Type-check nested expressions first
self.source.typecheck(meta)?;
self.destination.typecheck(meta)?;
self.failed.typecheck(meta)?;

// Then check if they're the correct types
let source_type = self.source.get_type();
if source_type != Type::Text {
let position = self.source.get_position(meta);
return error_pos!(meta, position => {
message: "Builtin function `mv` can only be used with values of type Text",
comment: format!("Given type: {}, expected type: {}", source_type, Type::Text)
});
}

let dest_type = self.destination.get_type();
if dest_type != Type::Text {
let position = self.destination.get_position(meta);
return error_pos!(meta, position => {
message: "Builtin function `mv` can only be used with values of type Text",
comment: format!("Given type: {}, expected type: {}", dest_type, Type::Text)
});
}

Ok(())
}
}

impl DocumentationModule for Mv {
fn document(&self, _meta: &ParserMetadata) -> String {
"".to_string()
Expand Down
3 changes: 3 additions & 0 deletions src/modules/builtin/nameof.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ impl TranslateModule for Nameof {
}
}


impl_noop_typecheck!(Nameof);

impl DocumentationModule for Nameof {
fn document(&self, _meta: &ParserMetadata) -> String {
"".to_string()
Expand Down
15 changes: 15 additions & 0 deletions src/modules/command/cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,21 @@ impl TranslateModule for Command {
}
}


impl TypeCheckModule for Command {
fn typecheck(&mut self, meta: &mut ParserMetadata) -> SyntaxResult {
// Type-check all interpolated expressions
for interp in &mut self.interps {
interp.typecheck(meta)?;
}

// Type-check the failed block
self.failed.typecheck(meta)?;

Ok(())
}
}

impl DocumentationModule for Command {
fn document(&self, _meta: &ParserMetadata) -> String {
"".to_string()
Expand Down
3 changes: 3 additions & 0 deletions src/modules/command/modifier.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,9 @@ impl TranslateModule for CommandModifier {
}
}


impl_noop_typecheck!(CommandModifier);

impl DocumentationModule for CommandModifier {
fn document(&self, _meta: &ParserMetadata) -> String {
"".to_string()
Expand Down
7 changes: 7 additions & 0 deletions src/modules/condition/failed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,3 +132,10 @@ impl TranslateModule for Failed {
}
}
}

impl TypeCheckModule for Failed {
fn typecheck(&mut self, meta: &mut ParserMetadata) -> SyntaxResult {
// Type-check the failed block
self.block.typecheck(meta)
}
}
Loading