Skip to content

Commit

Permalink
fix(typecheck): make a bit more robust
Browse files Browse the repository at this point in the history
  • Loading branch information
cecelot committed Apr 22, 2024
1 parent 5ac3fc4 commit be875f2
Show file tree
Hide file tree
Showing 17 changed files with 277 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ Err(
column: 5,
length: 3,
},
text: "the type of `foo` may not be valid",
text: "type may be invalid",
},
],
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
source: crates/kyac/src/pass/typecheck.rs
expression: errors
---
Err(
[
PreciseError {
filename: "test-cases/typecheck/generics/free-fun-cast-err.kya",
heading: "Foo is not a subclass of T",
source: " freeFunction(foo);",
span: Span {
line: 11,
column: 18,
length: 3,
},
text: "expression of type Foo",
},
],
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
source: crates/kyac/src/pass/typecheck.rs
expression: errors
---
Err(
[
PreciseError {
filename: "test-cases/typecheck/generics/method-cast-err.kya",
heading: "Bar is not a subclass of T",
source: " foo.print(bar);",
span: Span {
line: 14,
column: 15,
length: 3,
},
text: "expression of type Bar",
},
],
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
source: crates/kyac/src/pass/typecheck.rs
expression: errors
---
Err(
[
PreciseError {
filename: "test-cases/typecheck/generics/type-param-shadow.kya",
heading: "`T` already defined",
source: " fun print<T: Print>(self, val: T) {}",
span: Span {
line: 6,
column: 15,
length: 1,
},
text: "",
},
],
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
source: crates/kyac/src/pass/typecheck.rs
expression: errors
---
Err(
[
PreciseError {
filename: "test-cases/typecheck/generics/undef-generic-type.kya",
heading: "`R` is not defined",
source: " fun print<T: Print>(self, val: R) {}",
span: Span {
line: 6,
column: 36,
length: 1,
},
text: "",
},
],
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
source: crates/kyac/src/pass/typecheck.rs
expression: errors
---
Err(
[
PreciseError {
filename: "test-cases/typecheck/generics/undef-generic-type-free-fun.kya",
heading: "`R` is not defined",
source: "fun freeFunction<T: Print>(val: R) {}",
span: Span {
line: 7,
column: 33,
length: 1,
},
text: "",
},
],
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
source: crates/kyac/src/pass/typecheck.rs
expression: errors
---
Err(
[
PreciseError {
filename: "test-cases/typecheck/generics/unsatisfied-bounds.kya",
heading: "Baz does not satisfy bound Print",
source: " let bar: Bar<Baz> = Bar:init(x: Baz:init());",
span: Span {
line: 16,
column: 14,
length: 3,
},
text: "in instantiation of type here",
},
],
)
69 changes: 57 additions & 12 deletions crates/kyac/src/pass/typecheck.rs
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,7 @@ impl ResolveType for node::ClassDecl {
meta: &mut ResolvedMetaInfo,
) -> Result<ResolvedType, TypeError> {
cx.begin_scope();
cx.set_type_parameters(meta, self.tp.as_ref());
cx.set_type_parameters(meta, self.tp.as_ref(), true);
for field in &self.fields {
if let Err(e) = field.ty.resolve(cx, meta) {
cx.error(
Expand Down Expand Up @@ -299,7 +299,7 @@ impl ResolveType for Rc<node::FuncDecl> {
}
}
cx.begin_scope();
cx.set_type_parameters(meta, Some(&self.tp));
cx.set_type_parameters(meta, Some(&self.tp), true);
cx.function = Some(self.name.clone());
if self.params.len() > 8 {
cx.error(
Expand All @@ -325,9 +325,8 @@ impl ResolveType for Rc<node::FuncDecl> {
);
return Err(e);
}
cx.scope_mut()
.symbols
.insert(param.name.to_string(), Symbol::Function(Rc::clone(self)));
let ty = param.ty.resolve(cx, meta).unwrap().base.clone();
cx.scope_mut().symbols.insert(param.name.to_string(), ty);
}
for node in &self.body {
let _ = node.resolve(cx, meta);
Expand Down Expand Up @@ -575,10 +574,11 @@ impl ResolveType for node::Call {
}
_ => unimplemented!(),
};
let (arity, params, ty) = (
let (arity, params, ty, tp) = (
function.params.len(),
function.params.clone(),
function.ty.clone(),
function.tp.clone(),
);
if arity != self.args.len() {
cx.error(
Expand All @@ -594,7 +594,10 @@ impl ResolveType for node::Call {
for (i, arg) in self.args.iter().enumerate() {
let got = arg.resolve(cx, meta)?;
if i < params.len() {
cx.begin_scope();
cx.set_type_parameters(meta, Some(tp.as_ref()), false);
let expected = params[i].ty.resolve(cx, meta)?;
cx.end_scope();
if matches!(got.base, Symbol::Class(_)) {
let casted = cx.cast(&expected, &got);
if got != expected && casted.is_none() {
Expand Down Expand Up @@ -771,7 +774,7 @@ impl ResolveType for node::Access {
symbols.push(left.base.clone());
indices.push(index);
cx.begin_scope();
cx.set_type_parameters(meta, cls.tp.as_ref());
cx.set_type_parameters(meta, cls.tp.as_ref(), false);
ty = field.ty.resolve(cx, meta)?;
cx.end_scope();
} else if let Some(method) = left.method(cx.symbols, right) {
Expand Down Expand Up @@ -890,11 +893,26 @@ impl ResolveType for node::Ident {
}
Some(Symbol::Variable(v)) => v.ty.resolve(cx, meta),
Some(Symbol::Constant(c)) => c.ty.resolve(cx, meta),
Some(Symbol::Class(cls)) => Ok(ResolvedType::new(
Symbol::Class(Rc::clone(&cls)),
vec![],
Type::new(self.name.clone(), vec![]),
)),
Some(Symbol::Int) => Ok(ResolvedType::int()),
Some(Symbol::Float) => Ok(ResolvedType::float()),
Some(Symbol::Str) => Ok(ResolvedType::str()),
Some(Symbol::Bool) => Ok(ResolvedType::bool()),
Some(Symbol::Void) => Ok(ResolvedType::void()),
Some(Symbol::Opaque(s)) => Ok(ResolvedType::new(
Symbol::Opaque(s),
vec![],
Type::new(self.name.clone(), vec![]),
)),
_ => {
cx.error(
self.name.span,
format!("`{}` is not defined", &self.name),
format!("the type of `{}` may not be valid", &self.name),
format!("`{}` is not defined", self.name),
String::from("type may be invalid"),
);
Err(TypeError::Undefined)
}
Expand Down Expand Up @@ -991,7 +1009,7 @@ impl<'a> TypeResolverContext<'a> {
}

fn begin_scope(&mut self) {
self.scopes.push(Scope::default());
self.scopes.push(Scope::new());
}

fn end_scope(&mut self) {
Expand All @@ -1002,9 +1020,18 @@ impl<'a> TypeResolverContext<'a> {
&mut self,
meta: &mut ResolvedMetaInfo,
tp: Option<&Vec<TypeParameter>>,
initial: bool,
) {
if let Some(tp) = tp {
for typ in tp {
if self.ty(&typ.name.to_string()).is_some() && initial {
self.error(
typ.name.span,
format!("`{}` already defined", typ.name.lexeme.unwrap()),
String::new(),
);
continue;
}
let ty = match typ.bound {
Some(ref bound) => {
let raw_type = Type::new(bound.clone(), vec![]);
Expand Down Expand Up @@ -1066,11 +1093,13 @@ impl<'a> TypeResolverContext<'a> {
fn cast(&self, expected: &ResolvedType, got: &ResolvedType) -> Option<String> {
let cls = self.symbol(&got.meta.to_string())?;
let cls = matches!(cls, Symbol::Class(_)).then(|| cls.class().unwrap())?;
let expected =
matches!(expected.base, Symbol::Class(_)).then(|| expected.base.class().unwrap())?;
Symbol::superclasses(cls, self.symbols)
.iter()
.filter(|c| c.name != cls.name)
.map(|c| c.name.to_string())
.find(|cls| cls == &expected.meta.to_string())
.find(|cls| cls == &expected.name.to_string())
}
}

Expand All @@ -1080,6 +1109,15 @@ struct Scope {
types: HashMap<String, Option<ResolvedType>>,
}

impl Scope {
fn new() -> Self {
Self {
symbols: SymbolTable::default(),
types: HashMap::new(),
}
}
}

macro_rules! assert_typecheck {
($($path:expr => $name:ident),*) => {
#[cfg(test)]
Expand All @@ -1103,5 +1141,12 @@ macro_rules! assert_typecheck {

assert_typecheck! {
"test-cases/typecheck/varied.kya" => varied,
"test-cases/typecheck/classes.kya" => classes
"test-cases/typecheck/classes.kya" => classes,
// Generics
"test-cases/typecheck/generics/free-fun-cast-err.kya" => free_fun_cast_err,
"test-cases/typecheck/generics/method-cast-err.kya" => method_cast_err,
"test-cases/typecheck/generics/undef-generic-type.kya" => undef_generic_type,
"test-cases/typecheck/generics/undef-generic-type-free-fun.kya" => undef_type_param_free_fun,
"test-cases/typecheck/generics/type-param-shadow.kya" => type_param_shadow,
"test-cases/typecheck/generics/unsatisfied-bounds.kya" => unsatisfied_bounds
}
12 changes: 12 additions & 0 deletions crates/kyac/test-cases/typecheck/generics/free-fun-cast-err.kya
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
class Print {
fun print(self) {}
}

class Foo {}

fun freeFunction<T: Print>(val: T) {}

fun main() {
let foo: Foo = Foo:init();
freeFunction(foo);
}
15 changes: 15 additions & 0 deletions crates/kyac/test-cases/typecheck/generics/method-cast-err.kya
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
class Print {
fun print(self) {}
}

class Foo {
fun print<T: Print>(self, val: T) {}
}

class Bar {}

fun main() {
let bar: Bar = Bar:init();
let foo: Foo = Foo:init();
foo.print(bar);
}
11 changes: 11 additions & 0 deletions crates/kyac/test-cases/typecheck/generics/type-param-shadow.kya
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
class Print {
fun print(self) {}
}

class Foo<T> {
fun print<T: Print>(self, val: T) {}
}

class Bar {}

fun main() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
class Print {
fun print(self) {}
}

class Foo {}

fun freeFunction<T: Print>(val: R) {}

fun main() {
let foo: Foo = Foo:init();
freeFunction(foo);
}
15 changes: 15 additions & 0 deletions crates/kyac/test-cases/typecheck/generics/undef-generic-type.kya
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
class Print {
fun print(self) {}
}

class Foo {
fun print<T: Print>(self, val: R) {}
}

class Bar {}

fun main() {
let bar: Bar = Bar:init();
let foo: Foo = Foo:init();
foo.print(bar);
}
17 changes: 17 additions & 0 deletions crates/kyac/test-cases/typecheck/generics/unsatisfied-bounds.kya
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
class Print {
fun print(self) {}
}

class Foo<T> {
x: T
}

class Bar<T: Print> {}

class Baz {}

fun main() {
% currently not functional
% let foo: Foo<int> = Foo:init(x: 5);
let bar: Bar<Baz> = Bar:init(x: Baz:init());
}
Loading

0 comments on commit be875f2

Please sign in to comment.