-
-
Notifications
You must be signed in to change notification settings - Fork 407
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
RFC: Semantic Versioning for TypeScript Types #730
Conversation
This RFC proposes a definition of [Semantic Versioning][semver] for managing changes to TypeScript types, including when the TypeScript compiler makes breaking changes in its type-checking and type emit across a “minor”release.(Note that this RFC addresses *only* type checking and type emit, *no t* the “transpilation”mode of the TypeScript compiler.) [semver]: https://semver.org While this RFC is being authored in the context of Ember.js’ [adoption of TypeScript as a first-cl ass supported language][RFC], its recommendations are intentionally general, in hopes that th ese recommendations can be widely adopted by the broader TypeScript community. [RFC]: emberjs#724
5144c77
to
2fca926
Compare
Thanks so much for the detailed feedback, @RyanCavanaugh! Deeply appreciated, and will update this to account for those in the days ahead! |
- author and publish its types with `strict: true` and `noPropertyAccessFromIndexSignature: true` in its compiler configuration | ||
|
||
|
||
## How we teach this |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@emberjs/learning-team-managers definitely want to have the input of the Learning Core Team here. Not sure if this information should be a separate repository.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Input very welcome!
The reason I suggested to put it in a separate repo is that the goal is (explicitly!) for it not to remain an Ember-specific policy and design, much as semver.org is used by many projects. We need this, for our adoption process; but many others would benefit from it as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It strikes me that another way this process could go is to develop the repo independent of Ember first then make an RFC for adopting this practices within Ember. However, it’s going to be important for the project to have at least one major adoptee and if it can’t even get Ember to adopt then it’s pretty much a non-starter. So to me, that makes a good case for doing it the way this RFC has proposed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it’s also worth noting that this spec is mainly for package developers not end users. All end users really want to know is “we won’t break your types willy-nilly.” Of course, having an actual spec with details that they can read is useful, but I strongly suspect that most users won’t really care about the details. If we do it right, things will just work. Where the spec matters a lot more is for those making a package who actually have to make sure they don’t break stuff! All that to say, I don’t think there really needs to be a detailed explanation on the main Ember docs beyond “we adhere to TS SemVer.”
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So, in terms of how we teach this, I like the idea of it being a separate repo when it's finished. Many successful specs are living documents that have changes over time, and I think it makes sense for those standards conversations to have a home that is not mixed in with conversation about other unrelated things (and vice versa). We could keep it as an RFC entry, but I think as a standalone it has a better chance of influencing the broader JS ecosystem to adopt the same things, versus creating their own RFC version of this with modifications.
An alternate approach is to make the separate repo now, and then link to it in this RFC as a proposal to adopt that document. That would make the Ember relationship a bit clearer, but I think the quality of feedback and iteration cycle really benefits from the full text being in the RFC. We just need to make sure there are clear, bold links to the other repo in this RFC later on.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is really great. I’m still somewhat confused by “user constructible”, and I’m curious where enums fit in. The practical, detailed, non-normative appendices are a big help.
Thanks for the feedback, @jeremy-w! (😬 at all the typo/wordo fixes!) I'll be integrating the suggestions etc. later this week. Great callout on User-construct-ability requires a definition, I think, as well as a single choice of spelling. 😆 (Autocorrect converts "constructable" to "constructible," so it looks like that's what we'll be using.) As a brief summary for folks following this, though, the idea there is to provide package authors a way of publishing a type definition that's useful to end consumers while giving them the "out" of saying that the type is only constructible by the package itself, for example via a "factory"-style function. As a motivating example from Ember, the |
That helps clear up the constructibility question a lot. “Client code should not attempt to directly create an instance of this type, but can work with instances provided by the library using their public API.” Explicitly stating the changes considered non-breaking is probably needed to avoid issues with breaking derived types as well, eg breaking union discrimination or extension with a conflicting type as raised in other comments. |
I am a huge fan of having the SemVer definitions/expectations in writing somewhere. I don't have enough hands-on experience authoring public library types to comment on the specifics, but as an end user of TS, I can see how this is helpful. Thanks for writing it up! |
It seems to me like many/most of these rules could be summed up by:
And I'd be proponent of the following rules (not including them makes it almost impossible to change anything without causing a "breaking change"), though they don't always agree with what has been specified in the document:
|
I like those first three much as summaries, @tjjfvi! Thank you for putting it so clearly. As for the others, I think the (For lookers-on, I’ll be returning to this next week or the week after. I was busy wrapping up the quarter the last few weeks and am off this week!) |
Any new export can break a consumer: // myWrapperAroundLib.ts
export * from "lib"
export * from "./myUtils"
// myUtils.ts
export const something = 5; If |
Ah, that’s a fair point, and one we need to explicitly address one way or another! I’ll mull on it further; at first blush I think that re-exports are an area the current spec design basically doesn’t account for at all! |
Without having given it too much consideration, would it make sense to just ban |
|
Co-authored-by: Jeremy W. Sherman <jeremyw.sherman@gmail.com>
An update: I've been thinking on and talking with folks about especially the problems raised by Ryan, and I have the outline of some changes which I expect to land mid-to-late next week here, namely addressing the way that inference and mutability interact; that covers one subset of the issues Ryan addressed (though there are a couple others outstanding there, which neither I nor my regular collaborators here have yet come up with a robust solution for). |
Co-authored-by: Jeremy W. Sherman <jeremyw.sherman@gmail.com>
I've been mulling on the open points here for a while and it finally clicked for me this past weekend (I may just be slow!) that all of the inference + mutability issues ultimately come down to variance (as do many of the points I noted in the draft). For example, on function return types, I originally wrote this:
However, @RyanCavanaugh correctly pointed out that this would fail in this scenario: const arr = [libFunc()];
arr.push(undefined); The problem here is that The best solution I've come up with here so far is:
I'll be integrating a discussion of variance into the text to account for this. |
We talked about this at today’s Framework Core team meeting and are moving it forward into Final Comment Period! |
This was just a mental flub: it was always intended to be the latter, and I just repeatedly typo'd it.
Ah, that makes a lot more sense |
I am an active member of the TypeScript community, and given (emphasis mine):
I feel that feedback from the TypeScript community at large is valuable. I have significant concerns about the current content of the proposal, and though it may be workable for Ember, I think the current provisions would be unworkable for many TypeScript libraries. Consider the imaginary package export type FrobnicateOptions = {
intensity: number,
rng: () => number,
}
export function frobnicate(options: FrobnicateOptions) {
// ...
} This is perfectly reasonable code. The most notable thing about it is that it exports const frobnicateOptions: FrobnicateOptions = {
intensity: 5,
rng: () => Math.random() ** 2,
} If Soon, Fred gets a feature request – in the export type FrobnicateOptions = {
intensity: number,
rng: (callNumber: number) => number,
}
export function frobnicate(options: FrobnicateOptions) {
// ...
} Now, someone can create an Fred is happy with this change, and seeks to release it as However, to his horror, he realizes that this would be a breaking change:
This would usually not be considered a breaking change, as However, the current proposal treats all types as user-consumable, and so this change would break user code such as: const options: FrobnicateOptions = {
intensity: 5,
rng: () => Math.random() ** 2,
}
options.rng() Because now the type of (Note: I see mention of user-consumability in some of the above comments; was that once part of the document, and later removed, or was it proposed and then never implemented?) I propose the following additions to the RFC: A new concept, user-consumability. Exported values from a library are, by default, user-consumable but not user-constructible, unless specifically noted otherwise in their type. User-consumability of a type entails the following permissions:
The terms user-constructible and user-consumable refer to ways of constructing and consuming the types without going through the library; for example, user-constructible types allow object literal construction, and user-consumable types allow property access. In particular, "constructing" and "consuming" values by way of user-consumable library functions is always allowed. Specifically:
(Terms other than user-consumability and user-constructibility may be worthwhile, as they are rather close to each other.) |
Also, in the vein of requiring |
@tjjfvi this is excellent feedback—exactly what we’ve been hoping for! 💙 Do you mind opening a Discussion for both of these on the repo for the spec? 🙏🏼 |
@chriskrycho Certainly; I didn't realize that that repo existed 😅. I've opened semver-ts/semver-ts#3, semver-ts/semver-ts#4, and semver-ts/semver-ts#5. |
Per discussion at last week’s Framework Team meeting, we accepted this and have pointed to https://www.semver-ts.org as the now-authoritative form of the spec (it was just waiting on me to add that text for us to merge it)! 🎉 |
Advance RFC #730 "Semantic Versioning for TypeScript Types" to Stage Ready for Release
Advance RFC #730 `"Semantic Versioning for TypeScript Types"` to Stage Released
Advance RFC #730 `"Semantic Versioning for TypeScript Types"` to Stage Recommended
Summary
Introduce a policy for semantic versioning for TypeScript types, which accounts for both the details of TypeScript's structural type system and the need to absorb breaking changes in the TypeScript compiler.
Rendered text
Notes
Background
This is adapted from the work done at typed-ember/ember-cli-typescript#1158 – with significant changes made to the recommendations around the supported compiler version policy (recommending two alternative approaches) and adding details around strictness, module interop, and more.