Open blakeembrey opened 7 years ago
I would suggest the syntax is arguable here. Since TypeScript now allows leading pipe for union type.
class B {}
type A = | number |
B
Compiles now and is equivalent to type A = number | B
, thanks to automatic semicolon insertion.
I think this might not I expect if exact type is introduced.
Not sure if realted but FYI https://github.com/Microsoft/TypeScript/issues/7481
If the {| ... |}
syntax was adopted, we could build on mapped types so that you could write
type Exact<T> = {|
[P in keyof T]: P[T]
|}
and then you could write Exact<User>
.
This is probably the last thing I miss from Flow, compared to TypeScript.
The Object.assign
example is especially good. I understand why TypeScript behaves the way it does today, but most of the time I'd rather have the exact type.
@HerringtonDarkholme Thanks. My initial issue has mentioned that, but I omitted it in the end as someone would have a better syntax anyway, turns out they do 😄
@DanielRosenwasser That looks a lot more reasonable, thanks!
@wallverb I don't think so, though I'd also like to see that feature exist 😄
What if I want to express a union of types, where some of them are exact, and some of them are not? The suggested syntax would make it error-prone and difficult to read, even If extra attention is given for spacing:
|Type1| | |Type2| | Type3 | |Type4| | Type5 | |Type6|
Can you quickly tell which members of the union are not exact?
And without the careful spacing?
|Type1|||Type2||Type3||Type4||Type5||Type6|
(answer: Type3
, Type5
)
@rotemdan See the above answer, there's the generic type Extact
instead which is a more solid proposal than mine. I think this is the preferred approach.
There's also the concern of how it would look in editor hints, preview popups and compiler messages. Type aliases currently just "flatten" to raw type expressions. The alias is not preserved so the incomperhensible expressions would still appear in the editor, unless some special measures are applied to counteract that.
I find it hard to believe this syntax was accepted into a programming language like Flow, which does have unions with the same syntax as Typescript. To me it doesn't seem wise to introduce a flawed syntax that is fundamentally in conflict with existing syntax and then try very hard to "cover" it.
One interesting (amusing?) alternative is to use a modifier like only
. I had a draft for a proposal for this several months ago, I think, but I never submitted it:
function test(a: only string, b: only User) {};
That was the best syntax I could find back then.
Edit: just
might also work?
function test(a: just string, b: just User) {};
(Edit: now that I recall that syntax was originally for a modifier for nominal types, but I guess it doesn't really matter.. The two concepts are close enough so these keywords might also work here)
I was wondering, maybe both keywords could be introduced to describe two slightly different types of matching:
just T
(meaning: "exactly T
") for exact structural matching, as described here.only T
(meaning: "uniquely T
") for nominal matching.Nominal matching could be seen as an even "stricter" version of exact structural matching. It would mean that not only the type has to be structurally identical, the value itself must be associated with the exact same type identifier as specified. This may or may not support type aliases, in addition to interfaces and classes.
I personally don't believe the subtle difference would create that much confusion, though I feel it is up to the Typescript team to decide if the concept of a nominal modifier like only
seems appropriate to them. I'm only suggesting this as an option.
(Edit: just a note about only
when used with classes: there's an ambiguity here on whether it would allow for nominal subclasses when a base class is referenced - that needs to be discussed separately, I guess. To a lesser degree - the same could be considered for interfaces - though I don't currently feel it would be that useful)
This seems sort of like subtraction types in disguise. These issues might be relevant: https://github.com/Microsoft/TypeScript/issues/4183 https://github.com/Microsoft/TypeScript/issues/7993
@ethanresnick Why do you believe that?
This would be exceedingly useful in the codebase I'm working on right now. If this was already part of the language then I wouldn't have spent today tracking down an error.
(Perhaps other errors but not this particular error 😉)
I don't like the pipe syntax inspired by Flow. Something like exact
keyword behind interfaces would be easier to read.
exact interface Foo {}
@mohsen1 I'm sure most people would use the Exact
generic type in expression positions, so it shouldn't matter too much. However, I'd be concerned with a proposal like that as you might be prematurely overloading the left of the interface keyword which has previously been reserved for only exports (being consistent with JavaScript values - e.g. export const foo = {}
). It also indicates that maybe that keyword is available for types too (e.g. exact type Foo = {}
and now it'll be export exact interface Foo {}
).
With {| |}
syntax how would extends
work? will interface Bar extends Foo {| |}
be exact if Foo
is not exact?
I think exact
keyword makes it easy to tell if an interface is exact. It can (should?) work for type
too.
interface Foo {}
type Bar = exact Foo
Exceedingly helpful for things that work over databases or network calls to databases or SDKs like AWS SDK which take objects with all optional properties as additional data gets silently ignored and can lead to hard to very hard to find bugs :rose:
@mohsen1 That question seems irrelevant to the syntax, since the same question still exists using the keyword approach. Personally, I don't have a preferred answer and would have to play with existing expectations to answer it - but my initial reaction is that it shouldn't matter whether Foo
is exact or not.
The usage of an exact
keyword seems ambiguous - you're saying it can be used like exact interface Foo {}
or type Foo = exact {}
? What does exact Foo | Bar
mean? Using the generic approach and working with existing patterns means there's no re-invention or learning required. It's just interface Foo {||}
(this is the only new thing here), then type Foo = Exact<{}>
and Exact<Foo> | Bar
.
We talked about this for quite a while. I'll try to summarize the discussion.
Exact types are just a way to detect extra properties. The demand for exact types dropped off a lot when we initially implemented excess property checking (EPC). EPC was probably the biggest breaking change we've taken but it has paid off; almost immediately we got bugs when EPC didn't detect an excess property.
For the most part where people want exact types, we'd prefer to fix that by making EPC smarter. A key area here is when the target type is a union type - we want to just take this as a bug fix (EPC should work here but it's just not implemented yet).
Related to EPC is the problem of all-optional types (which I call "weak" types). Most likely, all weak types would want to be exact. We should just implement weak type detection (#7485 / #3842); the only blocker here is intersection types which require some extra complexity in implementation.
The first major problem we see with exact types is that it's really unclear which types should be marked exact.
At one end of the spectrum, you have functions which will literally throw an exception (or otherwise do bad things) if given an object with an own-key outside of some fixed domain. These are few and far between (I can't name an example from memory). In the middle, there are functions which silently ignore
unknown properties (almost all of them). And at the other end you have functions which generically operate over all properties (e.g. Object.keys
).
Clearly the "will throw if given extra data" functions should be marked as accepting exact types. But what about the middle? People will likely disagree. Point2D
/ Point3D
is a good example - you might reasonably say that a magnitude
function should have the type (p: exact Point2D) => number
to prevent passing a Point3D
. But why can't I pass my { x: 3, y: 14, units: 'meters' }
object to that function? This is where EPC comes in - you want to detect that "extra" units
property in locations where it's definitely discarded, but not actually block calls that involve aliasing.
We have some basic tenets that exact types would invalidate. For example, it's assumed that a type T & U
is always assignable to T
, but this fails if T
is an exact type. This is problematic because you might have some generic function that uses this T & U -> T
principle, but invoke the function with T
instantiated with an exact type. So there's no way we could make this sound (it's really not OK to error on instantiation) - not necessarily a blocker, but it's confusing to have a generic function be more permissive than a manually-instantiated version of itself!
It's also assumed that T
is always assignable to T | U
, but it's not obvious how to apply this rule if U
is an exact type. Is { s: "hello", n: 3 }
assignable to { s: string } | Exact<{ n: number }>
? "Yes" seems like the wrong answer because whoever looks for n
and finds it won't be happy to see s
, but "No" also seems wrong because we've violated the basic T -> T | U
rule.
What is the meaning of function f<T extends Exact<{ n: number }>(p: T)
? :confused:
Often exact types are desired where what you really want is an "auto-disjointed" union. In other words, you might have an API that can accept { type: "name", firstName: "bob", lastName: "bobson" }
or { type: "age", years: 32 }
but don't want to accept { type: "age", years: 32, firstName: 'bob" }
because something unpredictable will happen. The "right" type is arguably { type: "name", firstName: string, lastName: string, age: undefined } | { type: "age", years: number, firstName: undefined, lastName: undefined }
but good golly that is annoying to type out. We could potentially think about sugar for creating types like this.
Our hopeful diagnosis is that this is, outside of the relatively few truly-closed APIs, an XY Problem solution. Wherever possible we should use EPC to detect "bad" properties. So if you have a problem and you think exact types are the right solution, please describe the original problem here so we can compose a catalog of patterns and see if there are other solutions which would be less invasive/confusing.
The main place I see people get surprised by having no exact object type is in the behaviour of Object.keys
and for..in
-- they always produce a string
type instead of 'a'|'b'
for something typed { a: any, b: any }
.
As I mentioned in https://github.com/Microsoft/TypeScript/issues/14094 and you described in Miscellany section it's annoying that {first: string, last: string, fullName: string}
conforms to {first: string; last: string} | {fullName: string}
.
For example, it's assumed that a type T & U is always assignable to T, but this fails if T is an exact type
If T
is an exact type, then presumably T & U
is never
(or T === U
). Right?
Or U
is a non-exact subset of T
My use case that lead me to this suggestion are redux reducers.
interface State {
name: string;
}
function nameReducer(state: State, action: Action<string>): State {
return {
...state,
fullName: action.payload // compiles, but it's an programming mistake
}
}
As you pointed out in the summary, my issue isn't directly that I need exact interfaces, I need the spread operator to work precisely. But since the behavior of the spread operator is given by JS, the only solution that comes to my mind is to define the return type or the interface to be exact.
Do I understand correctly that assigning a value of T
to Exact<T>
would be an error?
interface Dog {
name: string;
isGoodBoy: boolean;
}
let a: Dog = { name: 'Waldo', isGoodBoy: true };
let b: Exact<Dog> = a;
In this example, narrowing Dog
to Exact<Dog>
would not be safe, right?
Consider this example:
interface PossiblyFlyingDog extends Dog {
canFly: boolean;
}
let c: PossiblyFlyingDog = { ...a, canFly: true };
let d: Dog = c; // this is okay
let e: Exact<Dog> = d; // but this is not
@leonadler Yes, that'd be the idea. You could only assign Exact<T>
to Exact<T>
. My immediate use-case is that validation functions would be handling the Exact
types (e.g. taking request payloads as any
and outputting valid Exact<T>
). Exact<T>
, however, would be assignable to T
.
@nerumo
As you pointed out in the summary, my issue isn't directly that I need exact interfaces, I need the spread operator to work precisely. But since the behavior of the spread operator is given by JS, the only solution that comes to my mind is to define the return type or the interface to be exact.
I have bumped on the same issue and figured out this solution which for me is quite elegant workaround :)
export type State = {
readonly counter: number,
readonly baseCurrency: string,
};
// BAD
export function badReducer(state: State = initialState, action: Action): State {
if (action.type === INCREASE_COUNTER) {
return {
...state,
counterTypoError: state.counter + 1, // OK
}; // it's a bug! but the compiler will not find it
}
}
// GOOD
export function goodReducer(state: State = initialState, action: Action): State {
let partialState: Partial<State> | undefined;
if (action.type === INCREASE_COUNTER) {
partialState = {
counterTypoError: state.counter + 1, // Error: Object literal may only specify known properties, and 'counterTypoError' does not exist in type 'Partial<State>'.
}; // now it's showing a typo error correctly
}
if (action.type === CHANGE_BASE_CURRENCY) {
partialState = { // Error: Types of property 'baseCurrency' are incompatible. Type 'number' is not assignable to type 'string'.
baseCurrency: 5,
}; // type errors also works fine
}
return partialState != null ? { ...state, ...partialState } : state;
}
you can find more in this section of my redux guide:
Note that this could be solved in userland using my constraint types proposal (#13257):
type Exact<T> = [
case U in U extends T && T extends U: T,
];
Edit: Updated syntax relative to proposal
@piotrwitek thank you, the Partial trick works perfectly and already found a bug in my code base ;) that's worth the little boilerplate code. But still I agree with @isiahmeadows that an Exact
@piotrwitek using Partial like that almost solved my problem, but it still allows the properties to become undefined even if the State interface clams they aren't (I'm assuming strictNullChecks).
I ended up with something slightly more complex to preserve the interface types:
export function updateWithPartial<S extends object>(current: S, update: Partial<S>): S {
return Object.assign({}, current, update);
}
export function updateWith<S extends object, K extends keyof S>(current: S, update: {[key in K]: S[key]}): S {
return Object.assign({}, current, update);
}
interface I {
foo: string;
bar: string;
}
const f: I = {foo: "a", bar: "b"}
updateWithPartial(f, {"foo": undefined}).foo.replace("a", "x"); // Compiles, but fails at runtime
updateWith(f, {foo: undefined}).foo.replace("a", "x"); // Does not compile
updateWith(f, {foo: "c"}).foo.replace("a", "x"); // Compiles and works
@asmundg that is correct, the solution will accept undefined, but from my point of view this is acceptable, because in my solutions I'm using only action creators with required params for payload, and this will ensure that no undefined value should ever be assigned to a non-nullable property. Practically I'm using this solution for quite some time in production and this problem never happened, but let me know your concerns.
export const CHANGE_BASE_CURRENCY = 'CHANGE_BASE_CURRENCY';
export const actionCreators = {
changeBaseCurrency: (payload: string) => ({
type: CHANGE_BASE_CURRENCY as typeof CHANGE_BASE_CURRENCY, payload,
}),
}
store.dispatch(actionCreators.changeBaseCurrency()); // Error: Supplied parameters do not match any signature of call target.
store.dispatch(actionCreators.changeBaseCurrency(undefined)); // Argument of type 'undefined' is not assignable to parameter of type 'string'.
store.dispatch(actionCreators.changeBaseCurrency('USD')); // OK => { type: "CHANGE_BASE_CURRENCY", payload: 'USD' }
DEMO%3B%20%2F%2F%20Error%3A%20Supplied%20parameters%20do%20not%20match%20any%20signature%20of%20call%20target.%0D%0Astore.dispatch(actionCreators.changeBaseCurrency(undefined))%3B%20%2F%2F%20Argument%20of%20type%20'undefined'%20is%20not%20assignable%20to%20parameter%20of%20type%20'string'.%0D%0Astore.dispatch(actionCreators.changeBaseCurrency('USD'))%3B%20%2F%2F%20OK%20%3D%3E%20%7B%20type%3A%20%22CHANGE_BASE_CURRENCY%22%2C%20payload%3A%20'USD'%20%7D) - enable strictNullChecks in options
you can also make a nullable payload as well if you need to, you can read more in my guide: https://github.com/piotrwitek/react-redux-typescript-guide#actions
When Rest Types get merged in, this feature can be easily made syntactic sugar over them.
The type equality logic should be made strict - only types with the same properties or types which have rest properties that can be instantiated in such a way that their parent types have the same properties are considered matching. To preserve backward compatibility, a synthetic rest type is added to all types unless one already exists. A new flag --strictTypes
is also added, which suppresses the addition of synthetic rest parameters.
Equalities under --strictTypes
:
type A = { x: number, y: string };
type B = { x: number, y: string, ...restB: <T>T };
type C = { x: number, y: string, z: boolean, ...restC: <T>T };
declare const a: A;
declare const b: B;
declare const c: C;
a = b; // Error, type B has extra property: "restB"
a = c; // Error, type C has extra properties: "z", "restC"
b = a; // OK, restB inferred as {}
b = c; // OK, restB inferred as { z: boolean, ...restC: <T>T }
c = a; // Error, type A is missing property: "z"
// restC inferred as {}
c = b; // Error, type B is missing property: "z"
// restC inferred as restB
If --strictTypes
is not switched on a ...rest: <T>T
property is automatically added on type A
. This way the lines a = b;
and a = c;
will no longer be errors, as is the case with variable b
on the two lines that follow.
it's assumed that a type T & U is always assignable to T, but this fails if T is an exact type.
Yes, &
allows bogus logic but so is the case with string & number
. Both string
and number
are distinct rigid types that cannot be intersected, however the type system allows it. Exact types are also rigid, so the inconsistency is still consistent. The problem lies in the &
operator - it's unsound.
Is { s: "hello", n: 3 } assignable to { s: string } | Exact<{ n: number }>.
This can be translated to:
type Test = { s: string, ...rest: <T>T } | { n: number }
const x: Test = { s: "hello", n: 3 }; // OK, s: string; rest inferred as { n: number }
So the answer should be "yes". It's unsafe to union exact with non-exact types, as the non-exact types subsume all exact types unless a discriminator property is present.
Re: the function f<T extends Exact<{ n: number }>(p: T)
in @RyanCavanaugh's comment above, in one of my libraries I would very much like to implement the following function:
const checkType = <T>() => <U extends Exact<T>>(value: U) => value;
I.e. a function that returns it's parameter with its exact same type, but at the same time also check whether it's type is also exactly the same type as another (T).
Here is a bit contrived example with three of my failed tries to satisfy both requirements:
CorrectObject
HasX
without specifying HasX
as the object's type
type AllowedFields = "x" | "y";
type CorrectObject = {[field in AllowedFields]?: number | string};
type HasX = { x: number };
function objectLiteralAssignment() { const o: CorrectObject = { x: 1, y: "y", // z: "z" // z is correctly prevented to be defined for o by Excess Properties rules };
const oAsHasX: HasX = o; // error: Types of property 'x' are incompatible. }
function objectMultipleAssignment() { const o = { x: 1, y: "y", z: "z", }; const o2 = o as CorrectObject; // succeeds, but undesirable property z is allowed
type HasX = { x: number };
const oAsHasX: HasX = o; // succeeds }
function genericExtends() {
const checkType =
type HasX = { x: number };
const oAsHasX: HasX = o; // succeeds }
Here `HasX` is a greatly simplified type (the actual type maps o against a schema type) which is defined in a different layer than the constant itself, so I can't make `o`'s type to be (`CorrectObject & HasX`).
With Exact Types, the solution would be:
```ts
function exactTypes() {
const checkType = <T>() => <U extends Exact<T>>(value: U) => value;
const o = checkType<CorrectObject>()({
x: 1,
y: "y",
// z: "z", // undesirable property z is *not* allowed
}); // o is inferred to be { x: number; y: string; }
type HasX = { x: number };
const oAsHasX: HasX = o; // succeeds
}
@andy-ms
If T is an exact type, then presumably T & U is never (or T === U). Right?
I think T & U
should be never
only if U
is provably incompatible with T
, e.g. if T
is Exact<{x: number | string}>
and U
is {[field: string]: number}
, then T & U
should be Exact<{x: number}>
See the first response to that:
Or U is a non-exact subset of T
I would say, if U is assignable to T, then T & U === T
. But if T
and U
are different exact types, then T & U === never
.
In your example, why is it necessary to have a checkType
function that does nothing? Why not just have const o: Exact<CorrectObject> = { ... }
?
Because it loses the information that x definitely exists (optional in CorrectObject) and is number (number | string in CorrectObject). Or perhaps I've misunderstood what Exact means, I thought it would just prevent extraneous properties, not that it would recurively mean all types must be exactly the same.
One more consideration in support for Exact Types and against the current EPC is refactoring - if Extract Variable refactoring was available, one would lose EPC unless the extracted variable introduced a type annotation, which could become very verbose.
To clarify why I supoort for Exact Types - it's not for discriminated unions but spelling errors and erronously extraneous properties in case the type costraint cannot be specified at the same time as the object literal.
@andy-ms
I would say, if U is assignable to T, then T & U === T. But if T and U are different exact types, then T & U === never.
The &
type operator is intersection operator, the result of it is the common subset of both sides, which doesn't necessarily equal either. Simplest example I can think of:
type T = Exact<{ x?: any, y: any }>;
type U = { x: any, y? any };
here T & U
should be Exact<{ x: any, y: any }>
, which is a subset of both T
and U
, but neither T
is a subset of U
(missing x) nor U
is a subset of T
(missing y).
This should work independent of whether T
, U
, or T & U
are exact types.
@magnushiie You have a good point -- exact types can limit assignability from types with a greater width, but still allow assignability from types with a greater depth. So you could intersect Exact<{ x: number | string }>
with Exact<{ x: string | boolean }>
to get Exact<{ x: string }>
. One problem is that this isn't actually typesafe if x
isn't readonly -- we might want to fix that mistake for exact types, since they mean opting in to stricter behavior.
Exact types could also be used for type arguments relations issues to index signatures.
interface T {
[index: string]: string;
}
interface S {
a: string;
b: string;
}
interface P extends S {
c: number;
}
declare function f(t: T);
declare function f2(): P;
const s: S = f2();
f(s); // Error because an interface can have more fields that is not conforming to an index signature
f({ a: '', b: '' }); // No error because literals is exact by default
Here's a hacky way to check for exact type:
// type we'll be asserting as exact:
interface TextOptions {
alignment: string;
color?: string;
padding?: number;
}
// when used as a return type:
function getDefaultOptions(): ExactReturn<typeof returnValue, TextOptions> {
const returnValue = { colour: 'blue', alignment: 'right', padding: 1 };
// ERROR: ^^ Property 'colour' is missing in type 'TextOptions'.
return returnValue
}
// when used as a type:
function example(a: TextOptions) {}
const someInput = {padding: 2, colour: '', alignment: 'right'}
example(someInput as Exact<typeof someInput, TextOptions>)
// ERROR: ^^ Property 'colour' is missing in type 'TextOptions'.
Unfortunately it's not currently possible to make the Exact assertion as a type-parameter, so it has to be made during call time (i.e. you need to remember about it).
Here are the helper utils required to make it work (thanks to @tycho01 for some of them):
type Exact<A, B extends Difference<A, B>> = AssertPassThrough<Difference<A, B>, A, B>
type ExactReturn<A, B extends Difference<A, B>> = B & Exact<A, B>
type AssertPassThrough<Actual, Passthrough, Expected extends Actual> = Passthrough;
type Difference<A, Without> = {
[P in DiffUnion<keyof A, keyof Without>]: A[P];
}
type DiffUnion<T extends string, U extends string> =
({[P in T]: P } &
{ [P in U]: never } &
{ [k: string]: never })[T];
Nice one! @gcanti (typelevel-ts
) and @pelotom (type-zoo
) might be interested as well. :)
To anyone interested, I found a simple way of enforcing exact types on function parameters. Works on TS 2.7, at least.
function myFn<T extends {[K in keyof U]: any}, U extends DesiredType>(arg: T & U): void;
EDIT: I guess for this to work you must specify an object literal directly into the argument; this doesn't work if you declare a separate const above and pass that in instead. :/ But one workaround is to just use object spread at the call site, i.e., myFn({...arg})
.
EDIT: sorry, I didn't read that you mentioned TS 2.7 only. I will test it there!
@vaskevich ~~I can't seem to get it to work, i.e. it's not detecting colour
as an excess property~~:
When conditional types land (#21316) you can do the following to require exact types as function parameters, even for "non-fresh" object literals:
type Exactify<T, X extends T> = T & {
[K in keyof X]: K extends keyof T ? X[K] : never
}
type Foo = {a?: string, b: number}
declare function requireExact<X extends Exactify<Foo, X>>(x: X): void;
const exact = {b: 1};
requireExact(exact); // okay
const inexact = {a: "hey", b: 3, c: 123};
requireExact(inexact); // error
// Types of property 'c' are incompatible.
// Type 'number' is not assignable to type 'never'.
Of course if you widen the type it won't work, but I don't think there's anything you can really do about that:
const inexact = {a: "hey", b: 3, c: 123} as Foo;
requireExact(inexact); // okay
Thoughts?
Looks like progress is being made on function parameters. Has anyone found a way to enforce exact types for a function return value?
@jezzgoodwin not really. See #241 which is the root cause of function returns not being properly checked for extra properties
One more use case. I've just almost run into a bug because of the following situation that is not reported as an error:
interface A {
field: string;
}
interface B {
field2: string;
field3?: string;
}
type AorB = A | B;
const fixture: AorB[] = [
{
field: 'sfasdf',
field3: 'asd' // ok?!
},
];
The obvious solution for this could be:
type AorB = Exact<A> | Exact<B>;
I saw a workaround proposed in #16679 but in my case, the type is AorBorC
(may grow) and each object have multiple properties, so I it's rather hard to manually compute set of fieldX?:never
properties for each type.
@michalstocki Isn't that #20863? You want excess property checking on unions to be stricter.
Anyway, in the absence of exact types and strict excess property checking on unions, you can do these fieldX?:never
properties programmatically instead of manually by using conditional types:
type AllKeys<U> = U extends any ? keyof U : never
type ExclusifyUnion<U> = [U] extends [infer V] ?
V extends any ?
(V & {[P in Exclude<AllKeys<U>, keyof V>]?: never})
: never : never
And then define your union as
type AorB = ExclusifyUnion<A | B>;
which expands out to
type AorB = (A & {
field2?: undefined;
field3?: undefined;
}) | (B & {
field?: undefined;
})
automatically. It works for any AorBorC
also.
Also see https://github.com/Microsoft/TypeScript/issues/14094#issuecomment-373780463 for exclusive or implementation
This is a proposal to enable a syntax for exact types. A similar feature can be seen in Flow (https://flowtype.org/docs/objects.html#exact-object-types), but I would like to propose it as a feature used for type literals and not interfaces. The specific syntax I'd propose using is the pipe (which almost mirrors the Flow implementation, but it should surround the type statement), as it's familiar as the mathematical absolute syntax.
This syntax change would be a new feature and affect new definition files being written if used as a parameter or exposed type. This syntax could be combined with other more complex types.
Apologies in advance if this is a duplicate, I could not seem to find the right keywords to find any duplicates of this feature.
Edit: This post was updated to use the preferred syntax proposal mentioned at https://github.com/Microsoft/TypeScript/issues/12936#issuecomment-267272371, which encompasses using a simpler syntax with a generic type to enable usage in expressions.