Skip to content

Allow type comparison when target is generic mapped type with key remapping #45700

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

Merged
merged 4 commits into from
Sep 8, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 36 additions & 15 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18585,39 +18585,60 @@ namespace ts {
originalErrorInfo = undefined;
}
}
else if (isGenericMappedType(target) && !target.declaration.nameType) {
// A source type T is related to a target type { [P in X]: T[P] }
const template = getTemplateTypeFromMappedType(target);
else if (isGenericMappedType(target)) {
// Check if source type `S` is related to target type `{ [P in Q]: T }` or `{ [P in Q as R]: T}`.
const keysRemapped = !!target.declaration.nameType;
const templateType = getTemplateTypeFromMappedType(target);
const modifiers = getMappedTypeModifiers(target);
if (!(modifiers & MappedTypeModifiers.ExcludeOptional)) {
if (template.flags & TypeFlags.IndexedAccess && (template as IndexedAccessType).objectType === source &&
(template as IndexedAccessType).indexType === getTypeParameterFromMappedType(target)) {
// If the mapped type has shape `{ [P in Q]: T[P] }`,
// source `S` is related to target if `T` = `S`, i.e. `S` is related to `{ [P in Q]: S[P] }`.
if (!keysRemapped && templateType.flags & TypeFlags.IndexedAccess && (templateType as IndexedAccessType).objectType === source &&
(templateType as IndexedAccessType).indexType === getTypeParameterFromMappedType(target)) {
return Ternary.True;
}
if (!isGenericMappedType(source)) {
const targetConstraint = getConstraintTypeFromMappedType(target);
// If target has shape `{ [P in Q as R]: T}`, then its keys have type `R`.
// If target has shape `{ [P in Q]: T }`, then its keys have type `Q`.
const targetKeys = keysRemapped ? getNameTypeFromMappedType(target)! : getConstraintTypeFromMappedType(target);
// Type of the keys of source type `S`, i.e. `keyof S`.
const sourceKeys = getIndexType(source, /*stringsOnly*/ undefined, /*noIndexSignatures*/ true);
const includeOptional = modifiers & MappedTypeModifiers.IncludeOptional;
const filteredByApplicability = includeOptional ? intersectTypes(targetConstraint, sourceKeys) : undefined;
// A source type T is related to a target type { [P in Q]: X } if Q is related to keyof T and T[Q] is related to X.
// A source type T is related to a target type { [P in Q]?: X } if some constituent Q' of Q is related to keyof T and T[Q'] is related to X.
const filteredByApplicability = includeOptional ? intersectTypes(targetKeys, sourceKeys) : undefined;
// A source type `S` is related to a target type `{ [P in Q]: T }` if `Q` is related to `keyof S` and `S[Q]` is related to `T`.
// A source type `S` is related to a target type `{ [P in Q as R]: T }` if `R` is related to `keyof S` and `S[R]` is related to `T.
// A source type `S` is related to a target type `{ [P in Q]?: T }` if some constituent `Q'` of `Q` is related to `keyof S` and `S[Q']` is related to `T`.
// A source type `S` is related to a target type `{ [P in Q as R]?: T }` if some constituent `R'` of `R` is related to `keyof S` and `S[R']` is related to `T`.
if (includeOptional
? !(filteredByApplicability!.flags & TypeFlags.Never)
: isRelatedTo(targetConstraint, sourceKeys)) {
const templateType = getTemplateTypeFromMappedType(target);
: isRelatedTo(targetKeys, sourceKeys)) {
const typeParameter = getTypeParameterFromMappedType(target);

// Fastpath: When the template has the form Obj[P] where P is the mapped type parameter, directly compare `source` with `Obj`
// to avoid creating the (potentially very large) number of new intermediate types made by manufacturing `source[P]`
// Fastpath: When the template type has the form `Obj[P]` where `P` is the mapped type parameter, directly compare source `S` with `Obj`
// to avoid creating the (potentially very large) number of new intermediate types made by manufacturing `S[P]`.
const nonNullComponent = extractTypesOfKind(templateType, ~TypeFlags.Nullable);
if (nonNullComponent.flags & TypeFlags.IndexedAccess && (nonNullComponent as IndexedAccessType).indexType === typeParameter) {
if (!keysRemapped && nonNullComponent.flags & TypeFlags.IndexedAccess && (nonNullComponent as IndexedAccessType).indexType === typeParameter) {
if (result = isRelatedTo(source, (nonNullComponent as IndexedAccessType).objectType, reportErrors)) {
return result;
}
}
else {
const indexingType = filteredByApplicability ? getIntersectionType([filteredByApplicability, typeParameter]) : typeParameter;
// We need to compare the type of a property on the source type `S` to the type of the same property on the target type,
// so we need to construct an indexing type representing a property, and then use indexing type to index the source type for comparison.

// If the target type has shape `{ [P in Q]: T }`, then a property of the target has type `P`.
// If the target type has shape `{ [P in Q]?: T }`, then a property of the target has type `P`,
// but the property is optional, so we only want to compare properties `P` that are common between `keyof S` and `Q`.
// If the target type has shape `{ [P in Q as R]: T }`, then a property of the target has type `R`.
// If the target type has shape `{ [P in Q as R]?: T }`, then a property of the target has type `R`,
// but the property is optional, so we only want to compare properties `R` that are common between `keyof S` and `R`.
const indexingType = keysRemapped
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not entirely sure this won't cause issues for the case where the target mapped type has an include optional modifier.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, to be clear here, in the keysRemapped case, we're skipping the getIntersectionType calls with the typeParameter, simply because the keys may or may not actually contain the remapping type parameter (eg, every key may just be mapped to string) - the targetKeys will already contain it if so. I think this is correct - it's just not necessarily clear. In the case where there's no as clause, we intersect the type parameter and the (filtered) keys so we get a generic property key - whereas when the keys are remapped, we don't need to do so as the remapping overrides that.

? (filteredByApplicability || targetKeys)
: filteredByApplicability
? getIntersectionType([filteredByApplicability, typeParameter])
: typeParameter;
Comment on lines +18635 to +18639
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const indexingType = keysRemapped
? (filteredByApplicability || targetKeys)
: filteredByApplicability
? getIntersectionType([filteredByApplicability, typeParameter])
: typeParameter;
const indexingType = keysRemapped ? (filteredByApplicability || targetKeys)
: filteredByApplicability ? getIntersectionType([filteredByApplicability, typeParameter])
: typeParameter;

const indexedAccessType = getIndexedAccessType(source, indexingType);
// Compare `S[indexingType]` to `T`, where `T` is the type of a property of the target type.
if (result = isRelatedTo(indexedAccessType, templateType, reportErrors)) {
return result;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
tests/cases/conformance/types/mapped/mappedTypeAsClauseRelationships.ts(12,9): error TS2322: Type 'T' is not assignable to type 'Modify<T>'.
tests/cases/conformance/types/mapped/mappedTypeAsClauseRelationships.ts(23,9): error TS2322: Type 'T' is not assignable to type 'FilterExclOpt<T>'.
tests/cases/conformance/types/mapped/mappedTypeAsClauseRelationships.ts(24,9): error TS2322: Type 'T' is not assignable to type 'ModifyExclOpt<T>'.


==== tests/cases/conformance/types/mapped/mappedTypeAsClauseRelationships.ts (3 errors) ====
// From original issue #45212:
type Methods<T> = { [P in keyof T as T[P] extends Function ? P : never]: T[P] };
type H<T> = T[keyof Methods<T>]; // Ok

// `Filter<T>` only filters out some keys of `T`.
type Filter<T> = { [P in keyof T as T[P] extends Function ? P : never]: T[P] };
// `Modify<T>` might modify some keys of `T`.
type Modify<T> = { [P in keyof T as P extends string? `bool${P}`: P]: T[P] };

function fun<T>(val: T) {
let x: Filter<T> = val; // Ok
let y: Modify<T> = val; // Error
~
!!! error TS2322: Type 'T' is not assignable to type 'Modify<T>'.
}

type FilterInclOpt<T> = { [P in keyof T as T[P] extends Function ? P : never]+?: T[P] };
type ModifyInclOpt<T> = { [P in keyof T as P extends string? `bool${P}`: never ]+?: T[P] };
type FilterExclOpt<T> = { [P in keyof T as T[P] extends Function ? P : never]-?: T[P] };
type ModifyExclOpt<T> = { [P in keyof T as P extends string? `bool${P}`: never ]-?: T[P] };

function fun2<T>(val: T) {
let x: FilterInclOpt<T> = val; // Ok
let y: ModifyInclOpt<T> = val; // Ok
Copy link
Member

@andrewbranch andrewbranch Sep 3, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My initial reaction to this one was that this is wrong because T might have some property that would conflict with the remapped property type, e.g. if T was instantiated with { foo: string, boolfoo: number }. However, it looks like that’s an unsoundness that already exists independent of mapped types entirely:

interface Foo {
    x?: string;
    y?: string;
    z?: string;
}

function f<T>(x: T) { // What if `T` is `{ x: number }`?
    const _: Foo = x;
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I tried to follow the already existing logic and reuse everything that's already there, so there might be inconsistencies that were already there or that result from the kind of types that are in key remapping clauses.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optional properties are a pretty big soundness hole, unfortunately. The unconstrained generic one is actually one I have a PR up to remove - #33570

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This exact discussed case has changed behavior in 51b346d#diff-25f367dc11e40edc9febb9d6f088e13f9c7c8086bb8b9f85aed321f0903bc46eR31-R33

One of my PRs bring back original behavior (somewhat accidentally, I wasn't aiming for it), see here

Which one is correct? 😅

let z: FilterExclOpt<T> = val; // Error
~
!!! error TS2322: Type 'T' is not assignable to type 'FilterExclOpt<T>'.
let w: ModifyExclOpt<T> = val; // Error
~
!!! error TS2322: Type 'T' is not assignable to type 'ModifyExclOpt<T>'.
}



41 changes: 41 additions & 0 deletions tests/baselines/reference/mappedTypeAsClauseRelationships.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
//// [mappedTypeAsClauseRelationships.ts]
// From original issue #45212:
type Methods<T> = { [P in keyof T as T[P] extends Function ? P : never]: T[P] };
type H<T> = T[keyof Methods<T>]; // Ok

// `Filter<T>` only filters out some keys of `T`.
type Filter<T> = { [P in keyof T as T[P] extends Function ? P : never]: T[P] };
// `Modify<T>` might modify some keys of `T`.
type Modify<T> = { [P in keyof T as P extends string? `bool${P}`: P]: T[P] };

function fun<T>(val: T) {
let x: Filter<T> = val; // Ok
let y: Modify<T> = val; // Error
}

type FilterInclOpt<T> = { [P in keyof T as T[P] extends Function ? P : never]+?: T[P] };
type ModifyInclOpt<T> = { [P in keyof T as P extends string? `bool${P}`: never ]+?: T[P] };
type FilterExclOpt<T> = { [P in keyof T as T[P] extends Function ? P : never]-?: T[P] };
type ModifyExclOpt<T> = { [P in keyof T as P extends string? `bool${P}`: never ]-?: T[P] };

function fun2<T>(val: T) {
let x: FilterInclOpt<T> = val; // Ok
let y: ModifyInclOpt<T> = val; // Ok
let z: FilterExclOpt<T> = val; // Error
let w: ModifyExclOpt<T> = val; // Error
}




//// [mappedTypeAsClauseRelationships.js]
function fun(val) {
var x = val; // Ok
var y = val; // Error
}
function fun2(val) {
var x = val; // Ok
var y = val; // Ok
var z = val; // Error
var w = val; // Error
}
142 changes: 142 additions & 0 deletions tests/baselines/reference/mappedTypeAsClauseRelationships.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
=== tests/cases/conformance/types/mapped/mappedTypeAsClauseRelationships.ts ===
// From original issue #45212:
type Methods<T> = { [P in keyof T as T[P] extends Function ? P : never]: T[P] };
>Methods : Symbol(Methods, Decl(mappedTypeAsClauseRelationships.ts, 0, 0))
>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 1, 13))
>P : Symbol(P, Decl(mappedTypeAsClauseRelationships.ts, 1, 21))
>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 1, 13))
>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 1, 13))
>P : Symbol(P, Decl(mappedTypeAsClauseRelationships.ts, 1, 21))
>Function : Symbol(Function, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>P : Symbol(P, Decl(mappedTypeAsClauseRelationships.ts, 1, 21))
>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 1, 13))
>P : Symbol(P, Decl(mappedTypeAsClauseRelationships.ts, 1, 21))

type H<T> = T[keyof Methods<T>]; // Ok
>H : Symbol(H, Decl(mappedTypeAsClauseRelationships.ts, 1, 80))
>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 2, 7))
>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 2, 7))
>Methods : Symbol(Methods, Decl(mappedTypeAsClauseRelationships.ts, 0, 0))
>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 2, 7))

// `Filter<T>` only filters out some keys of `T`.
type Filter<T> = { [P in keyof T as T[P] extends Function ? P : never]: T[P] };
>Filter : Symbol(Filter, Decl(mappedTypeAsClauseRelationships.ts, 2, 32))
>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 5, 12))
>P : Symbol(P, Decl(mappedTypeAsClauseRelationships.ts, 5, 20))
>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 5, 12))
>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 5, 12))
>P : Symbol(P, Decl(mappedTypeAsClauseRelationships.ts, 5, 20))
>Function : Symbol(Function, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>P : Symbol(P, Decl(mappedTypeAsClauseRelationships.ts, 5, 20))
>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 5, 12))
>P : Symbol(P, Decl(mappedTypeAsClauseRelationships.ts, 5, 20))

// `Modify<T>` might modify some keys of `T`.
type Modify<T> = { [P in keyof T as P extends string? `bool${P}`: P]: T[P] };
>Modify : Symbol(Modify, Decl(mappedTypeAsClauseRelationships.ts, 5, 79))
>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 7, 12))
>P : Symbol(P, Decl(mappedTypeAsClauseRelationships.ts, 7, 20))
>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 7, 12))
>P : Symbol(P, Decl(mappedTypeAsClauseRelationships.ts, 7, 20))
>P : Symbol(P, Decl(mappedTypeAsClauseRelationships.ts, 7, 20))
>P : Symbol(P, Decl(mappedTypeAsClauseRelationships.ts, 7, 20))
>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 7, 12))
>P : Symbol(P, Decl(mappedTypeAsClauseRelationships.ts, 7, 20))

function fun<T>(val: T) {
>fun : Symbol(fun, Decl(mappedTypeAsClauseRelationships.ts, 7, 77))
>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 9, 13))
>val : Symbol(val, Decl(mappedTypeAsClauseRelationships.ts, 9, 16))
>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 9, 13))

let x: Filter<T> = val; // Ok
>x : Symbol(x, Decl(mappedTypeAsClauseRelationships.ts, 10, 7))
>Filter : Symbol(Filter, Decl(mappedTypeAsClauseRelationships.ts, 2, 32))
>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 9, 13))
>val : Symbol(val, Decl(mappedTypeAsClauseRelationships.ts, 9, 16))

let y: Modify<T> = val; // Error
>y : Symbol(y, Decl(mappedTypeAsClauseRelationships.ts, 11, 7))
>Modify : Symbol(Modify, Decl(mappedTypeAsClauseRelationships.ts, 5, 79))
>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 9, 13))
>val : Symbol(val, Decl(mappedTypeAsClauseRelationships.ts, 9, 16))
}

type FilterInclOpt<T> = { [P in keyof T as T[P] extends Function ? P : never]+?: T[P] };
>FilterInclOpt : Symbol(FilterInclOpt, Decl(mappedTypeAsClauseRelationships.ts, 12, 1))
>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 14, 19))
>P : Symbol(P, Decl(mappedTypeAsClauseRelationships.ts, 14, 27))
>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 14, 19))
>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 14, 19))
>P : Symbol(P, Decl(mappedTypeAsClauseRelationships.ts, 14, 27))
>Function : Symbol(Function, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>P : Symbol(P, Decl(mappedTypeAsClauseRelationships.ts, 14, 27))
>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 14, 19))
>P : Symbol(P, Decl(mappedTypeAsClauseRelationships.ts, 14, 27))

type ModifyInclOpt<T> = { [P in keyof T as P extends string? `bool${P}`: never ]+?: T[P] };
>ModifyInclOpt : Symbol(ModifyInclOpt, Decl(mappedTypeAsClauseRelationships.ts, 14, 88))
>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 15, 19))
>P : Symbol(P, Decl(mappedTypeAsClauseRelationships.ts, 15, 27))
>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 15, 19))
>P : Symbol(P, Decl(mappedTypeAsClauseRelationships.ts, 15, 27))
>P : Symbol(P, Decl(mappedTypeAsClauseRelationships.ts, 15, 27))
>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 15, 19))
>P : Symbol(P, Decl(mappedTypeAsClauseRelationships.ts, 15, 27))

type FilterExclOpt<T> = { [P in keyof T as T[P] extends Function ? P : never]-?: T[P] };
>FilterExclOpt : Symbol(FilterExclOpt, Decl(mappedTypeAsClauseRelationships.ts, 15, 91))
>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 16, 19))
>P : Symbol(P, Decl(mappedTypeAsClauseRelationships.ts, 16, 27))
>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 16, 19))
>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 16, 19))
>P : Symbol(P, Decl(mappedTypeAsClauseRelationships.ts, 16, 27))
>Function : Symbol(Function, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>P : Symbol(P, Decl(mappedTypeAsClauseRelationships.ts, 16, 27))
>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 16, 19))
>P : Symbol(P, Decl(mappedTypeAsClauseRelationships.ts, 16, 27))

type ModifyExclOpt<T> = { [P in keyof T as P extends string? `bool${P}`: never ]-?: T[P] };
>ModifyExclOpt : Symbol(ModifyExclOpt, Decl(mappedTypeAsClauseRelationships.ts, 16, 88))
>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 17, 19))
>P : Symbol(P, Decl(mappedTypeAsClauseRelationships.ts, 17, 27))
>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 17, 19))
>P : Symbol(P, Decl(mappedTypeAsClauseRelationships.ts, 17, 27))
>P : Symbol(P, Decl(mappedTypeAsClauseRelationships.ts, 17, 27))
>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 17, 19))
>P : Symbol(P, Decl(mappedTypeAsClauseRelationships.ts, 17, 27))

function fun2<T>(val: T) {
>fun2 : Symbol(fun2, Decl(mappedTypeAsClauseRelationships.ts, 17, 91))
>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 19, 14))
>val : Symbol(val, Decl(mappedTypeAsClauseRelationships.ts, 19, 17))
>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 19, 14))

let x: FilterInclOpt<T> = val; // Ok
>x : Symbol(x, Decl(mappedTypeAsClauseRelationships.ts, 20, 7))
>FilterInclOpt : Symbol(FilterInclOpt, Decl(mappedTypeAsClauseRelationships.ts, 12, 1))
>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 19, 14))
>val : Symbol(val, Decl(mappedTypeAsClauseRelationships.ts, 19, 17))

let y: ModifyInclOpt<T> = val; // Ok
>y : Symbol(y, Decl(mappedTypeAsClauseRelationships.ts, 21, 7))
>ModifyInclOpt : Symbol(ModifyInclOpt, Decl(mappedTypeAsClauseRelationships.ts, 14, 88))
>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 19, 14))
>val : Symbol(val, Decl(mappedTypeAsClauseRelationships.ts, 19, 17))

let z: FilterExclOpt<T> = val; // Error
>z : Symbol(z, Decl(mappedTypeAsClauseRelationships.ts, 22, 7))
>FilterExclOpt : Symbol(FilterExclOpt, Decl(mappedTypeAsClauseRelationships.ts, 15, 91))
>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 19, 14))
>val : Symbol(val, Decl(mappedTypeAsClauseRelationships.ts, 19, 17))

let w: ModifyExclOpt<T> = val; // Error
>w : Symbol(w, Decl(mappedTypeAsClauseRelationships.ts, 23, 7))
>ModifyExclOpt : Symbol(ModifyExclOpt, Decl(mappedTypeAsClauseRelationships.ts, 16, 88))
>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 19, 14))
>val : Symbol(val, Decl(mappedTypeAsClauseRelationships.ts, 19, 17))
}



Loading