Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 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
a0e43a5
fix: loops
Ph0enixKM Nov 16, 2025
3f83793
feat: finish typecheck transition
Ph0enixKM Nov 17, 2025
e2bffa2
merge: resolve conflicts
Ph0enixKM Nov 17, 2025
95a1d3e
feat: resolve clippy errors
Ph0enixKM Nov 17, 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.

25 changes: 24 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,30 @@ impl AmberCompiler {
}
}

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

// 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 +348,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
57 changes: 34 additions & 23 deletions src/modules/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,31 +63,42 @@ 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 {
let no_scope_exists = meta.context.scopes.is_empty();
meta.with_push_scope(self.parses_syntax || no_scope_exists, |meta| {
// Type check all statements in the block
for statement in &mut self.statements {
statement.typecheck(meta)?;
}
Ok(())
})
}
Expand Down
10 changes: 10 additions & 0 deletions src/modules/builtin/cd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,16 @@ impl SyntaxModule<ParserMetadata> for Cd {
fn parse(&mut self, meta: &mut ParserMetadata) -> SyntaxResult {
token(meta, "cd")?;
syntax(meta, &mut self.value)?;
Ok(())
}
}

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 Down
6 changes: 6 additions & 0 deletions src/modules/builtin/echo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ impl SyntaxModule<ParserMetadata> for Echo {
}
}

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

impl TranslateModule for Echo {
fn translate(&self, meta: &mut TranslateMetadata) -> FragmentKind {
fragments!("echo ", self.value.translate(meta))
Expand Down
11 changes: 9 additions & 2 deletions src/modules/builtin/exit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,15 @@ impl SyntaxModule<ParserMetadata> for Exit {
self.code = Some(code_expr);
}

if let Some(ref code_expr) = self.code {
Ok(())
}
}

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

let code_type = code_expr.get_type();
if code_type != Type::Int {
let position = code_expr.get_position(meta);
Expand All @@ -34,7 +42,6 @@ impl SyntaxModule<ParserMetadata> for Exit {
});
}
}

Ok(())
}
}
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
27 changes: 19 additions & 8 deletions src/modules/builtin/lines.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,22 +30,33 @@ impl SyntaxModule<ParserMetadata> for LinesInvocation {
fn parse(&mut self, meta: &mut ParserMetadata) -> SyntaxResult {
token(meta, "lines")?;
token(meta, "(")?;
let tok = meta.get_current_token();
let mut path = Expr::new();
syntax(meta, &mut path)?;
token(meta, ")")?;
if path.get_type() != Type::Text {
let msg = format!(
"Expected value of type 'Text' but got '{}'",
path.get_type()
);
return error!(meta, tok, msg);
}
self.path = Box::new(Some(path));
Ok(())
}
}

impl TypeCheckModule for LinesInvocation {
fn typecheck(&mut self, meta: &mut ParserMetadata) -> SyntaxResult {
if let Some(path) = &mut *self.path {
path.typecheck(meta)?;
if path.get_type() != Type::Text {
let msg = format!(
"Expected value of type 'Text' but got '{}'",
path.get_type()
);
let pos = path.get_position(meta);
return error_pos!(meta, pos, msg);
}
Ok(())
} else {
unreachable!()
}
}
}

impl TranslateModule for LinesInvocation {
fn translate(&self, meta: &mut TranslateMetadata) -> FragmentKind {
let temp = format!("__AMBER_LINE_{}", meta.gen_value_id());
Expand Down
58 changes: 35 additions & 23 deletions src/modules/builtin/mv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::mem::swap;

use crate::fragments;
use crate::modules::command::modifier::CommandModifier;
use crate::modules::condition::failed::Failed;
use crate::modules::condition::failure_handler::FailureHandler;
use crate::modules::expression::expr::Expr;
use crate::modules::prelude::*;
use crate::modules::types::{Type, Typed};
Expand All @@ -13,7 +13,7 @@ pub struct Mv {
source: Box<Expr>,
destination: Box<Expr>,
modifier: CommandModifier,
failed: Failed,
failure_handler: FailureHandler,
}

impl SyntaxModule<ParserMetadata> for Mv {
Expand All @@ -23,8 +23,8 @@ impl SyntaxModule<ParserMetadata> for Mv {
Mv {
source: Box::new(Expr::new()),
destination: Box::new(Expr::new()),
failed: Failed::new(),
modifier: CommandModifier::new().parse_expr(),
failure_handler: FailureHandler::new(),
modifier: CommandModifier::new_expr(),
}
}

Expand All @@ -33,41 +33,53 @@ 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)?;
syntax(meta, &mut self.failure_handler)?;
Ok(())
})
}
}

impl TypeCheckModule for Mv {
fn typecheck(&mut self, meta: &mut ParserMetadata) -> SyntaxResult {
self.source.typecheck(meta)?;
self.destination.typecheck(meta)?;
self.failure_handler.typecheck(meta)?;

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 TranslateModule for Mv {
fn translate(&self, meta: &mut TranslateMetadata) -> FragmentKind {
let source = self.source.translate(meta);
let destination = self.destination.translate(meta);
let failed = self.failed.translate(meta);
let handler = self.failure_handler.translate(meta);
let mut is_silent = self.modifier.is_silent || meta.silenced;
swap(&mut is_silent, &mut meta.silenced);
let silent = meta.gen_silent().to_frag();
swap(&mut is_silent, &mut meta.silenced);
BlockFragment::new(vec![
fragments!("mv ", source, " ", destination, silent),
failed,
handler,
], false).to_frag()
}
}
Expand Down
16 changes: 12 additions & 4 deletions src/modules/builtin/nameof.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use heraclitus_compiler::prelude::*;
#[derive(Debug, Clone)]
pub struct Nameof {
name: String,
token: Option<Token>,
global_id: Option<usize>,
}

Expand All @@ -23,22 +24,29 @@ impl SyntaxModule<ParserMetadata> for Nameof {
fn new() -> Self {
Nameof {
name: String::new(),
token: None,
global_id: None,
}
}

fn parse(&mut self, meta: &mut ParserMetadata) -> SyntaxResult {
token(meta, "nameof")?;
let name = variable(meta, variable_name_extensions())?;
match meta.get_var(&name) {
self.token = meta.get_current_token();
self.name = variable(meta, variable_name_extensions())?;
Ok(())
}
}

impl TypeCheckModule for Nameof {
fn typecheck(&mut self, meta: &mut ParserMetadata) -> SyntaxResult {
match meta.get_var(&self.name) {
Some(var_decl) => {
self.name.clone_from(&var_decl.name);
self.global_id = var_decl.global_id;
Ok(())
}
None => {
let tok = meta.get_current_token();
error!(meta, tok, format!("Variable '{name}' not found"))
error!(meta, self.token.clone(), format!("Variable '{}' not found", self.name))
}
}
}
Expand Down
Loading