microsoft / microsoft/TypeScript
Infer from usage type logic
まだ誰も着手していません。
- 主要言語
- Go
- スター
- 111k
- フォーク
- 14.4k
- 平均マージ
- 1日 19時間
- マージ済み PR(30日)
- 117
説明
Bug Report
🔎 Search Terms
- infer from usage
- interface higher priority then type on type inference
🕗 Version & Regression Information
I think this has always been the behavior as far as I know. I didn't play around with this much yet, but the behavior exists in 4.0.5 and nightly and it seems odd for it to bounce around a lot.
⏯ Playground Link
💻 Code
// infers -> : { x: any; } = this makes sense
function inferWorksFine(obj) {
return obj.x
}
// infers -> Z, this also makes sense
function inferFromFunctionCallTypedWithType(obj) {
typedWithType(obj)
}
// infers -> Q, also makes sense
function inferFromFunctionCallTypedWithInterface(obj) {
typedWithInterface(obj)
}
/*
* Here is when behavior gets odd
*
*/
// infers -> { toplevel?: any; z?: number }
// I would prefer if toplevel resovled to a non ? type but thats sorta stylistic
// z is not optional and making it results in a type error
function inferFromUsageAndFunctionCallTypedWithType(obj) {
typedWithType(obj)
return obj.toplevel
}
// infers -> Q
// This seems wrong to me, interface "types" have priority over
// anonymous types, this results in errors because the inferred property
// is an anonymous type
function inferFromUsageAndFunctionCallTypeWithInterface(obj) {
typedWithInterface(obj)
return obj.toplevel
}
// infers -> Q
// From what I understand. The statement "type Z = {z: number}"
// is a type alias Z to an anonymous type {z: number} so the interface
// still has priority over it.
function inferFromFunctionCallTypedWithTypeAndFunctionCallTypedWithInterface(obj) {
typedWithType(obj)
typedWithInterface(obj)
}
// There are more examples you could contrive that look like this/combinations of this
// but I think this is enough for the point
type Z = {z: number}
function typedWithType(z: Z) {
}
interface Q {
q: number
}
function typedWithInterface(q: Q) {}
🙁 Explanation of Actual behavior
Note: If I reference line numbers or any function I'm referring to the main branch I pulled I think yesterday.
So I sort of already discussed the behavior in the code itself. So I think it makes sense to talk about the code that causes this behavior.
So this file path "TypeScript/src/services/codefixes/inferFromUsage.ts" is where the logic for inferFromUsage lives afaik.
Just to do this in order:
// infers -> { toplevel?: any; z?: number }
// I would prefer if toplevel resovled to a non ? type but thats sorta stylistic
// z is not optional and making it results in a type error
function inferFromUsageAndFunctionCallTypedWithType(obj) {
typedWithType(obj)
return obj.toplevel
}
The reason these are optional seem to stem from the function combineAnonymousTypes
Specifically
const members = mapEntries(props, (name, types) => {
const isOptional = types.length < anons.length ? SymbolFlags.Optional : 0;
const s = checker.createSymbol(SymbolFlags.Property | isOptional, name as __String);
s.links.type = checker.getUnionType(types);
return [name, s];
});
I'm actually not sure what the isOptional is intended to do. From what I understand anons.length is the number of anonymous types that the function is passed while types refers to possible type resolutions of the property with name name. I'm not sure what comparing these means.
Priority of interfaces
Since in the code I mention why I think type aliasing doesn't change anything I'll just show one example.
function inferFromFunctionCallTypedWithTypeAndFunctionCallTypedWithInterface(obj) {
typedWithType(obj)
typedWithInterface(obj)
}
So here the type will be resolved to the interface type. In the function combineTypes there is a priority table defined as follows:
const priorities: Priority[] = [
{
high: t => t === checker.getStringType() || t === checker.getNumberType(),
low: t => t === stringNumber
},
{
high: t => !(t.flags & (TypeFlags.Any | TypeFlags.Void)),
low: t => !!(t.flags & (TypeFlags.Any | TypeFlags.Void))
},
{
high: t => !(t.flags & (TypeFlags.Nullable | TypeFlags.Any | TypeFlags.Void)) && !(getObjectFlags(t) & ObjectFlags.Anonymous),
low: t => !!(getObjectFlags(t) & ObjectFlags.Anonymous)
}];
The last entry
{
high: t => !(t.flags & (TypeFlags.Nullable | TypeFlags.Any | TypeFlags.Void)) && !(getObjectFlags(t) & ObjectFlags.Anonymous),
low: t => !!(getObjectFlags(t) & ObjectFlags.Anonymous)
}
gives high priority to non-anonymous types which I think only means interfaces or a type which at some point was mixed with an interface? This would take further investigation. I'm not sure the purpose of this behavior or if its a bug.
🙂 Expected behavior
I want the behavior to be that the generated types don't cause errors when they don't have to, since currently generated types don't satisfy what they need to be.
Deleting these two lines "solves" the behavior, but there are definitely more things to look into. I'm not sure why they were written - like I don't have any intuition on what problem they solve - but I'm pretty sure they are there for important behavior.
Also since I'm listing things, I think this is the other "big one"
There are also more cases like
function f(obj) {
g(obj)
return obj.v > 1
}
not being able to resolve v to a number if "g" expects some type (even if the type is unrelated like {q}) and others.
コントリビューションガイド
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
調査の方向性
TypeScript/src/services/codefixes/inferFromUsage.ts から始め、リンクされている Playground のケースを combineAnonymousTypes および combineTypes と比較します。オプショナルプロパティのロジックと優先順位テーブルを追跡し、その後、何かを変更する前に意図された推論動作を判断します。報告された使用箇所からの推論のケースで、予期しない型エラーが発生しなくなれば完了です。
索引モデルが issue の本文から書いたものです。
評価
- 技術スタック
- typescript
- 領域
- compilers
- issue の種類
- バグ
- 難易度
- 5/5
- 見積もり時間
- 1週間以上
- 活発さ
- 停滞
- 明瞭さ
- 説明が足りない
- 初心者へのやさしさ
- 25/100