@@ -3752,7 +3752,28 @@ namespace ts {
3752
3752
return symbolToTypeNode(type.aliasSymbol, context, SymbolFlags.Type, typeArgumentNodes);
3753
3753
}
3754
3754
if (type.flags & (TypeFlags.Union | TypeFlags.Intersection)) {
3755
- const types = type.flags & TypeFlags.Union ? formatUnionTypes((<UnionType>type).types) : (<IntersectionType>type).types;
3755
+ const types = type.flags & TypeFlags.Union ? formatUnionTypes((<UnionType>type).types) : filter((<IntersectionType>type).types, t => !(context.flags & NodeBuilderFlags.UseAliasDefinedOutsideCurrentScope) || !(t.flags & TypeFlags.NominalBrand && !t.aliasSymbol));
3756
+ if (context.flags & NodeBuilderFlags.UseAliasDefinedOutsideCurrentScope && type.flags & TypeFlags.Intersection && length(types) !== length((<IntersectionType>type).types)) {
3757
+ // If an intersection had brands and no alias, add a `unique` to the front to indicate this
3758
+ // the brands aren't _precisely_ recoverable by this printback, but it's a good indicator in the LS that there's an unutterable nominal tag on the type
3759
+ // Ideally, we could hyperlink the `unique` we manufacture here to each of the brand locations it refers to
3760
+ context.approximateLength += 7;
3761
+ if (length(types) === 0) {
3762
+ context.approximateLength += 7;
3763
+ return createTypeOperatorNode(SyntaxKind.UniqueKeyword, createKeywordTypeNode(SyntaxKind.UnknownKeyword));
3764
+ }
3765
+ if (length(types) === 1) {
3766
+ return createTypeOperatorNode(SyntaxKind.UniqueKeyword, typeToTypeNodeHelper(types[0], context));
3767
+ }
3768
+ const nodes = mapToTypeNodes(types, context, /*isBareList*/ true);
3769
+ if (!length(nodes)) {
3770
+ if (!context.encounteredError && !(context.flags & NodeBuilderFlags.AllowEmptyUnionOrIntersection)) {
3771
+ context.encounteredError = true;
3772
+ }
3773
+ return undefined!; // TODO: GH#18217
3774
+ }
3775
+ return createTypeOperatorNode(SyntaxKind.UniqueKeyword, createUnionOrIntersectionTypeNode(SyntaxKind.IntersectionType, nodes!));
3776
+ }
3756
3777
if (length(types) === 1) {
3757
3778
return typeToTypeNodeHelper(types[0], context);
3758
3779
}
@@ -3799,6 +3820,17 @@ namespace ts {
3799
3820
if (type.flags & TypeFlags.Substitution) {
3800
3821
return typeToTypeNodeHelper((<SubstitutionType>type).typeVariable, context);
3801
3822
}
3823
+ if (type.flags & TypeFlags.NominalBrand) {
3824
+ if (!context.encounteredError && !(context.flags & NodeBuilderFlags.UseAliasDefinedOutsideCurrentScope) && !(context.flags & NodeBuilderFlags.AllowEmptyUnionOrIntersection)) {
3825
+ context.encounteredError = true;
3826
+ if (context.tracker && context.tracker.trackSymbol) {
3827
+ // TODO: issue a custom error message about brand name not being directly accessible
3828
+ context.tracker.trackSymbol(type.symbol, context.enclosingDeclaration, SymbolFlags.Type);
3829
+ }
3830
+ }
3831
+ context.approximateLength += 14;
3832
+ return createTypeOperatorNode(SyntaxKind.UniqueKeyword, createKeywordTypeNode(SyntaxKind.UnknownKeyword));
3833
+ }
3802
3834
3803
3835
return Debug.fail("Should be unreachable.");
3804
3836
@@ -10206,7 +10238,7 @@ namespace ts {
10206
10238
maybeTypeOfKind(type, TypeFlags.InstantiableNonPrimitive) ? getIndexTypeForGenericType(<InstantiableType | UnionOrIntersectionType>type, stringsOnly) :
10207
10239
getObjectFlags(type) & ObjectFlags.Mapped ? filterType(getConstraintTypeFromMappedType(<MappedType>type), t => !(noIndexSignatures && t.flags & (TypeFlags.Any | TypeFlags.String))) :
10208
10240
type === wildcardType ? wildcardType :
10209
- type.flags & TypeFlags.Unknown ? neverType :
10241
+ type.flags & ( TypeFlags.Unknown | TypeFlags.NominalBrand) ? neverType :
10210
10242
type.flags & (TypeFlags.Any | TypeFlags.Never) ? keyofConstraintType :
10211
10243
stringsOnly ? !noIndexSignatures && getIndexInfoOfType(type, IndexKind.String) ? stringType : getLiteralTypeFromProperties(type, TypeFlags.StringLiteral) :
10212
10244
!noIndexSignatures && getIndexInfoOfType(type, IndexKind.String) ? getUnionType([stringType, numberType, getLiteralTypeFromProperties(type, TypeFlags.UniqueESSymbol)]) :
@@ -10235,9 +10267,12 @@ namespace ts {
10235
10267
links.resolvedType = getIndexType(getTypeFromTypeNode(node.type));
10236
10268
break;
10237
10269
case SyntaxKind.UniqueKeyword:
10270
+ // Note, the first case does _not_ unwrap parenthesis - this is intentional
10271
+ // This means if you _actually want_ a _class_ of nominally branded symbols, you can
10272
+ // accomplish as much by writing `unique (symbol)` to avoid getting a symbol-tied-to-a-value
10238
10273
links.resolvedType = node.type.kind === SyntaxKind.SymbolKeyword
10239
10274
? getESSymbolLikeTypeForNode(walkUpParenthesizedTypes(node.parent))
10240
- : errorType ;
10275
+ : getUniqueBrandOfType(getTypeFromTypeNode(node.type), node.symbol, getAliasSymbolForTypeNode(node)) ;
10241
10276
break;
10242
10277
case SyntaxKind.ReadonlyKeyword:
10243
10278
links.resolvedType = getTypeFromTypeNode(node.type);
@@ -10249,6 +10284,37 @@ namespace ts {
10249
10284
return links.resolvedType;
10250
10285
}
10251
10286
10287
+ /**
10288
+ * Note that this construction of brands precludes the possibility of a branded `never` - a `unique never` will always just be `never`
10289
+ * Meanwhile, a `unique unknown` will produce _just_ the brand. A brand is printed as it's alias, or may be hinted at by `unique <type>`
10290
+ * (though that does not specify _which_ instance of `unique` is relevant, just that a brand has been applied!)
10291
+ */
10292
+ function getUniqueBrandOfType(type: Type, brand: Symbol, aliasSymbol: Symbol | undefined, aliasTypeArguments: readonly Type[] | undefined = getTypeArgumentsForAliasSymbol(aliasSymbol)) {
10293
+ const brandType = getOrCreateBrandFromSymbol(brand);
10294
+ if (type.flags & TypeFlags.AnyOrUnknown) {
10295
+ // A `unique any` or a `unique unknown` both become just the brand type, which needs an alias symbol to be printable
10296
+ // If a type like `type MyObj = { x: unique unknown }` is made, the type of `x` is _just_ the brand from the object literal
10297
+ // in such scenarios, all printback will simply be `unique unknown` and (if possible), a related span/link back to the `unique` keyword
10298
+ // where the type originated
10299
+ // Do note that this means `unique any` actually becomes a very restrictive type, because the `unique` brand removes the any-ness - this
10300
+ // is intentional. If you're opting in to nominal types, you _probably_ didn't want an `any` flowing in to destroy your brands.
10301
+ brandType.aliasSymbol = aliasSymbol;
10302
+ brandType.aliasTypeArguments = aliasTypeArguments;
10303
+ return brandType;
10304
+ }
10305
+ return getIntersectionType([type, brandType], aliasSymbol, aliasTypeArguments);
10306
+ }
10307
+
10308
+ function getOrCreateBrandFromSymbol(symbol: Symbol) {
10309
+ const links = getSymbolLinks(symbol);
10310
+ if (!links.brandType) {
10311
+ const brandType = <NominalBrandType>createType(TypeFlags.NominalBrand);
10312
+ brandType.symbol = symbol;
10313
+ links.brandType = brandType;
10314
+ }
10315
+ return links.brandType;
10316
+ }
10317
+
10252
10318
function createIndexedAccessType(objectType: Type, indexType: Type) {
10253
10319
const type = <IndexedAccessType>createType(TypeFlags.IndexedAccess);
10254
10320
type.objectType = objectType;
@@ -10297,6 +10363,9 @@ namespace ts {
10297
10363
}
10298
10364
10299
10365
function getPropertyTypeForIndexType(originalObjectType: Type, objectType: Type, indexType: Type, fullIndexType: Type, suppressNoImplicitAnyError: boolean, accessNode: ElementAccessExpression | IndexedAccessTypeNode | PropertyName | BindingName | SyntheticExpression | undefined, accessFlags: AccessFlags) {
10366
+ if (originalObjectType.flags & TypeFlags.NominalBrand) {
10367
+ return accessFlags & AccessFlags.Writing ? unknownType : neverType;
10368
+ }
10300
10369
const accessExpression = accessNode && accessNode.kind === SyntaxKind.ElementAccessExpression ? accessNode : undefined;
10301
10370
const propName = getPropertyNameFromIndex(indexType, accessNode);
10302
10371
if (propName !== undefined) {
@@ -33169,40 +33238,39 @@ namespace ts {
33169
33238
33170
33239
function checkGrammarTypeOperatorNode(node: TypeOperatorNode) {
33171
33240
if (node.operator === SyntaxKind.UniqueKeyword) {
33172
- if (node.type.kind !== SyntaxKind.SymbolKeyword) {
33173
- return grammarErrorOnNode(node.type, Diagnostics._0_expected, tokenToString(SyntaxKind.SymbolKeyword));
33174
- }
33175
-
33176
- const parent = walkUpParenthesizedTypes(node.parent);
33177
- switch (parent.kind) {
33178
- case SyntaxKind.VariableDeclaration:
33179
- const decl = parent as VariableDeclaration;
33180
- if (decl.name.kind !== SyntaxKind.Identifier) {
33181
- return grammarErrorOnNode(node, Diagnostics.unique_symbol_types_may_not_be_used_on_a_variable_declaration_with_a_binding_name);
33182
- }
33183
- if (!isVariableDeclarationInVariableStatement(decl)) {
33184
- return grammarErrorOnNode(node, Diagnostics.unique_symbol_types_are_only_allowed_on_variables_in_a_variable_statement);
33185
- }
33186
- if (!(decl.parent.flags & NodeFlags.Const)) {
33187
- return grammarErrorOnNode((<VariableDeclaration>parent).name, Diagnostics.A_variable_whose_type_is_a_unique_symbol_type_must_be_const);
33188
- }
33189
- break;
33241
+ if (node.type.kind === SyntaxKind.SymbolKeyword) {
33242
+ // Continue to recognize `unique symbol` as a special kind of type with certain restrictions to permit analysis
33243
+ const parent = walkUpParenthesizedTypes(node.parent);
33244
+ switch (parent.kind) {
33245
+ case SyntaxKind.VariableDeclaration:
33246
+ const decl = parent as VariableDeclaration;
33247
+ if (decl.name.kind !== SyntaxKind.Identifier) {
33248
+ return grammarErrorOnNode(node, Diagnostics.unique_symbol_types_may_not_be_used_on_a_variable_declaration_with_a_binding_name);
33249
+ }
33250
+ if (!isVariableDeclarationInVariableStatement(decl)) {
33251
+ return grammarErrorOnNode(node, Diagnostics.unique_symbol_types_are_only_allowed_on_variables_in_a_variable_statement);
33252
+ }
33253
+ if (!(decl.parent.flags & NodeFlags.Const)) {
33254
+ return grammarErrorOnNode((<VariableDeclaration>parent).name, Diagnostics.A_variable_whose_type_is_a_unique_symbol_type_must_be_const);
33255
+ }
33256
+ break;
33190
33257
33191
- case SyntaxKind.PropertyDeclaration:
33192
- if (!hasModifier(parent, ModifierFlags.Static) ||
33193
- !hasModifier(parent, ModifierFlags.Readonly)) {
33194
- return grammarErrorOnNode((<PropertyDeclaration>parent).name, Diagnostics.A_property_of_a_class_whose_type_is_a_unique_symbol_type_must_be_both_static_and_readonly);
33195
- }
33196
- break;
33258
+ case SyntaxKind.PropertyDeclaration:
33259
+ if (!hasModifier(parent, ModifierFlags.Static) ||
33260
+ !hasModifier(parent, ModifierFlags.Readonly)) {
33261
+ return grammarErrorOnNode((<PropertyDeclaration>parent).name, Diagnostics.A_property_of_a_class_whose_type_is_a_unique_symbol_type_must_be_both_static_and_readonly);
33262
+ }
33263
+ break;
33197
33264
33198
- case SyntaxKind.PropertySignature:
33199
- if (!hasModifier(parent, ModifierFlags.Readonly)) {
33200
- return grammarErrorOnNode((<PropertySignature>parent).name, Diagnostics.A_property_of_an_interface_or_type_literal_whose_type_is_a_unique_symbol_type_must_be_readonly);
33201
- }
33202
- break;
33265
+ case SyntaxKind.PropertySignature:
33266
+ if (!hasModifier(parent, ModifierFlags.Readonly)) {
33267
+ return grammarErrorOnNode((<PropertySignature>parent).name, Diagnostics.A_property_of_an_interface_or_type_literal_whose_type_is_a_unique_symbol_type_must_be_readonly);
33268
+ }
33269
+ break;
33203
33270
33204
- default:
33205
- return grammarErrorOnNode(node, Diagnostics.unique_symbol_types_are_not_allowed_here);
33271
+ default:
33272
+ return grammarErrorOnNode(node, Diagnostics.unique_symbol_types_are_not_allowed_here);
33273
+ }
33206
33274
}
33207
33275
}
33208
33276
else if (node.operator === SyntaxKind.ReadonlyKeyword) {
0 commit comments