8000 Stack overflow when specializing generic type instances · Issue #851 · inko-lang/inko · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content
8000

Stack overflow when specializing generic type instances #851

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
yorickpeterse opened this issue Mar 26, 2025 · 13 comments
Open

Stack overflow when specializing generic type instances #851

yorickpeterse opened this issue Mar 26, 2025 · 13 comments
Assignees
Labels
accepting contributions Issues that are suitable to be worked on by anybody, not just maintainers bug Defects, unintended behaviour, etc compiler Changes related to the compiler
Milestone

Comments

@yorickpeterse
Copy link
Collaborator

Please describe the bug

While refactoring std.optparse I ran into the following stack overflow:

[...]
#364 0x00005555591f190f in types::specialize::TypeSpecializer::specialize_generic_instance (self=..., ins=...) at specialize.rs:338
#365 0x00005555591f1221 in types::specialize::TypeSpecializer::specialize_type_instance (self=..., ins=...) at specialize.rs:263
#366 0x00005555591f10d4 in types::specialize::TypeSpecializer::specialize (self=..., value=...) at specialize.rs:148
#367 0x00005555591ff982 in types::specialize::{impl#0}::specialize_generic_instance::{closure#0} (p=...) at specialize.rs:343
#368 0x000055555920eb85 in core::iter::adapters::map::map_fold::{closure#0}<types::TypeParameterId, types::Shape, (), types::specialize::{impl#0}::specialize_generic_instance::{closure_env#0}, cor
e::iter::traits::iterator::Iterator::for_each::call::{closure_env#0}<types::Shape, alloc::vec::{impl#19}::extend_trusted::{closure_env#0}<types::Shape, alloc::alloc::Global, core::iter::adapters::
map::Map<alloc::vec::into_iter::IntoIter<types::TypeParameterId, alloc::alloc::Global>, types::specialize::{impl#0}::specialize_generic_instance::{closure_env#0}>>>> (acc=..., elt=...)
    at map.rs:89
#369 0x00005555591ede3e in core::iter::traits::iterator::Iterator::fold<alloc::vec::into_iter::IntoIter<types::TypeParameterId, alloc::alloc::Global>, (), core::iter::adapters::map::map_fold::{clo
sure_env#0}<types::TypeParameterId, types::Shape, (), types::specialize::{impl#0}::specialize_generic_instance::{closure_env#0}, core::iter::traits::iterator::Iterator::for_each::call::{closure_en
v#0}<types::Shape, alloc::vec::{impl#19}::extend_trusted::{closure_env#0}<types::Shape, alloc::alloc::Global, core::iter::adapters::map::Map<alloc::vec::into_iter::IntoIter<types::TypeParameterId,
 alloc::alloc::Global>, types::specialize::{impl#0}::specialize_generic_instance::{closure_env#0}>>>>> (self=..., init=..., f=...) at iterator.rs:2587
#370 0x000055555920e08d in core::iter::adapters::map::{impl#2}::fold<types::Shape, alloc::vec::into_iter::IntoIter<types::TypeParameterId, alloc::alloc::Global>, types::specialize::{impl#0}::speci
alize_generic_instance::{closure_env#0}, (), core::iter::traits::iterator::Iterator::for_each::call::{closure_env#0}<types::Shape, alloc::vec::{impl#19}::extend_trusted::{closure_env#0}<types::Sha
pe, alloc::alloc::Global, core::iter::adapters::map::Map<alloc::vec::into_iter::IntoIter<types::TypeParameterId, alloc::alloc::Global>, types::specialize::{impl#0}::specialize_generic_instance::{c
losure_env#0}>>>> (self=..., init=..., g=...) at map.rs:129
#371 0x000055555920e7a6 in core::iter::traits::iterator::Iterator::for_each<core::iter::adapters::map::Map<alloc::vec::into_iter::IntoIter<types::TypeParameterId, alloc::alloc::Global>, types::spe
cialize::{impl#0}::specialize_generic_instance::{closure_env#0}>, alloc::vec::{impl#19}::extend_trusted::{closure_env#0}<types::Shape, alloc::alloc::Global, core::iter::adapters::map::Map<alloc::v
ec::into_iter::IntoIter<types::TypeParameterId, alloc::alloc::Global>, types::specialize::{impl#0}::specialize_generic_instance::{closure_env#0}>>> (self=..., f=...) at iterator.rs:817
#372 0x0000555559239467 in alloc::vec::Vec<types::Shape, alloc::alloc::Global>::extend_trusted<types::Shape, alloc::alloc::Global, core::iter::adapters::map::Map<alloc::vec::into_iter::IntoIter<ty
pes::TypeParameterId, alloc::alloc::Global>, types::specialize::{impl#0}::specialize_generic_instance::{closure_env#0}>> (self=..., iterator=...) at mod.rs:3020
#373 0x000055555924376b in alloc::vec::spec_extend::{impl#1}::spec_extend<types::Shape, core::iter::adapters::map::Map<alloc::vec::into_iter::IntoIter<types::TypeParameterId, alloc::alloc::Global>
, types::specialize::{impl#0}::specialize_generic_instance::{closure_env#0}>, alloc::alloc::Global> (self=..., iterator=...) at spec_extend.rs:26
#374 0x0000555559238395 in alloc::vec::spec_from_iter_nested::{impl#1}::from_iter<types::Shape, core::iter::adapters::map::Map<alloc::vec::into_iter::IntoIter<types::TypeParameterId, alloc::alloc:
:Global>, types::specialize::{impl#0}::specialize_generic_instance::{closure_env#0}>> (iterator=...) at spec_from_iter_nested.rs:62
#375 0x000055555923eb92 in alloc::vec::in_place_collect::{impl#1}::from_iter<types::Shape, core::iter::adapters::map::Map<alloc::vec::into_iter::IntoIter<types::TypeParameterId, alloc::alloc::Glob
al>, types::specialize::{impl#0}::specialize_generic_instance::{closure_env#0}>> (iterator=...) at in_place_collect.rs:237
#376 0x0000555559243529 in alloc::vec::{impl#14}::from_iter<types::Shape, core::iter::adapters::map::Map<alloc::vec::into_iter::IntoIter<types::TypeParameterId, alloc::alloc::Global>, types::speci
alize::{impl#0}::specialize_generic_instance::{closure_env#0}>> (iter=...) at mod.rs:2894
#377 0x000055555920e6be in core::iter::traits::iterator::Iterator::collect<core::iter::adapters::map::Map<alloc::vec::into_iter::IntoIter<types::TypeParameterId, alloc::alloc::Global>, types::spec
ialize::{impl#0}::specialize_generic_instance::{closure_env#0}>, alloc::vec::Vec<types::Shape, alloc::alloc::Global>> (self=...) at iterator.rs:2003
#378 0x00005555591f190f in types::specialize::TypeSpecializer::specialize_generic_instance (self=..., ins=...) at specialize.rs:338
#379 0x00005555591f1221 in types::specialize::TypeSpecializer::specialize_type_instance (self=..., ins=...) at specialize.rs:263
#380 0x00005555591f10d4 in types::specialize::TypeSpecializer::specialize (self=..., value=...) at specialize.rs:148
#381 0x00005555591ff982 in types::specialize::{impl#0}::specialize_generic_instance::{closure#0} (p=...) at specialize.rs:343
[...]

The input file is as follows:

import std.env
import std.fmt (fmt)
import std.optparse (Options)
import std.stdio (Stderr, Stdout)
import std.sys

type async Main {
  fn async main {
    let opts = Options.new('example')

    opts.description = 'This is an example program to showcase the optparse library.'

    opts.flag('h', 'help', 'Show this help message')
    opts.single('c', 'config', 'PATH', 'Use a custom configuration file')
    opts.multiple('i', 'include', 'DIR', 'Add the directory')
    opts.multiple('I', 'ignore', '', 'Ignore something')
    opts.flag('', 'verbose', 'Use verbose output,\nlorem ipsum')
    opts.flag('', 'example', '')
    opts.flag('x', '', 'Foo')

    let matches = match opts.parse(env.arguments) {
      case Ok(v) -> v
      case Error(e) -> {
        let _ = Stderr.new.print('test: ${e}')

        sys.exit(1)
      }
    }

    if matches.contains?('help') {
      Stdout.new.write(opts.to_string)
    } else {
      Stdout.new.print(fmt(matches.remaining))
    }
  }
}

The standard library state is as follows:

diff --git a/std/src/std/optparse.inko b/std/src/std/optparse.inko
index 77f90add..9bb4ac42 100644
--- a/std/src/std/optparse.inko
+++ b/std/src/std/optparse.inko
@@ -35,7 +35,7 @@
 #
 # type async Main {
 #   fn async main {
-#     let opts = Options.new
+#     let opts = Options.new('example')
 #
 #     opts.flag('h', 'help', 'Show this help message')
 #
@@ -64,7 +64,7 @@
 #
 # type async Main {
 #   fn async main {
-#     let opts = Options.new
+#     let opts = Options.new('example')
 #
 #     opts.flag('h', 'help', 'Show this help message')
 #     opts.stop_at_first_non_option = true
@@ -84,18 +84,20 @@
 #
 # # Help messages
 #
-# Help messages are generated using the `Help` type:
+# Help messages are generated using `Options.to_string`:
 #
 # ```inko
 # import std.env
 # import std.fmt (fmt)
-# import std.optparse (Help, Options)
+# import std.optparse (Options)
 # import std.stdio (Stderr, Stdout)
 # import std.sys
 #
 # type async Main {
 #   fn async main {
-#     let opts = Options.new
+#     let opts = Options.new('example')
+#
+#     opts.description = 'This is an example program to showcase the optparse library.'
 #
 #     opts.flag('h', 'help', 'Show this help message')
 #     opts.single('c', 'config', 'PATH', 'Use a custom configuration file')
@@ -108,24 +110,14 @@
 #     let matches = match opts.parse(env.arguments) {
 #       case Ok(v) -> v
 #       case Error(e) -> {
-#         Stderr.new.print('test: ${e}')
+#         let _ = Stderr.new.print('test: ${e}')
+#
 #         sys.exit(1)
 #       }
 #     }
 #
 #     if matches.contains?('help') {
-#       let help = Help
-#         .new('test')
-#         .description(
-#           'This is an example program to showcase the optparse library.',
-#         )
-#         .section('Examples')
-#         .line('test --help')
-#         .section('Options')
-#         .options(opts)
-#         .to_string
-#
-#       Stdout.new.write(help)
+#       Stdout.new.write(opts.to_string)
 #     } else {
 #       Stdout.new.print(fmt(matches.remaining))
 #     }
@@ -136,14 +128,10 @@
 # When running this program with the `--help` option, the output is as follows:
 #
 # ```
-# Usage: test [OPTIONS]
+# Usage: example [OPTIONS]
 #
 # This is an example program to showcase the optparse library.
 #
-# Examples:
-#
-#   test --help
-#
 # Options:
 #
 #   -h, --help           Show this help message
@@ -537,8 +525,19 @@ type Opt {
   let @hint: String
 }
 
-# A type that describes the options to parse.
+# A type for defining and parsing command-line options and arguments.
 type pub Options {
+  # The name of the program.
+  let pub @name: String
+
+  # The usage section of the help message.
+  #
+  # This field defaults to the value `'[OPTIONS]'`.
+  let pub mut @usage: String
+
+  # The description of the command.
+  let pub mut @description: String
+
   # All options that have been defined.
   let @options: Array[Opt]
 
@@ -553,9 +552,18 @@ type pub Options {
   # definitions.
   let @mapping: Map[String, ref Opt]
 
-  # Returns a new empty `Options`.
-  fn pub static new -> Options {
-    Options(mapping: Map.new, options: [], stop_at_first_non_option: false)
+  # Returns a new `Options` value.
+  #
+  # The `name` argument is the name of the program.
+  fn pub static new(name: String) -> Options {
+    Options(
+      name: name,
+      usage: '[OPTIONS]',
+      description: '',
+      mapping: Map.new,
+      options: [],
+      stop_at_first_non_option: false,
+    )
   }
 
   # Adds a boolean option that can be specified once.
@@ -836,14 +844,19 @@ impl ToString for Options {
       if chars > max { chars } else { max }
     })
 
-    let buf = StringBuffer.new
+    let buf = StringBuffer.from_array(['Usage: ', @name, ' ', @usage, '\n'])
 
-    for (idx, opt) in opts.into_iter.with_index {
-      let desc = descs.get(idx).or_panic
+    if @description.size > 0 {
+      buf.push('\n')
+      buf.push(@description)
+      buf.push('\n')
+    }
+
+    buf.push('\nOptions:\n\n')
+
+    for (opt, desc) in opts.into_iter.zip(descs.into_iter) {
       let has_desc = desc.size > 0
 
-      if idx > 0 { buf.push('\n') }
-
       buf.push(INDENT)
 
       if has_desc {
@@ -860,6 +873,8 @@ impl ToString for Options {
       } else {
         buf.push(opt)
       }
+
+      buf.push('\n')
     }
 
     buf.into_string

Operating system

Fedora

Inko version

main

@yorickpeterse yorickpeterse added accepting contributions Issues that are suitable to be worked on by anybody, not just maintainers bug Defects, unintended behaviour, etc compiler Changes related to the compiler labels Mar 26, 2025
@yorickpeterse
Copy link
Collaborator Author

The following standalone program reproduces this stack overflow:

type async Main {
  fn async main {
    let a = ['foo', 'bar']
    let b = ['baz', 'quix']

    a.into_iter.zip(b.into_iter)
  }
}

@yorickpeterse yorickpeterse added this to the 0.19.0 milestone Mar 26, 2025
@yorickpeterse
Copy link
Collaborator Author

An alternative an even simpler way to reproduce the problem:

type async Main {
  fn async main {
    Option.Some('foo').zip(Option.Some('bar'))
  }
}

@yorickpeterse
Copy link
Collaborator Author

Looking at the code, we seem to be running into a case where we're specializing some type T which has a type parameter P assigned to the same T. This then results in us getting stuck in recursion because we recurse before generating the final specialized instance.

The challenge here is that we can't generate these final instances ahead of time as doing so requires the specialization key to be present, but we can't generate the key because we get stuck in a recursive type.

The first step here is figuring out what exactly the recursive type is, and if we should be running into such recursive types in the first place.

@yorickpeterse
Copy link
Collaborator Author

The recursions seems to involve one type parameter being assigned to another, which then leads back to the former:

diff --git a/types/src/specialize.rs b/types/src/specialize.rs
index ee9df306..f7cddee1 100644
--- a/types/src/specialize.rs
+++ b/types/src/specialize.rs
@@ -340,6 +340,16 @@ impl<'a, 'b, 'c> TypeSpecializer<'a, 'b, 'c> {
             .into_iter()
             .map(|p| {
                 let raw = args.get(p).unwrap();
+
+                // TODO: remove
+                println!(
+                    "param {} ({:?}) = type {} ({:?})",
+                    crate::format::format_type(self.db, p),
+                    p,
+                    crate::format::format_type(self.db, raw),
+                    raw
+                );
+
                 let typ = self.specialize(raw);
 
                 args.assign(p, typ);

Produces:

...
param A (TypeParameterId(46)) = type T (Any(RigidTypeParameter(TypeParameterId(42))))
param A (TypeParameterId(46)) = type T (Any(RigidTypeParameter(TypeParameterId(42))))
param A (TypeParameterId(46)) = type T (Any(RigidTypeParameter(TypeParameterId(42))))
param A (TypeParameterId(46)) = type T (Any(RigidTypeParameter(TypeParameterId(42))))
param A (TypeParameterId(46)) = type T (Any(RigidTypeParameter(TypeParameterId(42))))
param A (TypeParameterId(46)) = type T (Any(RigidTypeParameter(TypeParameterId(42))))

@yorickpeterse
Copy link
Collaborator Author

I did some further digging using this patch:

diff --git a/types/src/specialize.rs b/types/src/specialize.rs
index ee9df306..977439bb 100644
--- a/types/src/specialize.rs
+++ b/types/src/specialize.rs
@@ -335,11 +335,30 @@ impl<'a, 'b, 'c> TypeSpecializer<'a, 'b, 'c> {
         }
 
         let mut args = ins.type_arguments(self.db).unwrap().clone();
+
+        println!(
+            "\ntype   = {} ({:?})\nshapes = {:?}\nargs   = {:?}",
+            crate::format::format_type(self.db, ins),
+            ins,
+            self.shapes,
+            args,
+        );
+
         let mut shapes: Vec<Shape> = typ
             .type_parameters(self.db)
             .into_iter()
             .map(|p| {
                 let raw = args.get(p).unwrap();

8000
+
+                // TODO: remove
+                println!(
+                    "  {} ({:?}) = {} ({:?})",
+                    crate::format::format_type(self.db, p),
+                    p,
+                    crate::format::format_type(self.db, raw),
+                    raw,
+                );
+
                 let typ = self.specialize(raw);
 
                 args.assign(p, typ);

This produces:

...
type   = (T, O) (TypeInstance { instance_of: TypeId(6), type_arguments: 1924 })
shapes = {TypeParameterId(42): Inline(TypeInstance { instance_of: TypeId(6), type_arguments: 1924 }), TypeParameterId(302): String}
args   = TypeArguments { mapping: {TypeParameterId(46): Any(RigidTypeParameter(TypeParameterId(42))), TypeParameterId(47): Any(RigidTypeParameter(TypeParameterId(302)))} }
  A (TypeParameterId(46)) = T (Any(RigidTypeParameter(TypeParameterId(42))))

type   = (T, O) (TypeInstance { instance_of: TypeId(6), type_arguments: 1924 })
shapes = {TypeParameterId(42): Inline(TypeInstance { instance_of: TypeId(6), type_arguments: 1924 }), TypeParameterId(302): String}
args   = TypeArguments { mapping: {TypeParameterId(46): Any(RigidTypeParameter(TypeParameterId(42))), TypeParameterId(47): Any(RigidTypeParameter(TypeParameterId(302)))} }
  A (TypeParameterId(46)) = T (Any(RigidTypeParameter(TypeParameterId(42))))

type   = (T, O) (TypeInstance { instance_of: TypeId(6), type_arguments: 1924 })
shapes = {TypeParameterId(42): Inline(TypeInstance { instance_of: TypeId(6), type_arguments: 1924 }), TypeParameterId(302): String}
args   = TypeArguments { mapping: {TypeParameterId(46): Any(RigidTypeParameter(TypeParameterId(42))), TypeParameterId(47): Any(RigidTypeParameter(TypeParameterId(302)))} }
  A (TypeParameterId(46)) = T (Any(RigidTypeParameter(TypeParameterId(42))))

type   = (T, O) (TypeInstance { instance_of: TypeId(6), type_arguments: 1924 })
shapes = {TypeParameterId(42): Inline(TypeInstance { instance_of: TypeId(6), type_arguments: 1924 }), TypeParameterId(302): String}
args   = TypeArguments { mapping: {TypeParameterId(46): Any(RigidTypeParameter(TypeParameterId(42))), TypeParameterId(47): Any(RigidTypeParameter(TypeParameterId(302)))} }
  A (TypeParameterId(46)) = T (Any(RigidTypeParameter(TypeParameterId(42))))

Here it seems that T is assigned (T, O) itself, resulting in infinite recursion. This itself is wrong, so we need to figure out what is causing that.

@yorickpeterse
Copy link
Collaborator Author

If I apply this patch:

diff --git a/std/src/std/tuple.inko b/std/src/std/tuple.inko
index 257a44db..77e16d1a 100644
--- a/std/src/std/tuple.inko
+++ b/std/src/std/tuple.inko
@@ -76,31 +76,31 @@ impl Format for Tuple1 if A: Format {
 }
 
 # A 2-ary tuple.
-type builtin Tuple2[A, B] {
-  let pub @0: A
+type builtin Tuple2[FOO, B] {
+  let pub @0: FOO
   let pub @1: B
 }
 
-impl Equal for Tuple2 if A: Equal, B: Equal {
-  fn pub ==(other: ref Tuple2[A, B]) -> Bool {
+impl Equal for Tuple2 if FOO: Equal, B: Equal {
+  fn pub ==(other: ref Tuple2[FOO, B]) -> Bool {
     @0 == other.0 and @1 == other.1
   }
 }
 
-impl Hash for Tuple2 if A: Hash, B: Hash {
+impl Hash for Tuple2 if FOO: Hash, B: Hash {
   fn pub hash[H: mut + Hasher](hasher: mut H) {
     @0.hash(hasher)
     @1.hash(hasher)
   }
 }
 
-impl Clone for Tuple2 if A: Clone, B: Clone {
-  fn pub clone -> Tuple2[move A, move B] {
+impl Clone for Tuple2 if FOO: Clone, B: Clone {
+  fn pub clone -> Tuple2[move FOO, move B] {
     (@0.clone, @1.clone)
   }
 }
 
-impl Format for Tuple2 if A: Format, B: Format {
+impl Format for Tuple2 if FOO: Format, B: Format {
   fn pub fmt(formatter: mut Formatter) {
     formatter.tuple('').field(@0).field(@1).finish
   }
diff --git a/types/src/specialize.rs b/types/src/specialize.rs
index ee9df306..f8bef771 100644
--- a/types/src/specialize.rs
+++ b/types/src/specialize.rs
@@ -335,11 +335,46 @@ impl<'a, 'b, 'c> TypeSpecializer<'a, 'b, 'c> {
         }
 
         let mut args = ins.type_arguments(self.db).unwrap().clone();
+
+        println!("\n{} ({:?})", crate::format::format_type(self.db, ins), ins,);
+
+        println!("type arguments:");
+
+        for (k, v) in args.pairs() {
+            println!(
+                "  {} = {} ({:?} = {:?})",
+                crate::format::format_type(self.db, k),
+                crate::format::format_type(self.db, v),
+                k,
+                v,
+            );
+        }
+
+        println!("shapes:");
+        for (k, v) in self.shapes {
+            println!(
+                "  {} ({:?}) = {:?}",
+                crate::format::format_type(self.db, *k),
+                k,
+                v,
+            );
+        }
+
         let mut shapes: Vec<Shape> = typ
             .type_parameters(self.db)
             .into_iter()
             .map(|p| {
                 let raw = args.get(p).unwrap();
+
+                // TODO: remove
+                println!(
+                    "  {} ({:?}) = {} ({:?})",
+                    crate::format::format_type(self.db, p),
+                    p,
+                    crate::format::format_type(self.db, raw),
+                    raw,
+                );
+
                 let typ = self.specialize(raw);
 
                 args.assign(p, typ);

The output is as follows:

...
(T, O) (TypeInstance { instance_of: TypeId(6), type_arguments: 1924 })
type arguments:
  FOO = T (TypeParameterId(46) = Any(RigidTypeParameter(TypeParameterId(42))))
  B = O (TypeParameterId(47) = Any(RigidTypeParameter(TypeParameterId(302))))
shapes:
  T (TypeParameterId(42)) = Inline(TypeInstance { instance_of: TypeId(6), type_arguments: 1924 })
  O (TypeParameterId(302)) = String
  FOO (TypeParameterId(46)) = T (Any(RigidTypeParameter(TypeParameterId(42))))

Here FOO is assigned to T which is assigned to (T, O), resulting in the recursion. Why FOO = T isn't clear though.

@yorickpeterse
Copy link
Collaborator Author

To add: the T in (T, O) is the T as defined by Option.

@yorickpeterse
Copy link
Collaborator Author

This problem isn't present in 0.17.1 but is in 0.18.1. I'm guessing this is due to the introduction of stack allocated types and how it changes the specialization logic.

@yorickpeterse
Copy link
Collaborator Author
yorickpeterse commented Mar 26, 2025

Standalone input file that reproduces the issue:

type pub inline enum Opt[OPT_PARAM] {
  case Some(OPT_PARAM)
  case None

  fn pub move zip[O](other: Opt[O]) -> Opt[(OPT_PARAM, O)] {
    match (self, other) {
      case (Some(a), Some(b)) -> Opt.Some((a, b))
      case _ -> Opt.None
    }
  }
}

type async Main {
  fn async main {
    Opt.Some('foo').zip(Opt.Some('bar'))
  }
}

@yorickpeterse
Copy link
Collaborator Author

I'm contemplating if we should just switch to specializing over all types instead of grouping them together.

The idea of grouping was to reduce the amount of generated methods, but I'm not sure that was ever true to begin with. For example, if a method M is specialized over the Owned shape, and at some later point we create an instance of type T, we may have to generate a bunch of methods for it that are called through M because we might pass the instance to M. This can result in many methods being generated that aren't actually called. In contrast, if we specialize by types this won't be the case because every time we specialize M we know exactly what to specialize it over as there's no grouping.

Due to inlining of generic methods, we're also already sort of specializing over types but in an ad-hoc manner, at least if the receiver type is a concrete type. This means that whatever duplication full specialization may cause is already there to some degree.

The specialization setup is also quite complex and has been a source of quite a few bugs. In particular, reducing a set of types to a set of shapes and then expanding them (in case of inline types for example) to types is messy.

If we specialize by all types, I think the code setup as a whole would be a lot easier. Instead of shapes, the specialization keys would just be the (normalized) type arguments. For symbol name generation we'd just use the mangled symbol names of each type, similar to what we do for the Inline shape. If we don't want gigantic symbol names we can always use e.g. Blake3 to hash the names of the generic arguments, such that you end up with something like std.option.Option.zip#123456789abcdef... instead of a ton of gibberish.

With all that said, this would be a pretty big change so I'll have to give it some additional thought first.

@yorickpeterse
Copy link
Collaborator Author

Not directly related, but worth considering: I ran into a new instance of #666 when working on the upcoming std.net.http module:

thread 'main' panicked at types/src/lib.rs:5633:21:
type parameter 'T' (ID: 123) must be assigned a shape
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
fish: Job 1, 'inko test test/std/test_io.inko' terminated by signal SIGABRT (Abort)

Rather than spend hours trying to figure this out and fix it, I think this is yet another reason to move to just full on specialization instead of using shapes.

@yorickpeterse
Copy link
Collaborator Author

Seeing how CI just keeps failing now with duplicate symbol errors, it's time to take a wrecking ball to our specialisation code and A) just specialise over all types instead of shapes B) hopefully fix these symbol errors. This will probably take a few days though.

@yorickpeterse
Copy link
Collaborator Author

As a small update: I started work on this last week, and will continue this week. Progress is a bit slow though, as I'm re-evaluating past decisions related to specialization as I go along.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
accepting contributions Issues that are suitable to be worked on by anybody, not just maintainers bug Defects, unintended behaviour, etc compiler Changes related to the compiler
Projects
None yet
Development

No branches or pull requests

1 participant
0