Skip to content

Commit

Permalink
fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
yorickpeterse committed Nov 29, 2023
1 parent bca3cc1 commit 06537a0
Show file tree
Hide file tree
Showing 3 changed files with 99 additions and 23 deletions.
15 changes: 7 additions & 8 deletions compiler/src/type_check/expressions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -495,9 +495,12 @@ impl MethodCall {
self.type_arguments.clone(),
);

if !TypeChecker::new(&state.db)
.check_argument(given, expected, &mut scope)
{
// TODO: remove
let mut checker = TypeChecker::new(&state.db);

checker.trace = self.method.name(&state.db) == "foo";

if !checker.check_argument(given, expected, &mut scope) {
state.diagnostics.type_error(
format_type_with_arguments(&state.db, &scope.left, given),
format_type_with_arguments(&state.db, &scope.right, expected),
Expand Down Expand Up @@ -1620,11 +1623,7 @@ impl<'a> CheckMethodBody<'a> {

let value = self.expression(&mut field.value, scope);
let value_casted = value.cast_according_to(expected, self.db());
let mut checker = TypeChecker::new(self.db());

// TODO: remove
checker.trace = self.file().ends_with("Downloads/test.inko");

let checker = TypeChecker::new(self.db());
let mut env =
Environment::new(value_casted.type_arguments(self.db()), targs);

Expand Down
28 changes: 28 additions & 0 deletions std/test/diagnostics/uni_with_owned.inko
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
trait Eq[T] {
fn eq(other: T)
}

class Thing {}

impl Eq[uni Thing] for Thing {
fn eq(other: uni Thing) {}
}

impl Eq[uni Int] for Int {
fn eq(other: uni Int) {}
}

fn foo[F: Eq[F]](value: F) {}

fn bar {
# This isn't valid because `Thing` implements `Eq[uni Thing]`. Passing
# `Thing` `F: Eq[F]` results in `F: Eq[Thing]`. If we allowed this, we'd be
# able to pass a `Thing` to `Thing.eq` which violates the `uni` constraint of
# the `other` argument.
foo(Thing {})

# This _is_ valid because Int is a value type.
foo(42)
}

# uni_with_owned.inko:14:7 error(invalid-type): expected a value of type 'F', found 'Thing'
79 changes: 64 additions & 15 deletions types/src/check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@ enum Subtyping {

#[derive(Copy, Clone)]
struct Rules {
/// The rules to apply when type checking a class instance against a trait
/// instance.
class_subtyping: Subtyping,
/// The rules to apply when performing sub-typing checks.
subtyping: Subtyping,

/// If a `uni T` is compatible with a `T` value.
uni_compatible_with_owned: bool,

/// If type parameters should be turned into rigid parameters in various
/// contexts (e.g. when comparing trait implementations).
Expand All @@ -31,15 +33,16 @@ struct Rules {
impl Rules {
fn new() -> Rules {
Rules {
class_subtyping: Subtyping::No,
subtyping: Subtyping::No,
uni_compatible_with_owned: true,
rigid_parameters: false,
type_cast: false,
}
}

fn without_subtyping(mut self) -> Rules {
if let Subtyping::Yes = self.class_subtyping {
self.class_subtyping = Subtyping::No
if let Subtyping::Yes = self.subtyping {
self.subtyping = Subtyping::No
}

self
Expand All @@ -61,12 +64,12 @@ impl Rules {
}

fn with_one_time_subtyping(mut self) -> Rules {
self.class_subtyping = Subtyping::Once;
self.subtyping = Subtyping::Once;
self
}

fn with_subtyping(mut self) -> Rules {
self.class_subtyping = Subtyping::Yes;
self.subtyping = Subtyping::Yes;
self
}
}
Expand Down Expand Up @@ -380,8 +383,12 @@ impl<'a> TypeChecker<'a> {
},
TypeRef::Uni(left_id) => match right {
TypeRef::Owned(right_id)
| TypeRef::Any(right_id)
| TypeRef::Uni(right_id) => {
if rules.uni_compatible_with_owned
|| left.is_value_type(self.db) =>
{
self.check_type_id(left_id, right_id, env, rules)
}
TypeRef::Any(right_id) | TypeRef::Uni(right_id) => {
self.check_type_id(left_id, right_id, env, rules)
}
TypeRef::Ref(right_id) | TypeRef::Mut(right_id)
Expand Down Expand Up @@ -559,8 +566,8 @@ impl<'a> TypeChecker<'a> {
) -> bool {
let trait_rules = rules;

if let Subtyping::Once = rules.class_subtyping {
rules.class_subtyping = Subtyping::No;
if let Subtyping::Once = rules.subtyping {
rules.subtyping = Subtyping::No;
}

match left_id {
Expand Down Expand Up @@ -830,13 +837,34 @@ impl<'a> TypeChecker<'a> {
env: &mut Environment,
mut rules: Rules,
) -> bool {
// When checking trait implementations we don't know exactly how a `uni
// T` value is used, and thus can't know if it's safe to compare it to a
// `T`. Consider this example:
//
// trait Equal[T] {
// fn ==(other: T) -> Bool
// }
//
// class Thing {}
//
// impl Equal[uni Thing] for Thing {
// fn ==(other: uni Thing) -> Bool {
// true
// }
// }
//
// If we end up comparing `Equal[uni Thing]` with `Equal[Thing]` we
// can't allow this, because the argument of `==` could then be given a
// `Thing` when we instead expect a `uni Thing`.
rules.uni_compatible_with_owned = false;

// `Array[Cat]` isn't compatible with `mut Array[Animal]`, as that could
// result in a `Dog` being added to the Array.
match rules.class_subtyping {
match rules.subtyping {
Subtyping::No => return false,
Subtyping::Yes => {}
Subtyping::Once => {
rules.class_subtyping = Subtyping::No;
rules.subtyping = Subtyping::No;
}
}

Expand All @@ -848,6 +876,23 @@ impl<'a> TypeChecker<'a> {
return false;
};

// TODO: remove
if self.trace {
println!(
"trait: \x1b[0;1m{} -> {}\x1b[0m {:?} -> {:?}\n",
crate::format::format_type_with_arguments(
self.db,
&env.left,
imp.instance
),
crate::format::format_type_with_arguments(
self.db, &env.right, right
),
imp.instance,
right,
);
}

if left.instance_of.is_generic(self.db) {
// The implemented trait may refer to type parameters of the
// implementing class, so we need to expose those using a new scope.
Expand Down Expand Up @@ -939,8 +984,12 @@ impl<'a> TypeChecker<'a> {
left: TraitInstance,
right: TraitInstance,
env: &mut Environment,
rules: Rules,
mut rules: Rules,
) -> bool {
// Similar to when checking classes with traits, we have to be more
// strict about comparing `uni T` values with `T` values.
rules.uni_compatible_with_owned = false;

if left == right {
return true;
}
Expand Down

0 comments on commit 06537a0

Please sign in to comment.