Merge pull request #80 from mist-go/class-semantics

Class semantics
This commit is contained in:
2026-06-16 16:27:44 +02:00
committed by GitHub
6 changed files with 431 additions and 35 deletions
+55 -27
View File
@@ -184,8 +184,13 @@ fn is_source_newer(source *Path, output *Path) std::io::Result<bool> {
Ok(source_time > output_time)
}
pub enum TranspileError<'a> {
Parse[error::ParseError<'a, Vec<mist_parser::ast::TopLevel>>],
Semantic[Vec<mist_parser::semantics::SemanticError>]
}
pub fn transpile_code<'a>(path *PathBuf, source *'a str, mut output_file PathBuf, mod_decl *str, curr_mod_decl *Option<[mist_parser::ast::Visibility, mist_parser::ast::Identifier]>)
Result<[PathBuf, mist_codegen::RustCodegen, String], error::ParseError<'a, Vec<mist_parser::ast::TopLevel>>> {
Result<[PathBuf, mist_codegen::RustCodegen, String], TranspileError<'a>> {
if let Some[[_, name]] = &curr_mod_decl {
if output_file.file_name().map(fn(v) -> v.to_str()).unwrap_or_default() != Some("mod.rs") {
@@ -204,7 +209,11 @@ pub fn transpile_code<'a>(path *PathBuf, source *'a str, mut output_file PathBuf
let mut gc = mist_codegen::RustCodegen::new(path.clone());
let parsed = mist_parser::parse(source)?;
let parsed = mist_parser::parse(source).map_err(TranspileError::Parse)?;
for i in &parsed {
let _ = mist_parser::semantics::check_class_semantics(i).map_err(TranspileError::Semantic)?;
}
let output = gc.generate(parsed);
@@ -217,33 +226,9 @@ pub fn transpile_file(path *PathBuf, source *str, output_file PathBuf, mod_decl
let [output_file, gc, output] = match transpile_code(path, source, output_file, mod_decl, curr_mod_decl) {
Ok[output] => output;
Err[e] => {
match e {
error::ParseError::Ast[e] => {
let start_pos = e.span.start_pos().line_col();
let span = e.span.as_str();
eprintln!(
"\n{}:{}:{}\n \x1b[31mError\x1b[0m: {}\n\t{}{}\t{}",
path.as_os_str().display(),
start_pos.0,
start_pos.1,
e.error_message,
span,
if span.ends_with("\n") { "" } else { "\n" },
"^".repeat(span.trim().len()),
);
decode_err(&e, path);
process::exit(1);
}
error::ParseError::PreAst[e] => {
eprintln!("error: parse failed in {}\n{}", path.display(), e);
process::exit(1);
}
}
}
};
let res = fs::write(&output_file, format!("{mod_decl}{output}"));
@@ -272,3 +257,46 @@ pub fn transpile_file(path *PathBuf, source *str, output_file PathBuf, mod_decl
process::exit(1);
}
}
pub fn decode_err<'a>(err *TranspileError<'a>, path *PathBuf) {
let curr_dir = std::env::current_dir().unwrap();
match err {
TranspileError::Semantic[e_list] => {
for e in e_list {
eprintln!(
"\n{}:{}:{}\n \x1b[31mError\x1b[0m: {}",
path.strip_prefix(&curr_dir).unwrap_or_else(|_| path).as_os_str().display(),
e.line,
e.column,
e.error_message,
);
}
}
TranspileError::Parse[e] => {
match e {
error::ParseError::Ast[e] => {
let start_pos = e.span.start_pos().line_col();
let span = e.span.as_str();
eprintln!(
"\n{}:{}:{}\n \x1b[31mError\x1b[0m: {}\n\t{}{}\t{}",
path.strip_prefix(curr_dir).unwrap_or_else(|_| path).as_os_str().display(),
start_pos.0,
start_pos.1,
e.error_message,
span,
if span.ends_with("\n") { "" } else { "\n" },
"^".repeat(span.trim().len()),
);
}
error::ParseError::PreAst[e] => {
eprintln!("error: parse failed in {}\n{}", path.strip_prefix(curr_dir).unwrap_or_else(|_| path).display(), e);
}
}
}
}
}
+1 -1
View File
@@ -333,7 +333,7 @@ trait_decl = {
override_kw = { "override" ~ ("(" ~ expr_path ~ ")")? }
function_decl = {
visibility? ~ override_kw? ~ "fn" ~ ("void" | type_expr) ~ identifier ~ generics_decl? ~ "(" ~ method_args ~ ")" ~ (block | semicolon)
visibility? ~ "fn" ~ ("void" | type_expr) ~ identifier ~ generics_decl? ~ "(" ~ method_args ~ ")" ~ override_kw? ~ (block | semicolon)
}
// ======================================================
+1
View File
@@ -5,6 +5,7 @@ pub mod ast;
pub mod error;
pub mod parser;
pub mod rev_mapper;
pub mod semantics;
use ast::*;
+2 -1
View File
@@ -13,7 +13,6 @@ impl<'a> TryFrom<pest::iterators::Pair<'a, Rule>> for FunctionDecl {
ast_ensure!(pair, Rule::function_decl => {
let mut inner = pair.into_inner();
let visibility = Visibility::try_from(&mut inner);
let is_override = consume_rule(&mut inner, Rule::override_kw).map(Override::try_from).transpose();
let return_type = consume_rule(&mut inner, Rule::type_expr)
.map(TypeExpr::try_from)
@@ -64,6 +63,8 @@ impl<'a> TryFrom<pest::iterators::Pair<'a, Rule>> for FunctionDecl {
})
.unwrap_or_else(|| Ok(ParamList(self_param.into_iter().collect())));
let is_override = consume_rule(&mut inner, Rule::override_kw).map(Override::try_from).transpose();
let body = inner.next().map(Block::try_from).transpose();
ast_expr!(Self {
+367
View File
@@ -0,0 +1,367 @@
use crate::ast::{
Block, ClassItem, Expression, FunctionDecl, Identifier, MatchItem, Postfix, Prefix, Spanned,
Statement, StatementBranch, TopLevel, TopLevelKind, VarDeclStmt,
};
pub trait GetMutability {
fn get_mutability(&self) -> Vec<Identifier>;
}
#[derive(Debug, Clone)]
pub struct SemanticError {
pub line: usize,
pub column: usize,
pub error_message: String,
}
/// Class semantics - Ensuring fields are initialized
pub fn find_function_call<'a>(
items: &'a Vec<ClassItem>,
name: &Identifier,
) -> Option<&'a Spanned<FunctionDecl>> {
items.iter().find_map(|v| {
if let ClassItem::Method(m) = v {
if m.item.is_using_self() && &m.item.name == name {
Some(m)
} else {
None
}
} else {
None
}
})
}
pub fn check_class_semantics<'a>(top_level: &TopLevel) -> Result<(), Vec<SemanticError>> {
if let TopLevelKind::ClassDecl {
fields,
constructor,
items,
name,
inherits,
..
} = &top_level.0.item
{
if let Some(constructor) = constructor {
let mut fields = fields
.iter()
.map(|field| Spanned {
item: field.item.decl.name.clone(),
line: field.line,
column: field.column,
})
.collect::<Vec<_>>();
if inherits.is_some() {
fields.push(Spanned {
line: top_level.0.line,
column: top_level.0.column,
item: Identifier("_super".to_string()),
});
}
let mut mutability = constructor.item.body.get_mutability();
let mut i = 0;
while i < mutability.len() {
let name = mutability[i].clone();
if let Some(method) = find_function_call(items, &name) {
if let Some(body) = &method.item.body {
for entry in body.get_mutability() {
if !mutability.contains(&entry) {
mutability.push(entry);
}
}
}
}
i += 1;
}
let mut errs = Vec::new();
for field in &fields {
if !mutability.contains(&field.item) {
errs.push(SemanticError {
line: field.line,
column: field.column,
error_message: format!(
"class field `{}.{}` is uninitialized",
name.0, field.item.0
),
});
}
}
if errs.len() > 0 {
return Err(errs);
}
}
Ok(())
} else {
Ok(())
}
}
impl<T: GetMutability> GetMutability for Vec<T> {
fn get_mutability(&self) -> Vec<Identifier> {
self.iter().flat_map(T::get_mutability).collect()
}
}
impl GetMutability for Block {
fn get_mutability(&self) -> Vec<Identifier> {
let mut items: Vec<Identifier> = self
.statements
.iter()
.flat_map(|v| v.item.get_mutability())
.collect();
if let Some(v) = &self.soft_return {
items.append(&mut v.item.get_mutability());
}
items
}
}
impl GetMutability for Statement {
fn get_mutability(&self) -> Vec<Identifier> {
match self {
Statement::Block(body) | Statement::UnsafeBlock(body) => body.get_mutability(),
Statement::If {
initial,
else_if,
else_branch,
} => {
let mut all_sets: Vec<Vec<Identifier>> = Vec::new();
all_sets.push(initial.body.get_mutability());
for branch in else_if {
all_sets.push(branch.body.get_mutability());
}
if let Some(else_block) = else_branch {
all_sets.push(else_block.get_mutability());
} else {
return Vec::new();
}
let mut result = all_sets.remove(0);
for set in all_sets {
result.retain(|id| set.contains(id));
}
result
}
Statement::Loop(body) => body.get_mutability(),
Statement::While(branch) => branch.body.get_mutability(),
Statement::CStyleFor {
body,
init,
condition,
update,
..
} => {
let mut a = init.get_mutability();
a.append(&mut condition.get_mutability());
a.append(&mut update.get_mutability());
a.append(&mut body.get_mutability());
a
}
Statement::For { body, iterator, .. } => {
let mut a = iterator.get_mutability();
a.append(&mut body.get_mutability());
a
}
Statement::Match(_, items) => {
if items.is_empty() {
return Vec::new();
}
let mut all_sets: Vec<Vec<Identifier>> = items
.iter()
.map(|item| item.item.get_mutability())
.collect();
let mut result = all_sets.remove(0);
for set in all_sets {
result.retain(|id| set.contains(id));
}
result
}
Statement::VarDecl(stmt) => stmt
.init
.as_ref()
.map(|e| e.get_mutability())
.unwrap_or_default(),
Statement::Return(expr) => expr
.as_ref()
.map(|e| e.get_mutability())
.unwrap_or_default(),
Statement::Break | Statement::Continue => Vec::new(),
}
}
}
impl GetMutability for StatementBranch {
fn get_mutability(&self) -> Vec<Identifier> {
let mut a = self.condition.get_mutability();
a.append(&mut self.body.get_mutability());
a
}
}
impl GetMutability for MatchItem {
fn get_mutability(&self) -> Vec<Identifier> {
self.1.get_mutability()
}
}
impl GetMutability for VarDeclStmt {
fn get_mutability(&self) -> Vec<Identifier> {
self.init
.as_ref()
.map(|e| e.get_mutability())
.unwrap_or_default()
}
}
fn get_self_ref(initial: &Expression, postfixes: &[Postfix]) -> Option<Identifier> {
match initial {
Expression::Path(v) => {
let ident = v.0.first()?.ident.0.as_str();
if ident == "self" || ident == "super" {
if let Some(Postfix::FieldAccess(field, _)) = postfixes.first() {
if !matches!(postfixes.get(1), Some(Postfix::Call(_))) {
return Some(field.clone());
}
}
}
}
_ => {}
}
None
}
fn get_self_method_call(initial: &Expression, postfixes: &[Postfix]) -> Option<Identifier> {
match initial {
Expression::Path(v) => {
let ident = v.0.first()?.ident.0.as_str();
if ident == "self" || ident == "super" {
if let Some(Postfix::FieldAccess(method, _)) = postfixes.first() {
if matches!(postfixes.get(1), Some(Postfix::Call(_))) {
return Some(method.clone());
}
}
}
}
_ => {}
}
None
}
impl Expression {
fn get_self_ref(&self) -> Option<Identifier> {
match self {
Self::Fix {
initial, postfixes, ..
} => get_self_ref(initial, postfixes),
_ => None,
}
}
}
impl GetMutability for Expression {
fn get_mutability(&self) -> Vec<Identifier> {
match self {
Self::Array(v) => v.get_mutability(),
Self::ArrayRepeat(v, i) => {
let mut a = v.get_mutability();
a.append(&mut i.get_mutability());
a
}
Self::Binary { lhs, op, rhs } => {
if mutates_lhs(op) {
let mut a = if let Some(self_ref) = lhs.get_self_ref() {
vec![self_ref]
} else {
lhs.get_mutability()
};
a.append(&mut rhs.get_mutability());
a
} else {
let mut a = lhs.get_mutability();
a.append(&mut rhs.get_mutability());
a
}
}
Self::Statement(stmt) => stmt.get_mutability(),
Self::Fix {
initial,
prefixes,
postfixes,
} => {
let mut result = Vec::new();
// &mut self.field / &mut super.field / &mut super pattern
if prefixes.iter().any(|p| matches!(p, Prefix::RefMut)) {
if let Some(self_ref) = get_self_ref(initial, postfixes) {
result.push(self_ref);
} else if let Expression::Path(v) = &**initial {
if v.0.first().map_or(false, |s| s.ident.0 == "super") {
result.push(Identifier("_super".to_string()));
}
}
}
// Recurse into all postfix sub-expressions
for postfix in postfixes {
match postfix {
Postfix::Assign(_, rhs) => {
if let Some(self_ref) = get_self_ref(initial, postfixes) {
result.push(self_ref);
}
result.append(&mut rhs.get_mutability());
}
Postfix::Call(args) => {
for arg in args {
result.append(&mut arg.get_mutability());
}
}
Postfix::StructCall(fields) => {
for (_, expr) in fields {
if let Some(expr) = expr {
result.append(&mut expr.get_mutability());
}
}
}
Postfix::Index(expr) => {
result.append(&mut expr.get_mutability());
}
_ => {}
}
}
// Method calls on self: self.method_name(args)
if let Some(method) = get_self_method_call(initial, postfixes) {
result.push(method);
}
result
}
Self::Closure { body, .. } => body.get_mutability(),
Self::Literal(_) => Vec::new(),
Self::Path(v) => {
if v.0.first().map_or(false, |s| s.ident.0 == "super") {
vec![Identifier("_super".to_string())]
} else {
Vec::new()
}
}
}
}
}
pub fn mutates_lhs(op: &str) -> bool {
matches!(op, "=" | "->")
}
+4 -5
View File
@@ -1,8 +1,7 @@
pub module class_test;
pub class Animal<T> {
pub class Animal {
pub name String;
pub val T;
constructor() {
self.name = "Rex".to_string();
@@ -19,7 +18,7 @@ pub class Animal<T> {
pub fn void static_method() {}
}
pub class Mammal<T> : Animal<T> {
pub class Mammal : Animal {
constructor() {
super = Super::new();
}
@@ -30,12 +29,12 @@ pub class Mammal<T> : Animal<T> {
}
}
pub class Dog : Mammal<i32> {
pub class Dog : Mammal {
constructor() {
super = Super::new();
}
pub override(Animal<i32>) fn String speak(*self) {
pub fn String speak(*self) override(Animal) {
"Woof!".to_string()
}