Skip to content

Commit 4dd7855

Browse files
Disallow multiple dereferences during typechecking (#214)
1 parent 9d744a3 commit 4dd7855

File tree

3 files changed

+102
-22
lines changed

3 files changed

+102
-22
lines changed
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
tuple = (1, 1)
2+
3+
_ = (&&tuple).first
4+
5+
_ = tuple.!first
6+
_ = tuple.!missing
7+
8+
imm_ref = &tuple
9+
_ = imm_ref.!first
10+
_ = imm_ref.!missing
11+
12+
mut_tuple = mut (1, 1)
13+
14+
_ = mut_tuple.&missing
15+
_ = mut_tuple.!missing
16+
17+
mut_ref = !mut_tuple
18+
19+
_ = mut_ref.&missing
20+
_ = mut_ref.!missing
21+
22+
_ = (!&tuple).!first
23+
24+
// flags: --check
25+
// expected stderr:
26+
// field_diagnostics.an:3:6 error: &shared &shared Int c, Int d has no field 'first' of type { first: b, .. }
27+
// _ = (&&tuple).first
28+
//
29+
// field_diagnostics.an:5:5 error: Cannot mutably reference `tuple`. It was declared as immutable
30+
// _ = tuple.!first
31+
//
32+
// field_diagnostics.an:6:5 error: Int a, Int b has no field 'missing' of type { missing: b, .. }
33+
// _ = tuple.!missing
34+
//
35+
// field_diagnostics.an:9:5 error: Expected a mutable reference but found &shared Int b, Int c instead
36+
// _ = imm_ref.!first
37+
//
38+
// field_diagnostics.an:10:5 error: &shared Int b, Int c has no field 'missing' of type { missing: b, .. }
39+
// _ = imm_ref.!missing
40+
//
41+
// field_diagnostics.an:14:5 error: Int a, Int b has no field 'missing' of type { missing: b, .. }
42+
// _ = mut_tuple.&missing
43+
//
44+
// field_diagnostics.an:15:5 error: Int a, Int b has no field 'missing' of type { missing: b, .. }
45+
// _ = mut_tuple.!missing
46+
//
47+
// field_diagnostics.an:19:5 error: !shared Int b, Int c has no field 'missing' of type { missing: b, .. }
48+
// _ = mut_ref.&missing
49+
//
50+
// field_diagnostics.an:20:5 error: !shared Int b, Int c has no field 'missing' of type { missing: b, .. }
51+
// _ = mut_ref.!missing
52+
//
53+
// field_diagnostics.an:22:6 error: !shared &shared Int c, Int d has no field 'first' of type { first: b, .. }
54+
// _ = (!&tuple).!first

src/error/mod.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ pub enum TypeErrorKind {
125125
MatchPatternTypeDiffers,
126126
MatchReturnTypeDiffers,
127127
DoesNotMatchAnnotatedType,
128-
ExpectedStructReference,
128+
ExpectedMutable,
129129

130130
// This taking a String is the reason we can't have nice things (Copy)
131131
NoFieldOfType(/*field name*/ String),
@@ -309,8 +309,8 @@ impl Display for DiagnosticKind {
309309
DiagnosticKind::TypeError(TypeErrorKind::DoesNotMatchAnnotatedType, actual, expected) => {
310310
write!(f, "Expression of type {actual} does not match its annotated type {expected}")
311311
},
312-
DiagnosticKind::TypeError(TypeErrorKind::ExpectedStructReference, actual, _expected) => {
313-
write!(f, "Expected a struct reference but found {actual} instead")
312+
DiagnosticKind::TypeError(TypeErrorKind::ExpectedMutable, actual, _expected) => {
313+
write!(f, "Expected a mutable reference but found {actual} instead")
314314
},
315315
DiagnosticKind::TypeError(TypeErrorKind::NoFieldOfType(field_name), actual, expected) => {
316316
write!(f, "{actual} has no field '{field_name}' of type {expected}")

src/types/typechecker.rs

Lines changed: 45 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -856,9 +856,8 @@ fn get_fields(
856856
},
857857
}
858858
},
859-
TypeApplication(constructor, args) => match follow_bindings_in_cache_and_map(constructor, bindings, cache) {
860-
Ref { .. } => get_fields(&args[0], &[], bindings, cache),
861-
other => get_fields(&other, args, bindings, cache),
859+
TypeApplication(constructor, args) => {
860+
get_fields(&follow_bindings_in_cache_and_map(constructor, bindings, cache), args, bindings, cache)
862861
},
863862
Struct(fields, rest) => match &cache.type_bindings[rest.0] {
864863
Bound(binding) => get_fields(&binding.clone(), args, bindings, cache),
@@ -2007,33 +2006,60 @@ impl<'a> Inferable<'a> for ast::Extern<'a> {
20072006

20082007
impl<'a> Inferable<'a> for ast::MemberAccess<'a> {
20092008
fn infer_impl(&mut self, cache: &mut ModuleCache<'a>) -> TypeResult {
2010-
let mut result = infer(self.lhs.as_mut(), cache);
2009+
let result = infer(self.lhs.as_mut(), cache);
20112010

20122011
let level = LetBindingLevel(CURRENT_LEVEL.load(Ordering::SeqCst));
20132012
let mut field_type = cache.next_type_variable(level);
20142013

2015-
// If this is a mutable reference to a field, ensure the collection is either
2016-
// a mutable reference to a collection, or is a local mutable variable.
2017-
if let Some(Mutability::Mutable) = self.offset {
2018-
let collection_variable = next_type_variable(cache);
2019-
let expected = ref_of(Mutability::Mutable, collection_variable.clone(), cache);
2020-
2021-
match try_unify(&result.typ, &expected, self.lhs.locate(), cache, TE::ExpectedStructReference) {
2022-
Ok(bindings) => bindings.perform(cache),
2023-
Err(_) => check_field_access_lhs_is_mutable(&self.lhs, false, cache),
2024-
}
2025-
2026-
result.typ = collection_variable;
2027-
}
2028-
20292014
let mut fields = BTreeMap::new();
20302015
fields.insert(self.field.clone(), field_type.clone());
20312016

20322017
// The '..' or 'rest of the struct' stand-in variable
20332018
let rho = cache.next_type_variable_id(level);
20342019
let struct_type = Type::Struct(fields, rho);
20352020

2036-
unify(&result.typ, &struct_type, self.location, cache, TE::NoFieldOfType(self.field.clone()));
2021+
let mut bindings =
2022+
try_unify(&result.typ, &struct_type, self.location, cache, TE::NoFieldOfType(self.field.clone()));
2023+
2024+
if bindings.is_ok() && self.offset == Some(Mutability::Mutable) {
2025+
// If unification succeeded (`bindings.is_ok()`) and this is a
2026+
// mutable field access (`self.offset == Mutable`), `self.lhs`
2027+
// must be a mutable variable.
2028+
check_field_access_lhs_is_mutable(self.lhs.as_ref(), false, cache);
2029+
} else if let Err(error_without_deref) = bindings {
2030+
// If unification failed, try again with a single dereference.
2031+
//
2032+
// (This replicates logic from AST-to-HIR monomorphisation.)
2033+
2034+
// If this is a mutable field access (`a.!b`), it's necessary to
2035+
// unify the actual type (`result.typ`) with a mutable reference to
2036+
// the partial `struct_type`.
2037+
let struct_ref = ref_of(self.offset.unwrap_or(Mutability::Immutable), struct_type.clone(), cache);
2038+
2039+
bindings = try_unify(&result.typ, &struct_ref, self.lhs.locate(), cache, TE::ExpectedMutable);
2040+
2041+
// The complicated logic here is solely to improve diagnostics.
2042+
bindings = bindings.map_err(|error_with_mutable_deref| {
2043+
let struct_imm_ref = ref_of(Mutability::Immutable, struct_type.clone(), cache);
2044+
2045+
// This unification is only used for a single bit of information.
2046+
let unification_with_immutable_deref =
2047+
try_unify(&result.typ, &struct_imm_ref, self.lhs.locate(), cache, TE::ExpectedMutable);
2048+
2049+
if unification_with_immutable_deref.is_ok() {
2050+
// Show TE::ExpectedMutable from the second `try_unify`
2051+
// when an immutable deref unifies but a mutable deref
2052+
// doesn't.
2053+
error_with_mutable_deref
2054+
} else {
2055+
// Unification still fails against an immutable reference.
2056+
// Show the diagnostic that doesn't mention references.
2057+
error_without_deref
2058+
}
2059+
});
2060+
}
2061+
2062+
perform_bindings_or_push_error(bindings, cache);
20372063

20382064
if let Some(mutability) = self.offset {
20392065
field_type = ref_of(mutability, field_type, cache);

0 commit comments

Comments
 (0)