Skip to content
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

Generic models error: "No base constructor has the specified number of type arguments. ts(2508)" #358

Closed
sisp opened this issue Dec 18, 2021 · 10 comments
Labels
🐛 bug Something isn't working 🤞 maybe fixed? Maybe it is fixed 🎈 released A fix that should close the issue has been released

Comments

@sisp
Copy link
Contributor

sisp commented Dec 18, 2021

I've stumbled over a TS typing issue related to generic models where I am getting the error

No base constructor has the specified number of type arguments. ts(2508)

on the Model(...) call. I've isolated the problem to the following reduced example:

type ValueType = number | string

class Value<T extends DataType> extends Model(<T extends DataType>() => ({
  data: prop<T>(),
}))<T> {}

class Container<V extends Value<T>, T extends DataType> extends Model(< // <--- ERROR
  V extends Value<T>,
  T extends DataType
>() => ({
  value: prop<V>(),
}))<V, T> {}

It doesn't seem to be caused by:

  • more than one generic type parameter (because there's a test with two type parameters that succeeds)
  • simple dependence of one type parameter on another one because the following (replacing Value<T> by Record<string, T>) works:
    type DataType = number | string
    
    class Container<V extends Record<string, T>, T extends DataType> extends Model(<
      V extends Record<string, T>,
      T extends DataType
    >() => ({
      value: prop<V>(),
    }))<V, T> {}

So it looks like it's caused by the dependence of one type parameter on another that extends a generic model. This is as far as I've been able to narrow it down. I'm just not sure yet how to fix it. Any help is much appreciated. 🙏

@xaviergonz
Copy link
Owner

xaviergonz commented Dec 18, 2021

To be honest, I have no clue how to fix it. There's a workaround if you don't need a custom value type though:

class Container<T extends DataType> extends Model(<
  T extends DataType
>() => ({
  value: prop<ValueType<T>>(),
}))<T> {}

@xaviergonz
Copy link
Owner

xaviergonz commented Dec 18, 2021

or maybe you could use a factory function instead...

@xaviergonz
Copy link
Owner

Another workaround which adds a getter to type the prop properly

  type DataType = number | string

  class Value<T extends DataType> extends Model(<T extends DataType>() => ({
    data: prop<T>(),
  }))<T> {}

  class Container<V extends Value<T>, T extends DataType> extends Model(<T extends DataType>() => ({
    value: prop<Value<T>>(),
  }))<T> {
    get valueProperlyTyped() {
      return this.value as V
    }
  }

@xaviergonz
Copy link
Owner

As a side note, if TS were to allow the usage of types in base expressions all of this would be so much easier

@sisp
Copy link
Contributor Author

sisp commented Dec 18, 2021

Remember when we were discussing about generic models and I came up with this one trick that you made work in this library in the end? When I use the same simplified mock of Model and prop and copy the example from above in the TS Playground there, there's no typing error 🤔: TS Playground

@xaviergonz xaviergonz added 🎈 released A fix that should close the issue has been released 🐛 bug Something isn't working 🤞 maybe fixed? Maybe it is fixed labels Dec 18, 2021
@xaviergonz
Copy link
Owner

I looked into it further and found the issue. Should be fixed in 0.64.1

@sisp
Copy link
Contributor Author

sisp commented Dec 19, 2021

Awesome, that indeed fixes the test case I provided. 🎉

However, my actual use case has prop setters and when I modify the reduced example by adding a setter in the Value model

- data: prop<T>(),
+ data: prop<T>().withSetter(),

then the error shows up again. Do you happen to know how to solve this, too? 🙏

@xaviergonz
Copy link
Owner

xaviergonz commented Dec 19, 2021

Ok, that one I'm afraid I'm really lost. My hunch is that it has something to do with the contravariance of setter functions (https://docs.microsoft.com/en-us/dotnet/standard/generics/covariance-and-contravariance - https://dmitripavlutin.com/typescript-covariance-contravariance/). It seems that defining the setter manually works though as a workaround.

@xaviergonz
Copy link
Owner

xaviergonz commented Dec 19, 2021

Also your particular example can be written as

  type DataType = number | string

  @model("issue #358/2/Value")
  class Value<T extends DataType> extends Model(<T extends DataType>() => ({
    data: prop<T>().withSetter(),
  }))<T> {}

  @model("issue #358/2/Container")
  class Container<V extends Value<any>> extends Model(<
    V extends Value<DataType>
  >() => ({
    value: prop<V>(),
  }))<V> {}

  const c = new Container<Value<number>>({
    value: new Value<number>({
      data: 1,
    }),
  })

  assert(c.value, _ as Value<number>)
  assert(c.value.data, _ as number)

which would work, but I don't know how close that would be to your real case.

@sisp
Copy link
Contributor Author

sisp commented Dec 19, 2021

It seems that defining the setter manually works though as a workaround.

You're right, I hadn't tried that. Thanks!

Ok, that one I'm afraid I'm really lost.

No worries then since a manually defined setter works. But thank you very much for trying and having found a fix for the typing error without a generated setter. I'll close the issue because I think it's sufficiently resolved.

Also your particular example can be written as [...]

I'll need to try to transfer it to the real case, not sure yet. Thanks for the suggestion!

@sisp sisp closed this as completed Dec 19, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
🐛 bug Something isn't working 🤞 maybe fixed? Maybe it is fixed 🎈 released A fix that should close the issue has been released
Projects
None yet
Development

No branches or pull requests

2 participants