nim-lang / Nim

Nim is a statically typed compiled systems programming language. It combines successful concepts from mature languages like Python, Ada and Modula. Its design focuses on efficiency, expressiveness, and elegance (in that order of priority).
https://nim-lang.org
Other
16.63k stars 1.47k forks source link

always reinstantiate nominal values of generic instantiations #24425

Closed metagn closed 1 week ago

metagn commented 2 weeks ago

fixes #22479, fixes #24374, depends on #24429 and #24430

When instantiating generic types which directly have nominal types (object, distinct, ref/ptr object but not enums[^1]) as their values, the nominal type is now copied (in the case of ref objects, its child as well) so that it receives a fresh ID and typeInst field. Previously this only happened if it contained any generic types in its structure, as is the case for all other types.

This solves #22479 and #24374 by virtue of the IDs being unique, which is what destructors check for. Technically types containing generic param fields work for the same reason. There is also the benefit that the typeInst field is correct. However issues like #22445 aren't solved because the compiler still uses structural object equality checks for inheritance etc. which could be removed in a later PR.

Also fixes a pre-existing issue where destructors bound to object types with generic fields would not error when attempting to define a user destructor after the fact, but the error message doesn't show where the implicit destructor was created now since it was only created for another instance. To do this, a type flag is used that marks the generic type symbol when a generic instance has a destructor created. Reusing tfCheckedForDestructor for this doesn't work.

Maybe there is a nicer design that isn't an overreliance on the ID mechanism, but the shortcomings of tyGenericInst are too ingrained in the compiler to use for this. I thought about maybe adding something like tyNominalGenericInst, but it's really much easier if the nominal type itself directly contains the information of its generic parameters, or at least its "symbol", which the design is heading towards.

[^1]: See this test in enumutils. The field symbols b0/b1 always have the uninstantiated type B because enum fields don't expect to be generic, so no generic instance of B matches its own symbols. Wouldn't expect anyone to use generic enums but maybe someone does.

metagn commented 2 weeks ago

Hits a pre-existing bug:

type
  Foo[T] = object of RootObj
    x: T
  Bar = object of Foo[int]

proc foo(x: Foo) = discard

foo(Bar())

This works if x: T is removed before this PR.

Same for tinvalid_rebind, this compiles but shouldn't:

type Foo[T] = object
  x: T

proc main =
  var f: Foo[int]

proc `=destroy`[T](f: var Foo[T]) =
  # should say: cannot bind another '=destroy' to: Foo
  discard

This one is lower priority though IMO

metagn commented 2 weeks ago

Will try to split the fixes for the above pre-existing issues as well as the one in jsonutils into their own PRs, as I don't think they require the main change. But I might be wrong.

metagn commented 1 week ago

Did not move the fix for tinvalid_rebind to its own PR since it removes information from the error message, and this PR is already related to destructors. I know not skipping tyGenericInst instead was the intended solution but I can't think of how this wouldn't be beneficial.

github-actions[bot] commented 1 week ago

Thanks for your hard work on this PR! The lines below are statistics of the Nim compiler built from 05c74d684481733c507dd18a7da8a296289ca32d

Hint: mm: orc; opt: speed; options: -d:release 177779 lines; 8.891s; 653.508MiB peakmem