-
Notifications
You must be signed in to change notification settings - Fork 13k
Fixed crash related to index type deferral on generic mapped types with name types #60528
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
base: main
Are you sure you want to change the base?
Changes from 6 commits
b9a01cb
e13ad38
880d4af
4c69959
585091e
ac23642
2d24ebc
51a631a
5480c6a
b901985
916de93
35ab81f
3d1fe73
f92747d
ebc2320
c1ba85d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14749,6 +14749,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { | |
undefined; | ||
} | ||
if (t.flags & TypeFlags.Index) { | ||
if (isGenericMappedType((t as IndexType).type)) { | ||
const mappedType = (t as IndexType).type as MappedType; | ||
if (getNameTypeFromMappedType(mappedType) && !isMappedTypeWithKeyofConstraintDeclaration(mappedType)) { | ||
return getBaseConstraint(getIndexTypeForMappedType(mappedType, IndexFlags.None)); | ||
} | ||
} | ||
return stringNumberSymbolType; | ||
} | ||
if (t.flags & TypeFlags.TemplateLiteral) { | ||
|
@@ -18250,7 +18256,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { | |
// a circular definition. For this reason, we only eagerly manifest the keys if the constraint is non-generic. | ||
if (isGenericIndexType(constraintType)) { | ||
if (isMappedTypeWithKeyofConstraintDeclaration(type)) { | ||
// We have a generic index and a homomorphic mapping (but a distributive key remapping) - we need to defer | ||
// We have a generic index and a homomorphic mapping and a key remapping - we need to defer | ||
// the whole `keyof whatever` for later since it's not safe to resolve the shape of modifier type. | ||
return getIndexTypeForGenericType(type, indexFlags); | ||
} | ||
|
@@ -18280,25 +18286,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { | |
} | ||
} | ||
|
||
// Ordinarily we reduce a keyof M, where M is a mapped type { [P in K as N<P>]: X }, to simply N<K>. This however presumes | ||
// that N distributes over union types, i.e. that N<A | B | C> is equivalent to N<A> | N<B> | N<C>. Specifically, we only | ||
// want to perform the reduction when the name type of a mapped type is distributive with respect to the type variable | ||
// introduced by the 'in' clause of the mapped type. Note that non-generic types are considered to be distributive because | ||
// they're the same type regardless of what's being distributed over. | ||
function hasDistributiveNameType(mappedType: MappedType) { | ||
const typeVariable = getTypeParameterFromMappedType(mappedType); | ||
return isDistributive(getNameTypeFromMappedType(mappedType) || typeVariable); | ||
function isDistributive(type: Type): boolean { | ||
return type.flags & (TypeFlags.AnyOrUnknown | TypeFlags.Primitive | TypeFlags.Never | TypeFlags.TypeParameter | TypeFlags.Object | TypeFlags.NonPrimitive) ? true : | ||
type.flags & TypeFlags.Conditional ? (type as ConditionalType).root.isDistributive && (type as ConditionalType).checkType === typeVariable : | ||
type.flags & (TypeFlags.UnionOrIntersection | TypeFlags.TemplateLiteral) ? every((type as UnionOrIntersectionType | TemplateLiteralType).types, isDistributive) : | ||
type.flags & TypeFlags.IndexedAccess ? isDistributive((type as IndexedAccessType).objectType) && isDistributive((type as IndexedAccessType).indexType) : | ||
type.flags & TypeFlags.Substitution ? isDistributive((type as SubstitutionType).baseType) && isDistributive((type as SubstitutionType).constraint) : | ||
type.flags & TypeFlags.StringMapping ? isDistributive((type as StringMappingType).type) : | ||
false; | ||
} | ||
} | ||
|
||
function getLiteralTypeFromPropertyName(name: PropertyName | JsxAttributeName) { | ||
if (isPrivateIdentifier(name)) { | ||
return neverType; | ||
|
@@ -18350,7 +18337,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { | |
function shouldDeferIndexType(type: Type, indexFlags = IndexFlags.None) { | ||
return !!(type.flags & TypeFlags.InstantiableNonPrimitive || | ||
isGenericTupleType(type) || | ||
isGenericMappedType(type) && (!hasDistributiveNameType(type) || getMappedTypeNameTypeKind(type) === MappedTypeNameTypeKind.Remapping) || | ||
isGenericMappedType(type) && getNameTypeFromMappedType(type) || | ||
type.flags & TypeFlags.Union && !(indexFlags & IndexFlags.NoReducibleCheck) && isGenericReducibleType(type) || | ||
type.flags & TypeFlags.Intersection && maybeTypeOfKind(type, TypeFlags.Instantiable) && some((type as IntersectionType).types, isEmptyAnonymousObjectType)); | ||
} | ||
|
@@ -18871,6 +18858,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { | |
function getSimplifiedType(type: Type, writing: boolean): Type { | ||
return type.flags & TypeFlags.IndexedAccess ? getSimplifiedIndexedAccessType(type as IndexedAccessType, writing) : | ||
type.flags & TypeFlags.Conditional ? getSimplifiedConditionalType(type as ConditionalType, writing) : | ||
type.flags & TypeFlags.Index ? getSimplifiedIndexType(type as IndexType) : | ||
type; | ||
} | ||
|
||
|
@@ -18970,6 +18958,13 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { | |
return type; | ||
} | ||
|
||
function getSimplifiedIndexType(type: IndexType) { | ||
if (isGenericMappedType(type.type) && getNameTypeFromMappedType(type.type) && !isMappedTypeWithKeyofConstraintDeclaration(type.type)) { | ||
return getIndexTypeForMappedType(type.type, IndexFlags.None); | ||
} | ||
return type; | ||
} | ||
|
||
/** | ||
* Invokes union simplification logic to determine if an intersection is considered empty as a union constituent | ||
*/ | ||
|
@@ -42086,12 +42081,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { | |
// Check if the index type is assignable to 'keyof T' for the object type. | ||
const objectType = (type as IndexedAccessType).objectType; | ||
const indexType = (type as IndexedAccessType).indexType; | ||
// skip index type deferral on remapping mapped types | ||
const objectIndexType = isGenericMappedType(objectType) && getMappedTypeNameTypeKind(objectType) === MappedTypeNameTypeKind.Remapping | ||
? getIndexTypeForMappedType(objectType, IndexFlags.None) | ||
: getIndexType(objectType, IndexFlags.None); | ||
Comment on lines
-42089
to
-42092
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This reverts my own change from #55140 . I think now the check wasn't exhaustive anyway and this is now better handled by |
||
const hasNumberIndexInfo = !!getIndexInfoOfType(objectType, numberType); | ||
if (everyType(indexType, t => isTypeAssignableTo(t, objectIndexType) || hasNumberIndexInfo && isApplicableIndexType(t, numberType))) { | ||
if (everyType(indexType, t => isTypeAssignableTo(t, getIndexType(objectType, IndexFlags.None)) || hasNumberIndexInfo && isApplicableIndexType(t, numberType))) { | ||
if ( | ||
accessNode.kind === SyntaxKind.ElementAccessExpression && isAssignmentTarget(accessNode) && | ||
getObjectFlags(objectType) & ObjectFlags.Mapped && getMappedTypeModifiers(objectType as MappedType) & MappedTypeModifiers.IncludeReadonly | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
//// [tests/cases/conformance/types/mapped/mappedTypeAsClauseRecursiveNoCrash1.ts] //// | ||
|
||
=== mappedTypeAsClauseRecursiveNoCrash1.ts === | ||
// https://github.com/microsoft/TypeScript/issues/60476 | ||
|
||
export type FlattenType<Source extends object, Target> = { | ||
>FlattenType : Symbol(FlattenType, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 0, 0)) | ||
>Source : Symbol(Source, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 2, 24)) | ||
>Target : Symbol(Target, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 2, 46)) | ||
|
||
[Key in keyof Source as Key extends string | ||
>Key : Symbol(Key, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 3, 3)) | ||
>Source : Symbol(Source, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 2, 24)) | ||
>Key : Symbol(Key, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 3, 3)) | ||
|
||
? Source[Key] extends object | ||
>Source : Symbol(Source, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 2, 24)) | ||
>Key : Symbol(Key, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 3, 3)) | ||
|
||
? `${Key}.${keyof FlattenType<Source[Key], Target> & string}` | ||
>Key : Symbol(Key, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 3, 3)) | ||
>FlattenType : Symbol(FlattenType, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 0, 0)) | ||
>Source : Symbol(Source, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 2, 24)) | ||
>Key : Symbol(Key, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 3, 3)) | ||
>Target : Symbol(Target, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 2, 46)) | ||
|
||
: Key | ||
>Key : Symbol(Key, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 3, 3)) | ||
|
||
: never]-?: Target; | ||
>Target : Symbol(Target, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 2, 46)) | ||
|
||
}; | ||
|
||
type FieldSelect = { | ||
>FieldSelect : Symbol(FieldSelect, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 8, 2)) | ||
|
||
table: string; | ||
>table : Symbol(table, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 10, 20)) | ||
|
||
field: string; | ||
>field : Symbol(field, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 11, 16)) | ||
|
||
}; | ||
|
||
type Address = { | ||
>Address : Symbol(Address, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 13, 2)) | ||
|
||
postCode: string; | ||
>postCode : Symbol(postCode, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 15, 16)) | ||
|
||
description: string; | ||
>description : Symbol(description, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 16, 19)) | ||
|
||
address: string; | ||
>address : Symbol(address, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 17, 22)) | ||
|
||
}; | ||
|
||
type User = { | ||
>User : Symbol(User, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 19, 2)) | ||
|
||
id: number; | ||
>id : Symbol(id, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 21, 13)) | ||
|
||
name: string; | ||
>name : Symbol(name, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 22, 13)) | ||
|
||
address: Address; | ||
>address : Symbol(address, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 23, 15)) | ||
>Address : Symbol(Address, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 13, 2)) | ||
|
||
}; | ||
|
||
type FlattenedUser = FlattenType<User, FieldSelect>; | ||
>FlattenedUser : Symbol(FlattenedUser, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 25, 2)) | ||
>FlattenType : Symbol(FlattenType, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 0, 0)) | ||
>User : Symbol(User, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 19, 2)) | ||
>FieldSelect : Symbol(FieldSelect, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 8, 2)) | ||
|
||
type FlattenedUserKeys = keyof FlattenType<User, FieldSelect>; | ||
>FlattenedUserKeys : Symbol(FlattenedUserKeys, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 27, 52)) | ||
>FlattenType : Symbol(FlattenType, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 0, 0)) | ||
>User : Symbol(User, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 19, 2)) | ||
>FieldSelect : Symbol(FieldSelect, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 8, 2)) | ||
|
||
export type FlattenTypeKeys<Source extends object, Target> = keyof { | ||
>FlattenTypeKeys : Symbol(FlattenTypeKeys, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 28, 62)) | ||
>Source : Symbol(Source, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 30, 28)) | ||
>Target : Symbol(Target, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 30, 50)) | ||
|
||
[Key in keyof Source as Key extends string | ||
>Key : Symbol(Key, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 31, 3)) | ||
>Source : Symbol(Source, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 30, 28)) | ||
>Key : Symbol(Key, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 31, 3)) | ||
|
||
? Source[Key] extends object | ||
>Source : Symbol(Source, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 30, 28)) | ||
>Key : Symbol(Key, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 31, 3)) | ||
|
||
? `${Key}.${keyof FlattenType<Source[Key], Target> & string}` | ||
>Key : Symbol(Key, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 31, 3)) | ||
>FlattenType : Symbol(FlattenType, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 0, 0)) | ||
>Source : Symbol(Source, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 30, 28)) | ||
>Key : Symbol(Key, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 31, 3)) | ||
>Target : Symbol(Target, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 30, 50)) | ||
|
||
: Key | ||
>Key : Symbol(Key, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 31, 3)) | ||
|
||
: never]-?: Target; | ||
>Target : Symbol(Target, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 30, 50)) | ||
|
||
}; | ||
|
||
type FlattenedUserKeys2 = FlattenTypeKeys<User, FieldSelect>; | ||
>FlattenedUserKeys2 : Symbol(FlattenedUserKeys2, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 36, 2)) | ||
>FlattenTypeKeys : Symbol(FlattenTypeKeys, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 28, 62)) | ||
>User : Symbol(User, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 19, 2)) | ||
>FieldSelect : Symbol(FieldSelect, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 8, 2)) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
//// [tests/cases/conformance/types/mapped/mappedTypeAsClauseRecursiveNoCrash1.ts] //// | ||
|
||
=== mappedTypeAsClauseRecursiveNoCrash1.ts === | ||
// https://github.com/microsoft/TypeScript/issues/60476 | ||
|
||
export type FlattenType<Source extends object, Target> = { | ||
>FlattenType : FlattenType<Source, Target> | ||
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ||
|
||
[Key in keyof Source as Key extends string | ||
? Source[Key] extends object | ||
? `${Key}.${keyof FlattenType<Source[Key], Target> & string}` | ||
: Key | ||
: never]-?: Target; | ||
}; | ||
|
||
type FieldSelect = { | ||
>FieldSelect : FieldSelect | ||
> : ^^^^^^^^^^^ | ||
|
||
table: string; | ||
>table : string | ||
> : ^^^^^^ | ||
|
||
field: string; | ||
>field : string | ||
> : ^^^^^^ | ||
|
||
}; | ||
|
||
type Address = { | ||
>Address : Address | ||
> : ^^^^^^^ | ||
|
||
postCode: string; | ||
>postCode : string | ||
> : ^^^^^^ | ||
|
||
description: string; | ||
>description : string | ||
> : ^^^^^^ | ||
|
||
address: string; | ||
>address : string | ||
> : ^^^^^^ | ||
|
||
}; | ||
|
||
type User = { | ||
>User : User | ||
> : ^^^^ | ||
|
||
id: number; | ||
>id : number | ||
> : ^^^^^^ | ||
|
||
name: string; | ||
>name : string | ||
> : ^^^^^^ | ||
|
||
address: Address; | ||
>address : Address | ||
> : ^^^^^^^ | ||
|
||
}; | ||
|
||
type FlattenedUser = FlattenType<User, FieldSelect>; | ||
>FlattenedUser : FlattenType<User, FieldSelect> | ||
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ||
|
||
type FlattenedUserKeys = keyof FlattenType<User, FieldSelect>; | ||
>FlattenedUserKeys : "id" | "name" | "address.address" | "address.postCode" | "address.description" | ||
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ||
|
||
export type FlattenTypeKeys<Source extends object, Target> = keyof { | ||
>FlattenTypeKeys : keyof { [Key in keyof Source as Key extends string ? Source[Key] extends object ? `${Key}.${keyof FlattenType<Source[Key], Target> & string}` : Key : never]-?: Target; } | ||
> : ^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ||
|
||
[Key in keyof Source as Key extends string | ||
? Source[Key] extends object | ||
? `${Key}.${keyof FlattenType<Source[Key], Target> & string}` | ||
: Key | ||
: never]-?: Target; | ||
}; | ||
|
||
type FlattenedUserKeys2 = FlattenTypeKeys<User, FieldSelect>; | ||
>FlattenedUserKeys2 : "id" | "name" | "address.address" | "address.postCode" | "address.description" | ||
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,11 +4,11 @@ mappedTypeConstraints2.ts(16,11): error TS2322: Type 'Mapped3<K>[Uppercase<K>]' | |
Type 'Mapped3<K>[Uppercase<string>]' is not assignable to type '{ a: K; }'. | ||
Type 'Mapped3<K>[string]' is not assignable to type '{ a: K; }'. | ||
mappedTypeConstraints2.ts(42,7): error TS2322: Type 'Mapped6<K>[keyof Mapped6<K>]' is not assignable to type '`_${string}`'. | ||
Type 'Mapped6<K>[string] | Mapped6<K>[number] | Mapped6<K>[symbol]' is not assignable to type '`_${string}`'. | ||
Type 'Mapped6<K>[string]' is not assignable to type '`_${string}`'. | ||
mappedTypeConstraints2.ts(51,57): error TS2322: Type 'Foo<T>[`get${T}`]' is not assignable to type 'T'. | ||
Type 'Mapped6<K>[`_${K}`]' is not assignable to type '`_${string}`'. | ||
Type 'Mapped6<K>[`_${string}`]' is not assignable to type '`_${string}`'. | ||
mappedTypeConstraints2.ts(59,57): error TS2322: Type 'Foo<T>[`get${T}`]' is not assignable to type 'T'. | ||
'T' could be instantiated with an arbitrary type which could be unrelated to 'Foo<T>[`get${T}`]'. | ||
mappedTypeConstraints2.ts(82,9): error TS2322: Type 'ObjectWithUnderscoredKeys<K>[`_${K}`]' is not assignable to type 'true'. | ||
mappedTypeConstraints2.ts(90,9): error TS2322: Type 'ObjectWithUnderscoredKeys<K>[`_${K}`]' is not assignable to type 'true'. | ||
Type 'ObjectWithUnderscoredKeys<K>[`_${string}`]' is not assignable to type 'true'. | ||
|
||
|
||
|
@@ -64,8 +64,16 @@ mappedTypeConstraints2.ts(82,9): error TS2322: Type 'ObjectWithUnderscoredKeys<K | |
let s: `_${string}` = obj[key]; // Error | ||
~ | ||
!!! error TS2322: Type 'Mapped6<K>[keyof Mapped6<K>]' is not assignable to type '`_${string}`'. | ||
!!! error TS2322: Type 'Mapped6<K>[string] | Mapped6<K>[number] | Mapped6<K>[symbol]' is not assignable to type '`_${string}`'. | ||
!!! error TS2322: Type 'Mapped6<K>[string]' is not assignable to type '`_${string}`'. | ||
!!! error TS2322: Type 'Mapped6<K>[`_${K}`]' is not assignable to type '`_${string}`'. | ||
!!! error TS2322: Type 'Mapped6<K>[`_${string}`]' is not assignable to type '`_${string}`'. | ||
Comment on lines
+67
to
+68
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I believe this change is a fix. The new error matches what was reported by TS 5.0: TS playground |
||
} | ||
|
||
type Mapped7<K extends string> = { | ||
[P in K as [P] extends [`_${string}`] ? P : never]: P; | ||
}; | ||
|
||
function f7<K extends string>(obj: Mapped7<K>, key: keyof Mapped7<K>) { | ||
let s: `_${string}` = obj[key]; | ||
} | ||
|
||
// Repro from #47794 | ||
|
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.
Alternatively, maybe this could be kept but repurposed slightly. It would have to check if used template literal types don't refer to the type variable introduced by 'in' clause more than once. I don't think this is something the compiler would usually do in any other place so I'm hesitant to say that this would be a better solution.