Skip to content

Commit

Permalink
feat: better wref handling in dynamic cast
Browse files Browse the repository at this point in the history
  • Loading branch information
jac3km4 committed Aug 12, 2024
1 parent 6b79caf commit d34f65b
Show file tree
Hide file tree
Showing 5 changed files with 100 additions and 14 deletions.
3 changes: 2 additions & 1 deletion compiler/src/assembler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,8 @@ impl<'a> Assembler<'a> {
},
Expr::Cast(type_, expr, span) => {
if let TypeId::Class(class) = type_ {
self.emit(Instr::DynamicCast(class, 0));
let is_weak = matches!(type_of(&expr, scope, pool)?, TypeId::WeakRef(_));
self.emit(Instr::DynamicCast(class, is_weak as u8));
self.assemble(*expr, scope, pool, None)?;
} else {
return Err(Cause::UnsupportedOperation("casting", type_.pretty(pool)?).with_span(span));
Expand Down
6 changes: 6 additions & 0 deletions compiler/src/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ pub enum Diagnostic {
ClassWithNoIndirectionDeprecation(Ident, Span),
#[error("cannot sort this type of array, only arrays of primitives can be sorted")]
InvalidSortType(Span),
#[error("this cast cannot succeed, because '{0}' is not related to '{1}'")]
PointlessDynCast(Ident, Ident, Span),
#[error("this cast is redundant, '{0}' is already a '{1}'")]
RedundantDynCast(Ident, Ident, Span),
#[error("syntax error, expected {0}")]
SyntaxError(ExpectedSet, Span),
#[error("{0}")]
Expand Down Expand Up @@ -138,6 +142,8 @@ impl Diagnostic {
| Self::NonClassRefDeprecation(_, span)
| Self::ClassWithNoIndirectionDeprecation(_, span)
| Self::InvalidSortType(span)
| Self::PointlessDynCast(_, _, span)
| Self::RedundantDynCast(_, _, span)
| Self::CompileError(_, span)
| Self::SyntaxError(_, span)
| Self::CteError(_, span) => *span,
Expand Down
26 changes: 18 additions & 8 deletions compiler/src/typechecker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,14 +119,24 @@ impl<'a> TypeChecker<'a> {
Expr::Declare(local, Some(type_.into()), initializer.map(Box::new), *span)
}
Expr::Cast(type_name, expr, span) => {
let type_ = scope.resolve_type(type_name, self.pool).with_span(*span)?;
let to = scope.resolve_type(type_name, self.pool).with_span(*span)?;
let checked = self.check(expr, None, scope)?;
if let TypeId::WeakRef(inner) = type_of(&checked, scope, self.pool)? {
let converted = insert_conversion(checked, &TypeId::Ref(inner), Conversion::WeakRefToRef);
Expr::Cast(type_, Box::new(converted), *span)
} else {
Expr::Cast(type_, Box::new(checked), *span)
if matches!(to, TypeId::Class(_)) {
let from = type_of(&checked, scope, self.pool)?;
let from_class = from.unwrapped();
if find_conversion(&to, from_class, self.pool)?.is_none() {
let from_name = from_class.pretty(self.pool)?;
let to_name = to.pretty(self.pool)?;
if find_conversion(from_class, &to, self.pool)?.is_none() {
self.diagnostics
.push(Diagnostic::PointlessDynCast(from_name, to_name, *span));
} else {
self.diagnostics
.push(Diagnostic::RedundantDynCast(from_name, to_name, *span));
}
}
}
Expr::Cast(to, Box::new(checked), *span)
}
Expr::Assign(lhs, rhs, span) => {
let lhs_typed = self.check(lhs, None, scope)?;
Expand Down Expand Up @@ -809,8 +819,8 @@ pub fn type_of(expr: &TypedExpr, scope: &Scope, pool: &ConstantPool) -> Result<T
Expr::ArrayLit(_, type_, _) => TypeId::Array(type_.clone().unwrap()),
Expr::InterpolatedString(_, _, span) => scope.resolve_type(&TypeName::STRING, pool).with_span(*span)?,
Expr::Cast(type_, expr, _) => match type_of(expr, scope, pool)? {
TypeId::WeakRef(_) | TypeId::Ref(_) => TypeId::Ref(Box::new(type_.clone())),
TypeId::ScriptRef(_) => TypeId::ScriptRef(Box::new(type_.clone())),
TypeId::Ref(_) => TypeId::Ref(Box::new(type_.clone())),
TypeId::WeakRef(_) => TypeId::WeakRef(Box::new(type_.clone())),
_ => type_.clone(),
},
Expr::Call(Callable::Function(index), _, _, span) => match pool.function(*index)?.return_type {
Expand Down
17 changes: 12 additions & 5 deletions compiler/tests/bytecode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@ use utils::TestContext;
fn compile_dynamic_casts() {
let sources = "
func Testing() {
let b: wref<B> = new B();
let a: wref<A> = b as A;
let a: wref<A> = new B();
let b: wref<B> = a as B;
let c: ref<A> = new B();
let d = c as A;
}
class A {}
Expand All @@ -25,10 +27,15 @@ fn compile_dynamic_casts() {
mem!(New(class_b)),
pat!(Assign),
mem!(Local(a)),
pat!(RefToWeakRef),
mem!(DynamicCast(class_a, __)),
pat!(WeakRefToRef),
pat!(DynamicCast(_, 1)),
mem!(Local(b)),
pat!(Assign),
mem!(Local(c)),
mem!(New(class_b)),
pat!(Assign),
mem!(Local(d)),
pat!(DynamicCast(_, 0)),
mem!(Local(c)),
pat!(Nop)
];
TestContext::compiled(vec![sources]).unwrap().run("Testing", check);
Expand Down
62 changes: 62 additions & 0 deletions compiler/tests/compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -418,3 +418,65 @@ fn fail_on_nonprim_sort() {
let (_, errs) = compiled(vec![sources]).unwrap();
assert!(matches!(&errs[..], &[Diagnostic::InvalidSortType(_),]), "{:?}", errs);
}

#[test]
fn fail_invalid_dyn_cast() {
let sources = "
func Testing() {
let a = new A();
let b = a as B;
}
class A {}
struct B {}
";

let (_, errs) = compiled(vec![sources]).unwrap();
let errs = errs.into_iter().filter(Diagnostic::is_fatal).collect_vec();
assert!(
matches!(&errs[..], &[Diagnostic::CompileError(Cause::NonClassRef(_), _)]),
"{:?}",
errs
);
}

#[test]
fn warn_pointless_dyn_cast() {
let sources = "
func Testing() {
new A() as B;
}
class A {}
class B {}
";

let (_, errs) = compiled(vec![sources]).unwrap();
assert!(
matches!(&errs[..], &[Diagnostic::PointlessDynCast(_, _, _)]),
"{:?}",
errs
);
}

#[test]
fn warn_redundant_dyn_cast() {
let sources = "
func Testing() {
new B() as A;
}
class A {}
class B extends A {}
";

let (_, errs) = compiled(vec![sources]).unwrap();
assert!(
matches!(&errs[..], &[Diagnostic::RedundantDynCast(_, _, _)]),
"{:?}",
errs
);
}

0 comments on commit d34f65b

Please sign in to comment.