Improve type readability (fix type alias propagation, allow aliasing inferred types)
Chưa có ai nhận issue này.
Đánh giá
- Độ khó
- 5/5
- Thời gian dự kiến
- Hơn một tuần
- Mức phù hợp với người mới
- 25/100
- Loại issue
- Tính năng
- Độ rõ ràng
- Khá rõ ràng
- Mức độ hoạt động
- Đình trệ
- Công nghệ
- typescript
- Lĩnh vực
- compilers, developer-experience
Hướng nghiên cứu
Chưa xác định được tệp mã nguồn, bài kiểm thử hoặc điểm vào nào. Trước tiên, hãy xem lại các ví dụ tạo động lực về TypeScript và io-ts, sau đó theo dõi cách trình biên dịch bảo toàn và hiển thị các bí danh kiểu trước khi xác định liệu cú pháp bí danh và các quy tắc lan truyền được đề xuất đã có thiết kế được thống nhất cùng các bài kiểm thử tương ứng hay chưa.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Mô tả
Suggestion
🔍 Search Terms
preserve type aliases,inferred types,type names,readability,type expansion
✅ Viability Checklist
My suggestion meets these guidelines:
- [*] This wouldn't be a breaking change in existing TypeScript/JavaScript code
- [*] This wouldn't change the runtime behavior of existing JavaScript code
- [*] This could be implemented without emitting different JS based on the types of the expressions
- [*] This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
- [*] This feature would agree with the rest of TypeScript's Design Goals.
⭐ Suggestion
One thing that makes working with Typescript's type system occasionally hopeless is when cumulative inferred types are displayed in type expansion and 'pollute' the information in quick info or errors.
So the proposal is two fold:
- Fix type expansion so that the names of type aliases are preferred over their expanded inferred types wherever a type alias is available.
- Add syntax that allows us to 'alias'/name inferred types and allow those alias names to carry through type expansion.
📃 Motivating Example
With any kind of sophisticated typing, we quickly end up with error messages and parameter info that takes multiple seconds to load, and shows a truncated mess of type syntax. Worse, the expansion of these inferred types accumulates and adversely affects any code that touches them.
Typescript fails us in two distinct areas with regard to type expansion, and the culprit seems to be inferred types...
1. Typescript doesn't always respect type aliases, even when they are explicitly provided
E.g. First, what works:
// Let's start with a contrived mapped type:
type Mapped<T> = {
[P in keyof T]: never;
};
// And a generic function that takes an argument and returns the mapped type of it.
function map<T>(value: T): Mapped<T> {
return {} as never;
}
// Create a Person via inference
const p = {age: 5, name: 'Bobby'};
// And quick info on 'inferred' displays: Mapped<{age: number, name: string}>
const inferred = map(p);
// Let's clean up the inferred type by defining a type alias.
type Person = {age: number; name: string};
// Now quick info on 'named' shows: Mapped<Person>
const named = map({age: 5, name: 'Bobby'} as Person);
So far, so good. The rule seems to be that if we explicitly alias a type, Typescript will use that name and terminate expansion.
But this rule gets broken when there is logic to the type alias.
// Use `io-ts` as an example and declare runtime types/validators like so:
import * as t from 'io-ts';
const Person = t.type({
name: t.string,
age: t.number,
});
// And infer the underlying data type from it...
type Person = t.TypeOf<typeof Person>;
// Now, let's put them through the same Mapped type and map function...
// We use the type alias 'Person' in the same manner to try to 'name' the type, but...
const named = map({age: 5, name: 'Bobby'} as Person);
// Quick info on 'named' is: Mapped<{name: string, age: number}> not Mapped<Person>
You could argue that this is handy, but the consequences of this deviation from the rule means that all other types referencing this one will repeat the full expansion.
2. There is no way to name an inferred type
There is also a scenario where fully expanded inferred types are unavoidable. More specifically, where there is no way to explicitly specify a type alias whose name should be displayed. E.g.
// Let's return to our Person type and try some composition:
const Family = t.type({
mother: Person,
father: Person,
child: Person,
});
// The type of 'Family' is: t.TypeC<{mother: t.TypeC<{name: t.StringC, age: t.NumberC}>, father: t.TypeC<{name: t.StringC, age: t.NumberC}>, child: t.TypeC<{name: t.StringC, age: t.NumberC}>}>
That's already pretty out of hand. All we would need to know, ideally, is something like (pseudocode) t.TypeC<{mother: Person, father: Person, child: Person}>.
But since the type of Person is inferred from the function's return type, we don't get a chance to create a type alias for it. In fact, that's the reason we're able to declare a type alias of the same name for the underlying data type.
So wherever we use the Person instance, we're now stuck with a fully expanded inferred type. If we were able to provide Typescript with an alias for the inferred type, we could escape expansion and clean up type expansions everywhere.
Proposed as type syntax
Such syntax could work like this:
// Combining the syntax for type assertion and type alias declaration:
const Person = t.type({
name: t.string,
age: t.number,
}) as type PersonType;
So that Family would then display: t.TypeC<{mother: PersonType, father: PersonType, child: PersonType}>
Since the above kind of factory that returns an inferred type is a common pattern, it could also be shorthanded like this:
const type PersonType = t.type({
name: t.string,
age: t.number,
});
...so that the name of the variable would double as the name of the type alias.
There could even be syntax that is used upstream by the function returning the inferred type...
function map<T>(value: T): Mapped<T> as type new {
return {} as never;
}
// Here, the type of `MyMap` is `MyMap` (it gets aliased by default with the same name).
const MyMap = map({ age: 22, name: 'Bobby});
The MyMap alias expanded is Mapped<{ age: number, name: string }>
// We could also allow for when wrapping is needed...
function get<T>(value: T): Wrapper<T as type new> {
return {} as never;
}
// Here Person instance is typed as Wrapped<Person>, where Person is { age: number, name: string }
const Person = get({ age: 22, name: 'Bobby' })
💻 Use Cases
I think I've covered quite specific use cases in my motivating example.
Leveraging type inference to construct sophisticated types is common enough now that we should pay particular attention to developer experience for type systems.
These two changes would especially allow both developers and library authors to have better control over the types their code emits to intellisense and allow them to craft an improved developer experience.
Specifically, we could use libraries like io-ts and zod without polluting our type intellisense.
- Ngôn ngữ chính
- Go
- Star
- 111k
- Fork
- 14.4k
- Merge trung bình
- 1 ngày 15 giờ
- Pull request đã merge (30 ngày)
- 106
Hướng dẫn đóng góp
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của microsoft/TypeScript
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 88/100
microsoft/TypeScript#64322 · 2 bình luận · 1 reaction · 2 người được giao ·
-
Possible Improvement
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
microsoft/TypeScript#64278 · 1 bình luận · 1 reaction ·
-
Docs
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 70/100
microsoft/TypeScript#64118 · 1 bình luận ·
-
Độ khó 1/5 Dưới một giờ Mức phù hợp với người mới 88/100
microsoft/TypeScript#64094 ·
-
Docs
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 76/100
microsoft/TypeScript#63959 · 5 bình luận ·
Tất cả issue của microsoft/TypeScript
Issue tương tự
-
Type/Bug
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
OpenNSW/nsw-srilanka#497 ·
-
Độ khó 1/5 Dưới một giờ Mức phù hợp với người mới 92/100
milvus-io/birdwatcher#545 ·
-
kind/bug
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 88/100
kubernetes-sigs/prow#953 · 1 bình luận ·
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 88/100
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 88/100
caddyserver/caddy#8046 ·