Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
sem: rework instantiation of object types (#876)
## Summary Rework the instantiation logic for generic object types, and also improve the early detection of illegal inheritance. The goal is to make generic object types behave the same in all situations and to have the implementation in a single place. **Breaking changes:** * the base type for an object may only use a single `ref`/`ptr` indirection; inheriting from a type like `ref ref Object` is now disallowed **Fixes for parametric object types where none of the parameters are used in the body:** * type-bound operators for them now work the same as for other parametric object types, instead of all type instances using the same set of procedures * the static `of` operator doesn't treat all type instances thereof as the same type * the dynamic `of` operator doesn't treat all type instance thereof as the same type, but only when using either the JS or VM backend; the problem still remains for the C backend * `requiresInit` is now correctly inferred for them **Other fixes:** * `privateAccess Typ[int]` no longer allows access to the fields of all `Typ` type instances * generic `object` types are now consistently checked for duplicate field names, regardless of how they're instantiated * sharing field names with fields inside unused `when` branches now works consistently for generic `object` types * the VM doesn't crash anymore when accessing the field of an indirectly instantiated `object` where the base type is not known early (e.g., because its a generic parameter) * illegal inheritance (e.g., base is not an object) is now rejected in all cases, regardless of a generic `object` type is instantiated * the compiler doesn't crash anymore when inheriting from an unresolved generic `object` type that has a type parameter as the base type * "inheritance only works with non-final object type" errors are not reported when the specified base type is not an `object` type **Improvements:** * illegal inheritance for generic object types is now detected at definition-time in more complex cases ## Details ### Definition analysis * add the `skipToObject` procedure to the `types` module. It replaces the previously used `skipGenericInvocation`, by default doesn't skip over more than one `ref|ptr` indirection, and also supports retrieving the object type in cases such as `Wrapper[T] = T; Wrapper[Obj[T]]` * only add the fields of the base type(s) to the `check` set if the base is a concrete type (the comment stating that it already worked like this was incorrect) * report an "is not a concrete type" error when the object type is not generic and the type specified as the base is a meta type By using the `skipToObject` in `semObjectNode`, more cases of illegal inheritance are detected on type definition and only a single `ptr|ref` indirection is allowed, which enforces the updated inheritance rules. Contrary to what the comment in `semObjectNode` stated, the fields of the base type were added for checking (via `addInheritedFields`), even when the specified base type is generic, which resulted in both crashes (when one of the base type's base types was a `tyGenericParam`) and duplicate field names that could be legal depending on the type arguments being rejected too eagerly. ### Instantiation rework Many of the bugs regarding generic object types were caused by their processing being scattered across multiple procedures (`semGeneric`, `replaceTypeVarsTAux`, and `generateTypeInstance`), where not all of them are called in all situations. For example, the most proper handling (`semGeneric`) was only used when a generic is invoked with concrete type arguments (e.g., `Generic[int]`). To fix this the scattered logic is all moved into a single procedure (`instantiate`), which replaces the `replaceTypeVarsT` call in `handleGenericInvocation`. Since `handleGenericInvocation` is called for all generic invocations, this makes sure that instantiation always works the same. The new `instantiate` procedure also makes it possible to, in the future, customize the behaviour for other types created through invocations. For `object` types, `instantiate` also verifies that the resolved base type is valid. `replaceTypeVarsT` did not, meaning that object types not instantiated via `semGeneric` could previously use, after type parameter are replaced, any non-meta type as the base type without the compiler complaining (although the compiler would have most likely crashed later on). The `replaceObjBranches`, `recomputeFieldPositions`, and `semObjectTypeForInheritedGenericInst` procedures together with the `nkRecWhen` handling from `replaceTypeVarsN` are all merged together into the new `instantiateRecord` procedure -- `addInheritedFields` and `addInheritedFieldsAux` are simplified and moved into the `semtypinst` module. This means that `object` types are now always instantiated in the same way, regardless of which path led up to the `handleGenericInvocation` call. There are two other important changes: 1. a new instance is always created, even if the `object` type doesn't reference any type parameters in its body 2. all instantiated `object` types get a unique `PSym` instead of re- using the generic type's one The consequences are as follows. For the first point: * `sameType` fully consider phantom object types now, which means that `inheritanceDiff` (used for computing the inheritance relationship between two objects outside of `sigmatch`) does too * the `typeInst` field for all phantom object types where none of the parameters are used in the body points to the correct `tyGenericInst`, meaning that `hashType` (which always uses `typeInst`, if available) works correctly for all phantom object types * the run-time object type identification for both VM and JS backend is based on the `PType`'s ID, meaning that the `of` operator for both backends now considers all phantom object types For the second point: * the `typ` field for a `PSym` of an instantiated `object` type now points back to the instantiated type instead of the generic type * the `owner` for the fields of an instantiated `object` type points to instantiated type instead of the originating-from generic type With `hashType` now always producing different hashes for different phantom `object` types, each one gets its own set of type-bound operators lifted. An optimization is possible (and implemented): since each `tyObject` `PType` is guaranteed to be unique now, the canonicalization through `hashType` is unnecessary and can be elided. ### Adjusted `privateAccess` Since all instantiated object types now use a unique symbol instead of the symbol of their originating-from `tyGenericBody`, `semPrivateAccess` as currently implemented doesn't work anymore: the symbol of the generic object (retrieved by `toObjectFromRefPtrGeneric`) is added to the "allow access" list, but the `owner` of the instances' fields now points to the instantiated type. `toObjectFromRefPtrGeneric` is changed to retrieve the symbol of the instance (if the provided type is a `tyGenericInst`), restoring the basic functionality. For restoring support for "allow access to all instances of generic type" , `fieldVisible`, for instantiated types coming from generics, also checks if the symbol of the generic type is in the "allow access" list. ### Changes to tests * add tests for the fixed issues * create a single test file for illegal inheritance detection (`tillegal_base_type.nim`) and merge the `tparameterizedparent0.nim` and `tparameterizedparent1.nim` tests into it --------- Co-authored-by: Saem Ghani <saemghani+github@gmail.com> Co-authored-by: Clyybber <darkmine956@gmail.com>
- Loading branch information