Skip to content

Commit

Permalink
Reintroduce Self types
Browse files Browse the repository at this point in the history
This reintroduces Self types after they were removed as part of the
0.11.0 release. This new setup is much simpler, less buggy (in part due
to that simplicity), and allows us to solve various soundness issues
that the lack of Self types either introduced or made difficult to
resolve otherwise.

This fixes #643, fixes
#787 and fixes
#811.

Changelog: added
  • Loading branch information
yorickpeterse committed Feb 11, 2025
1 parent a3a15ed commit a19cd2c
Show file tree
Hide file tree
Showing 31 changed files with 999 additions and 212 deletions.
52 changes: 50 additions & 2 deletions compiler/src/hir.rs

Large diffs are not rendered by default.

7 changes: 6 additions & 1 deletion compiler/src/mir/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2287,7 +2287,12 @@ impl Mir {
if let Some(stype) = tid.specialization_key(&state.db).self_type
{
let self_node = state.dependency_graph.add_module(
stype.instance_of().module(&state.db).name(&state.db),
stype
.as_type_instance()
.unwrap()
.instance_of()
.module(&state.db)
.name(&state.db),
);

state
Expand Down
58 changes: 37 additions & 21 deletions compiler/src/mir/specialize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@ use indexmap::{IndexMap, IndexSet};
use std::collections::{HashMap, HashSet, VecDeque};
use std::mem::swap;
use types::check::TypeChecker;
use types::format::format_type;
use types::specialize::{ordered_shapes_from_map, TypeSpecializer};
use types::{
Block as _, Database, InternedTypeArguments, MethodId, Shape,
TypeArguments, TypeId, TypeInstance, TypeParameterId, TypeRef, CALL_METHOD,
DECREMENT_METHOD, DROPPER_METHOD, INCREMENT_METHOD,
TypeArguments, TypeEnum, TypeId, TypeInstance, TypeParameterId, TypeRef,
CALL_METHOD, DECREMENT_METHOD, DROPPER_METHOD, INCREMENT_METHOD,
};

fn argument_shape(
Expand All @@ -34,7 +35,7 @@ fn specialize_constants(

// Constants never need access to the self type, so we just use a dummy
// value here.
let stype = TypeInstance::new(TypeId::nil());
let stype = TypeEnum::TypeInstance(TypeInstance::new(TypeId::nil()));

for &id in mir.constants.keys() {
let old_typ = id.value_type(db);
Expand Down Expand Up @@ -97,7 +98,7 @@ fn shapes_compatible_with_bounds(

struct Job {
/// The type of `self` within the method.
self_type: TypeInstance,
self_type: TypeEnum,

/// The ID of the method that's being specialized.
method: MethodId,
Expand All @@ -124,7 +125,7 @@ impl Work {

fn push(
&mut self,
self_type: TypeInstance,
self_type: TypeEnum,
method: MethodId,
shapes: HashMap<TypeParameterId, Shape>,
) -> bool {
Expand Down Expand Up @@ -228,7 +229,7 @@ impl DynamicCalls {

/// A compiler pass that specializes generic types.
pub(crate) struct Specialize<'a, 'b> {
self_type: TypeInstance,
self_type: TypeEnum,
method: MethodId,
state: &'a mut State,
work: &'b mut Work,
Expand Down Expand Up @@ -276,7 +277,11 @@ impl<'a, 'b> Specialize<'a, 'b> {
let main_method = state.db.main_method().unwrap();
let main_mod = main_type.module(&state.db);

work.push(TypeInstance::new(main_type), main_method, HashMap::new());
work.push(
TypeEnum::TypeInstance(TypeInstance::new(main_type)),
main_method,
HashMap::new(),
);

// The main() method isn't called explicitly, so we have to manually
// record it in the main type.
Expand Down Expand Up @@ -852,11 +857,20 @@ impl<'a, 'b> Specialize<'a, 'b> {
type_arguments: Option<&TypeArguments>,
) -> Instruction {
let typ = receiver.instance_of();
let method_impl = typ

let Some(method_impl) = typ
.specialization_source(&self.state.db)
.unwrap_or(typ)
.method(&self.state.db, call.method.name(&self.state.db))
.unwrap();
else {
panic!(
"can't devirtualize call to {}.{} in {}.{}",
receiver.instance_of().name(&self.state.db),
call.method.name(&self.state.db),
format_type(&self.state.db, self.self_type),
self.method.name(&self.state.db),
);
};

let mut shapes = type_arguments
.map(|args| self.type_argument_shapes(call.method, args))
Expand Down Expand Up @@ -899,18 +913,18 @@ impl<'a, 'b> Specialize<'a, 'b> {
type_id: TypeId,
method: MethodId,
shapes: &HashMap<TypeParameterId, Shape>,
custom_self_type: Option<TypeInstance>,
custom_self_type: Option<TypeEnum>,
) -> MethodId {
let ins = TypeInstance::new(type_id);
let stype = custom_self_type.unwrap_or(ins);
let stype = custom_self_type.unwrap_or(TypeEnum::TypeInstance(ins));

// Regular methods on regular types don't need to be specialized.
if !type_id.is_generic(&self.state.db)
&& !type_id.is_closure(&self.state.db)
&& !method.is_generic(&self.state.db)
{
if self.work.push(stype, method, shapes.clone()) {
self.update_method_type(method, shapes);
self.update_method_type(stype, method, shapes);
self.regular_methods.push(method);
}

Expand Down Expand Up @@ -959,7 +973,7 @@ impl<'a, 'b> Specialize<'a, 'b> {

for name in methods {
let method = type_id.method(&self.state.db, name).unwrap();
let stype = TypeInstance::new(type_id);
let stype = TypeEnum::TypeInstance(TypeInstance::new(type_id));

if self.work.push(stype, method, HashMap::new()) {
self.regular_methods.push(method);
Expand All @@ -980,7 +994,7 @@ impl<'a, 'b> Specialize<'a, 'b> {
let stype = if type_id.is_closure(&self.state.db) {
self.self_type
} else {
TypeInstance::new(type_id)
TypeEnum::TypeInstance(TypeInstance::new(type_id))
};

if original == type_id {
Expand Down Expand Up @@ -1021,7 +1035,7 @@ impl<'a, 'b> Specialize<'a, 'b> {
let method = original.method(&self.state.db, name).unwrap();

if original == type_id {
let stype = TypeInstance::new(type_id);
let stype = TypeEnum::TypeInstance(TypeInstance::new(type_id));

if self.work.push(stype, method, HashMap::new()) {
self.regular_methods.push(method);
Expand Down Expand Up @@ -1089,14 +1103,15 @@ impl<'a, 'b> Specialize<'a, 'b> {

let new = method.clone_for_specialization(&mut self.state.db);
let old_ret = method.return_type(&self.state.db);
let stype = receiver.as_type_enum(&self.state.db).unwrap();

for arg in method.arguments(&self.state.db) {
let arg_type = TypeSpecializer::new(
&mut self.state.db,
self.interned,
shapes,
&mut self.types,
self.self_type,
stype,
)
.specialize(arg.value_type);

Expand All @@ -1107,7 +1122,7 @@ impl<'a, 'b> Specialize<'a, 'b> {
self.interned,
shapes,
&mut self.types,
self.self_type,
stype,
)
.specialize(raw_var_type);

Expand All @@ -1125,7 +1140,7 @@ impl<'a, 'b> Specialize<'a, 'b> {
self.interned,
shapes,
&mut self.types,
self.self_type,
stype,
)
.specialize(old_ret);

Expand All @@ -1150,6 +1165,7 @@ impl<'a, 'b> Specialize<'a, 'b> {
/// return types are specialized (if needed).
fn update_method_type(
&mut self,
self_type: TypeEnum,
method: MethodId,
shapes: &HashMap<TypeParameterId, Shape>,
) {
Expand All @@ -1161,7 +1177,7 @@ impl<'a, 'b> Specialize<'a, 'b> {
self.interned,
shapes,
&mut self.types,
self.self_type,
self_type,
)
.specialize(arg.value_type);

Expand All @@ -1171,7 +1187,7 @@ impl<'a, 'b> Specialize<'a, 'b> {
self.interned,
shapes,
&mut self.types,
self.self_type,
self_type,
)
.specialize(raw_var_type);

Expand All @@ -1189,7 +1205,7 @@ impl<'a, 'b> Specialize<'a, 'b> {
self.interned,
shapes,
&mut self.types,
self.self_type,
self_type,
)
.specialize(old_ret);

Expand Down
7 changes: 6 additions & 1 deletion compiler/src/symbol_names.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,12 @@ fn format_type_base_name(db: &Database, id: TypeId, name: &mut String) {
}

let loc = id.location(db);
let stype = id.specialization_key(db).self_type.unwrap().instance_of();
let stype = id
.specialization_key(db)
.self_type
.and_then(|e| e.as_type_instance())
.unwrap()
.instance_of();

// The exact format used here doesn't really matter, but we try to keep it
// somewhat readable for use in external tooling (e.g. a profiler that
Expand Down
1 change: 1 addition & 0 deletions compiler/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ pub(crate) fn hir_type_name(
location: Location,
) -> hir::TypeName {
hir::TypeName {
self_type: false,
source: None,
resolved_type: TypeRef::Unknown,
name: hir::Constant { name: name.to_string(), location },
Expand Down
15 changes: 0 additions & 15 deletions compiler/src/type_check/define_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1875,21 +1875,6 @@ mod tests {
assert_eq!(error.location(), &cols(33, 46));
}

#[test]
fn test_define_field_with_self_type() {
let mut state = State::new(Config::new());
let mut modules = parse(&mut state, "type Person { let @name: Self }");

DefineTypes::run_all(&mut state, &mut modules);

assert!(!DefineFields::run_all(&mut state, &mut modules));

let error = state.diagnostics.iter().next().unwrap();

assert_eq!(error.id(), DiagnosticId::InvalidSymbol);
assert_eq!(error.location(), &cols(26, 29));
}

#[test]
fn test_define_trait_type_parameter() {
let mut state = State::new(Config::new());
Expand Down
Loading

0 comments on commit a19cd2c

Please sign in to comment.