diff --git a/packages/typescript/src/api/proto.generated.ts b/packages/typescript/src/api/proto.generated.ts index 6528b59b0f3f9..42099e84f5dda 100644 --- a/packages/typescript/src/api/proto.generated.ts +++ b/packages/typescript/src/api/proto.generated.ts @@ -1229,6 +1229,7 @@ export interface CompilerOptions { disableSolutionSearching?: boolean; disableReferencedProjectLoad?: boolean; erasableSyntaxOnly?: boolean; + enforceReadonly?: boolean; exactOptionalPropertyTypes?: boolean; experimentalDecorators?: boolean; forceConsistentCasingInFileNames?: boolean; diff --git a/tsc/internal/checker/checker.go b/tsc/internal/checker/checker.go index fb3c33c01b814..b0d969c498067 100644 --- a/tsc/internal/checker/checker.go +++ b/tsc/internal/checker/checker.go @@ -615,6 +615,7 @@ type Checker struct { noImplicitAny bool noImplicitThis bool useUnknownInCatchVariables bool + enforceReadonly bool exactOptionalPropertyTypes bool canCollectSymbolAliasAccessibilityData bool wasCanceled bool @@ -930,6 +931,7 @@ func NewChecker(program Program, tracer *Tracer) (*Checker, *sync.Mutex) { c.noImplicitAny = c.compilerOptions.GetStrictOptionValue(c.compilerOptions.NoImplicitAny) c.noImplicitThis = c.compilerOptions.GetStrictOptionValue(c.compilerOptions.NoImplicitThis) c.useUnknownInCatchVariables = c.compilerOptions.GetStrictOptionValue(c.compilerOptions.UseUnknownInCatchVariables) + c.enforceReadonly = c.compilerOptions.EnforceReadonly == core.TSTrue c.exactOptionalPropertyTypes = c.compilerOptions.ExactOptionalPropertyTypes == core.TSTrue c.canCollectSymbolAliasAccessibilityData = c.compilerOptions.VerbatimModuleSyntax.IsFalseOrUnknown() c.arrayVariances = []VarianceFlags{VarianceFlagsCovariant} @@ -13266,10 +13268,6 @@ func (c *Checker) checkObjectLiteral(node *ast.Node, checkMode CheckMode) *Type } } inConstContext := c.isConstContext(node) - var checkFlags ast.CheckFlags - if inConstContext { - checkFlags = ast.CheckFlagsReadonly - } objectFlags := ObjectFlagsFreshLiteral patternWithComputedProperties := false hasComputedStringProperty := false @@ -13330,6 +13328,10 @@ func (c *Checker) checkObjectLiteral(node *ast.Node, checkMode CheckMode) *Type if computedNameType != nil && isTypeUsableAsPropertyName(computedNameType) { nameType = computedNameType } + checkFlags := ast.CheckFlags(0) + if inConstContext && !(c.enforceReadonly && contextualType != nil && c.isContextualPropertyMutable(contextualType, member.Name, nameType)) { + checkFlags = ast.CheckFlagsReadonly + } var prop *ast.Symbol if nameType != nil { prop = c.newSymbolEx(ast.SymbolFlagsProperty|member.Flags, getPropertyNameFromType(nameType), checkFlags|ast.CheckFlagsLate) @@ -29168,38 +29170,37 @@ func getMappedTypeModifiers(t *Type) MappedTypeModifiers { return modifiers } -// Return -1, 0, or 1, where -1 means optionality is stripped (i.e. -?), 0 means optionality is unchanged, and 1 means -// optionality is added (i.e. +?). -func getMappedTypeOptionality(t *Type) int { - modifiers := getMappedTypeModifiers(t) - switch { - case modifiers&MappedTypeModifiersExcludeOptional != 0: - return -1 - case modifiers&MappedTypeModifiersIncludeOptional != 0: - return 1 - } - return 0 -} - -// Return -1, 0, or 1, for stripped, unchanged, or added optionality respectively. When a homomorphic mapped type doesn't -// modify optionality, recursively consult the optionality of the type being mapped over to see if it strips or adds optionality. -// For intersections, return -1 or 1 when all constituents strip or add optionality, otherwise return 0. -func (c *Checker) getCombinedMappedTypeOptionality(t *Type) int { +// Return the effective modifiers of a mapped type. When a homomorphic mapped type doesn't include modifiers, instead +// recursively obtain the effective modifiers of the type being mapped over. For intersections, return 0 if the effective +// modifiers differ between constituent types. +func (c *Checker) getEffectiveMappedTypeModifiers(t *Type, mask MappedTypeModifiers) MappedTypeModifiers { if t.objectFlags&ObjectFlagsMapped != 0 { - optionality := getMappedTypeOptionality(t) - if optionality != 0 { - return optionality + modifiers := getMappedTypeModifiers(t) & mask + if modifiers != 0 { + return modifiers } - return c.getCombinedMappedTypeOptionality(c.getModifiersTypeFromMappedType(t)) + return c.getEffectiveMappedTypeModifiers(c.getModifiersTypeFromMappedType(t), mask) } if t.flags&TypeFlagsIntersection != 0 { - optionality := c.getCombinedMappedTypeOptionality(t.Types()[0]) + modifiers := c.getEffectiveMappedTypeModifiers(t.Types()[0], mask) for _, t := range t.Types()[1:] { - if c.getCombinedMappedTypeOptionality(t) != optionality { + if c.getEffectiveMappedTypeModifiers(t, mask) != modifiers { return 0 } } - return optionality + return modifiers + } + return 0 +} + +// Return -1, 0, or 1, where -1 means modifier is stripped, 0 means modifier is unchanged, and 1 means modifier is added. +func (c *Checker) getMappedTypeModifiersRank(t *Type, mask MappedTypeModifiers) int { + modifiers := c.getEffectiveMappedTypeModifiers(t, mask) + if modifiers&(MappedTypeModifiersExcludeReadonly|MappedTypeModifiersExcludeOptional) != 0 { + return -1 + } + if modifiers&(MappedTypeModifiersIncludeReadonly|MappedTypeModifiersIncludeOptional) != 0 { + return 1 } return 0 } @@ -29439,10 +29440,10 @@ func (c *Checker) substituteIndexedMappedType(objectType *Type, index *Type) *Ty mapper := newSimpleTypeMapper(c.getTypeParameterFromMappedType(objectType), index) templateMapper := c.combineTypeMappers(objectType.AsMappedType().mapper, mapper) instantiatedTemplateType := c.instantiateType(c.getTemplateTypeFromMappedType(core.OrElse(objectType.AsMappedType().target, objectType)), templateMapper) - isOptional := getMappedTypeOptionality(objectType) > 0 + isOptional := getMappedTypeModifiers(objectType)&MappedTypeModifiersIncludeOptional != 0 if !isOptional { if c.isGenericType(objectType) { - isOptional = c.getCombinedMappedTypeOptionality(c.getModifiersTypeFromMappedType(objectType)) > 0 + isOptional = c.getEffectiveMappedTypeModifiers(c.getModifiersTypeFromMappedType(objectType), MappedTypeModifiersIncludeOptional|MappedTypeModifiersExcludeOptional)&MappedTypeModifiersIncludeOptional != 0 } else { isOptional = c.couldAccessOptionalProperty(objectType, index) } @@ -30751,6 +30752,28 @@ func (c *Checker) getTypeOfPropertyOfContextualTypeEx(t *Type, name string, name }, true /*noReductions*/) } +func (c *Checker) isContextualPropertyMutable(t *Type, name string, nameType *Type) bool { + return someType(t, func(t *Type) bool { + propertyName := name + if nameType != nil { + if !isTypeUsableAsPropertyName(nameType) { + return false + } + propertyName = getPropertyNameFromType(nameType) + } + if prop := c.getPropertyOfType(t, propertyName); prop != nil { + return !c.isReadonlySymbol(prop) + } + if nameType == nil { + nameType = c.getStringLiteralType(name) + } + if indexInfo := c.findApplicableIndexInfo(c.getIndexInfosOfStructuredType(t), nameType); indexInfo != nil { + return !indexInfo.isReadonly + } + return false + }) +} + func (c *Checker) getIndexedMappedTypeSubstitutedTypeOfContextualType(t *Type, name string, nameType *Type) *Type { propertyNameType := nameType if propertyNameType == nil { diff --git a/tsc/internal/checker/relater.go b/tsc/internal/checker/relater.go index 9008420fb7cd1..ae0a9283bf226 100644 --- a/tsc/internal/checker/relater.go +++ b/tsc/internal/checker/relater.go @@ -3455,7 +3455,8 @@ func (r *Relater) structuredTypeRelatedToWorker(source *Type, target *Type, repo case target.flags&TypeFlagsTypeParameter != 0: // A source type { [P in Q]: X } is related to a target type T if keyof T is related to Q and X is related to T[Q]. if source.objectFlags&ObjectFlagsMapped != 0 && source.AsMappedType().declaration.NameType == nil && r.isRelatedTo(r.c.getIndexType(target), r.c.getConstraintTypeFromMappedType(source), RecursionFlagsBoth, false) != TernaryFalse { - if getMappedTypeModifiers(source)&MappedTypeModifiersIncludeOptional == 0 { + if getMappedTypeModifiers(source)&MappedTypeModifiersIncludeOptional == 0 && + !(r.c.enforceReadonly && r.relation != r.c.comparableRelation && getMappedTypeModifiers(source)&MappedTypeModifiersIncludeReadonly != 0) { templateType := r.c.getTemplateTypeFromMappedType(source) indexedAccessType := r.c.getIndexedAccessType(target, r.c.getTypeParameterFromMappedType(source)) result = r.isRelatedTo(templateType, indexedAccessType, RecursionFlagsBoth, reportErrors) @@ -3627,7 +3628,8 @@ func (r *Relater) structuredTypeRelatedToWorker(source *Type, target *Type, repo keysRemapped := target.AsMappedType().declaration.NameType != nil templateType := r.c.getTemplateTypeFromMappedType(target) modifiers := getMappedTypeModifiers(target) - if modifiers&MappedTypeModifiersExcludeOptional == 0 { + if modifiers&MappedTypeModifiersExcludeOptional == 0 && + !(r.c.enforceReadonly && r.relation != r.c.comparableRelation && modifiers&MappedTypeModifiersExcludeReadonly != 0) { // 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&TypeFlagsIndexedAccess != 0 && templateType.AsIndexedAccessType().objectType == source && templateType.AsIndexedAccessType().indexType == r.c.getTypeParameterFromMappedType(target) { @@ -4004,10 +4006,12 @@ func (r *Relater) typeArgumentsRelatedTo(sources []*Type, targets []*Type, varia func (r *Relater) mappedTypeRelatedTo(source *Type, target *Type, reportErrors bool) Ternary { modifiersRelated := r.relation == r.c.comparableRelation || r.relation == r.c.identityRelation && getMappedTypeModifiers(source) == getMappedTypeModifiers(target) || - r.relation != r.c.identityRelation && r.c.getCombinedMappedTypeOptionality(source) <= r.c.getCombinedMappedTypeOptionality(target) + r.relation != r.c.identityRelation && + r.c.getMappedTypeModifiersRank(source, MappedTypeModifiersIncludeOptional|MappedTypeModifiersExcludeOptional) <= r.c.getMappedTypeModifiersRank(target, MappedTypeModifiersIncludeOptional|MappedTypeModifiersExcludeOptional) && + (!r.c.enforceReadonly || r.c.getMappedTypeModifiersRank(source, MappedTypeModifiersIncludeReadonly|MappedTypeModifiersExcludeReadonly) <= r.c.getMappedTypeModifiersRank(target, MappedTypeModifiersIncludeReadonly|MappedTypeModifiersExcludeReadonly)) if modifiersRelated { targetConstraint := r.c.getConstraintTypeFromMappedType(target) - sourceConstraint := r.c.instantiateType(r.c.getConstraintTypeFromMappedType(source), core.IfElse(r.c.getCombinedMappedTypeOptionality(source) < 0, r.c.reportUnmeasurableMapper, r.c.reportUnreliableMapper)) + sourceConstraint := r.c.instantiateType(r.c.getConstraintTypeFromMappedType(source), core.IfElse(r.c.getEffectiveMappedTypeModifiers(source, MappedTypeModifiersIncludeOptional|MappedTypeModifiersExcludeOptional)&MappedTypeModifiersExcludeOptional != 0, r.c.reportUnmeasurableMapper, r.c.reportUnreliableMapper)) if result := r.isRelatedTo(targetConstraint, sourceConstraint, RecursionFlagsBoth, reportErrors); result != TernaryFalse { mapper := newSimpleTypeMapper(r.c.getTypeParameterFromMappedType(source), r.c.getTypeParameterFromMappedType(target)) if r.c.instantiateType(r.c.getNameTypeFromMappedType(source), mapper) == r.c.instantiateType(r.c.getNameTypeFromMappedType(target), mapper) { @@ -4335,7 +4339,11 @@ func (r *Relater) propertyRelatedTo(source *Type, target *Type, sourceProp *ast. // from deciding which type "wins" in union subtype reduction. // They're still assignable to one another, since `readonly` doesn't affect assignability. // This is only applied during the strictSubtypeRelation -- currently used in subtype reduction - if r.relation == r.c.strictSubtypeRelation && r.c.isReadonlySymbol(sourceProp) && !r.c.isReadonlySymbol(targetProp) { + if (r.relation == r.c.strictSubtypeRelation || r.c.enforceReadonly && r.relation != r.c.comparableRelation) && + r.c.isReadonlySymbol(sourceProp) && !r.c.isReadonlySymbol(targetProp) && targetProp.Flags&ast.SymbolFlagsMethod == 0 { + if reportErrors { + r.reportError(diagnostics.Property_0_is_readonly_in_the_source_but_not_in_the_target, r.c.symbolToString(targetProp)) + } return TernaryFalse } // If the target comes from a partial union prop, allow `undefined` in the target type @@ -4709,12 +4717,21 @@ func (r *Relater) membersRelatedToIndexInfo(source *Type, targetInfo *IndexInfo, func (r *Relater) indexInfoRelatedTo(sourceInfo *IndexInfo, targetInfo *IndexInfo, reportErrors bool, intersectionState IntersectionState) Ternary { related := r.isRelatedToEx(sourceInfo.valueType, targetInfo.valueType, RecursionFlagsBoth, reportErrors, nil /*headMessage*/, intersectionState) - if related == TernaryFalse && reportErrors { - if sourceInfo.keyType == targetInfo.keyType { - r.reportError(diagnostics.X_0_index_signatures_are_incompatible, r.c.TypeToString(sourceInfo.keyType)) - } else { - r.reportError(diagnostics.X_0_and_1_index_signatures_are_incompatible, r.c.TypeToString(sourceInfo.keyType), r.c.TypeToString(targetInfo.keyType)) + if related == TernaryFalse { + if reportErrors { + if sourceInfo.keyType == targetInfo.keyType { + r.reportError(diagnostics.X_0_index_signatures_are_incompatible, r.c.TypeToString(sourceInfo.keyType)) + } else { + r.reportError(diagnostics.X_0_and_1_index_signatures_are_incompatible, r.c.TypeToString(sourceInfo.keyType), r.c.TypeToString(targetInfo.keyType)) + } + } + return TernaryFalse + } + if r.c.enforceReadonly && r.relation != r.c.comparableRelation && sourceInfo.isReadonly && !targetInfo.isReadonly { + if reportErrors { + r.reportError(diagnostics.X_0_index_signature_is_readonly_in_the_source_but_not_in_the_target, r.c.TypeToString(sourceInfo.keyType)) } + return TernaryFalse } return related } diff --git a/tsc/internal/core/compileroptions.go b/tsc/internal/core/compileroptions.go index 55401a499f3a8..61510b6fa6e0d 100644 --- a/tsc/internal/core/compileroptions.go +++ b/tsc/internal/core/compileroptions.go @@ -40,6 +40,7 @@ type CompilerOptions struct { DisableSolutionSearching Tristate `json:"disableSolutionSearching,omitzero"` DisableReferencedProjectLoad Tristate `json:"disableReferencedProjectLoad,omitzero"` ErasableSyntaxOnly Tristate `json:"erasableSyntaxOnly,omitzero"` + EnforceReadonly Tristate `json:"enforceReadonly,omitzero"` ExactOptionalPropertyTypes Tristate `json:"exactOptionalPropertyTypes,omitzero"` ExperimentalDecorators Tristate `json:"experimentalDecorators,omitzero"` ForceConsistentCasingInFileNames Tristate `json:"forceConsistentCasingInFileNames,omitzero"` diff --git a/tsc/internal/diagnostics/diagnosticMessages.json b/tsc/internal/diagnostics/diagnosticMessages.json index 17586e927d0ac..aad56bbe6ac2f 100644 --- a/tsc/internal/diagnostics/diagnosticMessages.json +++ b/tsc/internal/diagnostics/diagnosticMessages.json @@ -4463,6 +4463,14 @@ "category": "Error", "code": 4126 }, + "Property '{0}' is 'readonly' in the source but not in the target.": { + "category": "Error", + "code": 4129 + }, + "'{0}' index signature is 'readonly' in the source but not in the target.": { + "category": "Error", + "code": 4130 + }, "This member cannot have an 'override' modifier because its name is dynamic.": { "category": "Error", "code": 4127 @@ -6565,6 +6573,10 @@ "category": "Message", "code": 6720 }, + "Ensure that 'readonly' properties remain read-only in type relationships.": { + "category": "Message", + "code": 6722 + }, "Do not allow runtime constructs that are not part of ECMAScript.": { "category": "Message", "code": 6721 diff --git a/tsc/internal/diagnostics/diagnostics_generated.go b/tsc/internal/diagnostics/diagnostics_generated.go index 96641c4d85818..8eabe6b442155 100644 --- a/tsc/internal/diagnostics/diagnostics_generated.go +++ b/tsc/internal/diagnostics/diagnostics_generated.go @@ -2232,6 +2232,10 @@ var This_member_cannot_have_an_override_modifier_because_its_name_is_dynamic = & var This_member_cannot_have_a_JSDoc_comment_with_an_override_tag_because_its_name_is_dynamic = &Message{code: 4128, category: CategoryError, key: "This_member_cannot_have_a_JSDoc_comment_with_an_override_tag_because_its_name_is_dynamic_4128", text: "This member cannot have a JSDoc comment with an '@override' tag because its name is dynamic."} +var Property_0_is_readonly_in_the_source_but_not_in_the_target = &Message{code: 4129, category: CategoryError, key: "Property_0_is_readonly_in_the_source_but_not_in_the_target_4129", text: "Property '{0}' is 'readonly' in the source but not in the target."} + +var X_0_index_signature_is_readonly_in_the_source_but_not_in_the_target = &Message{code: 4130, category: CategoryError, key: "_0_index_signature_is_readonly_in_the_source_but_not_in_the_target_4130", text: "'{0}' index signature is 'readonly' in the source but not in the target."} + var The_current_host_does_not_support_the_0_option = &Message{code: 5001, category: CategoryError, key: "The_current_host_does_not_support_the_0_option_5001", text: "The current host does not support the '{0}' option."} var Option_0_requires_value_to_be_greater_than_1 = &Message{code: 5002, category: CategoryError, key: "Option_0_requires_value_to_be_greater_than_1_5002", text: "Option '{0}' requires value to be greater than '{1}'."} @@ -3282,6 +3286,8 @@ var Built_in_iterators_are_instantiated_with_a_TReturn_type_of_undefined_instead var Do_not_allow_runtime_constructs_that_are_not_part_of_ECMAScript = &Message{code: 6721, category: CategoryMessage, key: "Do_not_allow_runtime_constructs_that_are_not_part_of_ECMAScript_6721", text: "Do not allow runtime constructs that are not part of ECMAScript."} +var Ensure_that_readonly_properties_remain_read_only_in_type_relationships = &Message{code: 6722, category: CategoryMessage, key: "Ensure_that_readonly_properties_remain_read_only_in_type_relationships_6722", text: "Ensure that 'readonly' properties remain read-only in type relationships."} + var Default_catch_clause_variables_as_unknown_instead_of_any = &Message{code: 6803, category: CategoryMessage, key: "Default_catch_clause_variables_as_unknown_instead_of_any_6803", text: "Default catch clause variables as 'unknown' instead of 'any'."} var Do_not_transform_or_elide_any_imports_or_exports_not_marked_as_type_only_ensuring_they_are_written_in_the_output_file_s_format_based_on_the_module_setting = &Message{code: 6804, category: CategoryMessage, key: "Do_not_transform_or_elide_any_imports_or_exports_not_marked_as_type_only_ensuring_they_are_written_i_6804", text: "Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting."} @@ -6642,6 +6648,10 @@ func keyToMessage(key Key) *Message { return This_member_cannot_have_an_override_modifier_because_its_name_is_dynamic case "This_member_cannot_have_a_JSDoc_comment_with_an_override_tag_because_its_name_is_dynamic_4128": return This_member_cannot_have_a_JSDoc_comment_with_an_override_tag_because_its_name_is_dynamic + case "Property_0_is_readonly_in_the_source_but_not_in_the_target_4129": + return Property_0_is_readonly_in_the_source_but_not_in_the_target + case "_0_index_signature_is_readonly_in_the_source_but_not_in_the_target_4130": + return X_0_index_signature_is_readonly_in_the_source_but_not_in_the_target case "The_current_host_does_not_support_the_0_option_5001": return The_current_host_does_not_support_the_0_option case "Option_0_requires_value_to_be_greater_than_1_5002": @@ -7692,6 +7702,8 @@ func keyToMessage(key Key) *Message { return Built_in_iterators_are_instantiated_with_a_TReturn_type_of_undefined_instead_of_any case "Do_not_allow_runtime_constructs_that_are_not_part_of_ECMAScript_6721": return Do_not_allow_runtime_constructs_that_are_not_part_of_ECMAScript + case "Ensure_that_readonly_properties_remain_read_only_in_type_relationships_6722": + return Ensure_that_readonly_properties_remain_read_only_in_type_relationships case "Default_catch_clause_variables_as_unknown_instead_of_any_6803": return Default_catch_clause_variables_as_unknown_instead_of_any case "Do_not_transform_or_elide_any_imports_or_exports_not_marked_as_type_only_ensuring_they_are_written_i_6804": diff --git a/tsc/internal/execute/tsc/init.go b/tsc/internal/execute/tsc/init.go index 58fb6a1a208d7..f62b616813147 100644 --- a/tsc/internal/execute/tsc/init.go +++ b/tsc/internal/execute/tsc/init.go @@ -176,6 +176,7 @@ func generateTSConfig(options *collections.OrderedMap[string, any], locale local emitHeader(diagnostics.Stricter_Typechecking_Options) emitOption("noUncheckedIndexedAccess" /*defaultValue*/, true, commentedNever) + emitOption("enforceReadonly" /*defaultValue*/, true, commentedNever) emitOption("exactOptionalPropertyTypes" /*defaultValue*/, true, commentedNever) newline() diff --git a/tsc/internal/tsoptions/declscompiler.go b/tsc/internal/tsoptions/declscompiler.go index 46fb42faee7c5..e4123de32ddb9 100644 --- a/tsc/internal/tsoptions/declscompiler.go +++ b/tsc/internal/tsoptions/declscompiler.go @@ -668,6 +668,15 @@ var optionsForCompiler = []*CommandLineOption{ Description: diagnostics.Raise_an_error_when_a_function_parameter_isn_t_read, DefaultValueDescription: false, }, + { + Name: "enforceReadonly", + Kind: CommandLineOptionTypeBoolean, + AffectsSemanticDiagnostics: true, + AffectsBuildInfo: true, + Category: diagnostics.Type_Checking, + Description: diagnostics.Ensure_that_readonly_properties_remain_read_only_in_type_relationships, + DefaultValueDescription: false, + }, { Name: "exactOptionalPropertyTypes", Kind: CommandLineOptionTypeBoolean, diff --git a/tsc/internal/tsoptions/parsinghelpers.go b/tsc/internal/tsoptions/parsinghelpers.go index fa7491b1e0c06..11e1f4e01f5b5 100644 --- a/tsc/internal/tsoptions/parsinghelpers.go +++ b/tsc/internal/tsoptions/parsinghelpers.go @@ -344,6 +344,8 @@ func parseCompilerOptions(key string, value any, allOptions *core.CompilerOption allOptions.EmitBOM = ParseTristate(value) case "esModuleInterop": allOptions.ESModuleInterop = ParseTristate(value) + case "enforceReadonly": + allOptions.EnforceReadonly = ParseTristate(value) case "exactOptionalPropertyTypes": allOptions.ExactOptionalPropertyTypes = ParseTristate(value) case "explainFiles": diff --git a/tsc/testdata/baselines/reference/compiler/enforceReadonly1.errors.txt b/tsc/testdata/baselines/reference/compiler/enforceReadonly1.errors.txt new file mode 100644 index 0000000000000..4fe8786273069 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/enforceReadonly1.errors.txt @@ -0,0 +1,142 @@ +enforceReadonly1.ts(2,5): error TS2322: Type '{ readonly x: string; }' is not assignable to type '{ x: string; }'. + Property 'x' is 'readonly' in the source but not in the target. +enforceReadonly1.ts(6,5): error TS2322: Type '{ readonly [x: string]: string; }' is not assignable to type '{ [x: string]: string; }'. + 'string' index signature is 'readonly' in the source but not in the target. +enforceReadonly1.ts(15,5): error TS2322: Type 'T' is not assignable to type 'Mutable'. +enforceReadonly1.ts(16,5): error TS2322: Type 'Readonly' is not assignable to type 'Mutable'. +enforceReadonly1.ts(18,5): error TS2322: Type 'Readonly' is not assignable to type 'T'. + 'T' could be instantiated with an arbitrary type which could be unrelated to 'Readonly'. +enforceReadonly1.ts(25,5): error TS2322: Type '{ readonly foo: () => void; }' is not assignable to type '{ foo: () => void; }'. + Property 'foo' is 'readonly' in the source but not in the target. +enforceReadonly1.ts(32,11): error TS2430: Interface 'D1' incorrectly extends interface 'B1'. + Property 'x' is 'readonly' in the source but not in the target. +enforceReadonly1.ts(41,11): error TS2430: Interface 'D2' incorrectly extends interface 'B2'. + Property 'x' is 'readonly' in the source but not in the target. +enforceReadonly1.ts(49,7): error TS2415: Class 'D3' incorrectly extends base class 'B3'. + Property 'x' is 'readonly' in the source but not in the target. +enforceReadonly1.ts(85,3): error TS2540: Cannot assign to 'value' because it is a read-only property. +enforceReadonly1.ts(87,5): error TS2322: Type 'ImmutableValue' is not assignable to type 'MutableValue'. + Property 'value' is 'readonly' in the source but not in the target. + + +==== enforceReadonly1.ts (11 errors) ==== + function f1(mp: { x: string }, rp: { readonly x: string }, mx: { [x: string]: string }, rx: { readonly [x: string]: string }) { + mp = rp; // Error + ~~ +!!! error TS2322: Type '{ readonly x: string; }' is not assignable to type '{ x: string; }'. +!!! error TS2322: Property 'x' is 'readonly' in the source but not in the target. + rp = mp; + mx = mp; + mx = rp; + mx = rx; // Error + ~~ +!!! error TS2322: Type '{ readonly [x: string]: string; }' is not assignable to type '{ [x: string]: string; }'. +!!! error TS2322: 'string' index signature is 'readonly' in the source but not in the target. + rx = mp; + rx = rp; + rx = mx; + } + + type Mutable = { -readonly [P in keyof T]: T[P] }; + + function f2(mt: Mutable, tt: T, rt: Readonly) { + mt = tt; // Error + ~~ +!!! error TS2322: Type 'T' is not assignable to type 'Mutable'. +!!! related TS2208 enforceReadonly1.ts:14:13: This type parameter might need an `extends Mutable` constraint. + mt = rt; // Error + ~~ +!!! error TS2322: Type 'Readonly' is not assignable to type 'Mutable'. + tt = mt; + tt = rt; // Error + ~~ +!!! error TS2322: Type 'Readonly' is not assignable to type 'T'. +!!! error TS2322: 'T' could be instantiated with an arbitrary type which could be unrelated to 'Readonly'. + rt = mt; + rt = tt; + } + + function f3(m: { foo(): void }, p: { foo: () => void }, r: { readonly foo: () => void }) { + m = r; + p = r; // Error + ~ +!!! error TS2322: Type '{ readonly foo: () => void; }' is not assignable to type '{ foo: () => void; }'. +!!! error TS2322: Property 'foo' is 'readonly' in the source but not in the target. + } + + interface B1 { + x: number; + } + + interface D1 extends B1 { // Error + ~~ +!!! error TS2430: Interface 'D1' incorrectly extends interface 'B1'. +!!! error TS2430: Property 'x' is 'readonly' in the source but not in the target. + readonly x: number; + } + + interface B2 { + get x(): number; + set x(value: number); + } + + interface D2 extends B2 { // Error + ~~ +!!! error TS2430: Interface 'D2' incorrectly extends interface 'B2'. +!!! error TS2430: Property 'x' is 'readonly' in the source but not in the target. + get x(): number; + } + + class B3 { + x = 0; + } + + class D3 extends B3 { // Error + ~~ +!!! error TS2415: Class 'D3' incorrectly extends base class 'B3'. +!!! error TS2415: Property 'x' is 'readonly' in the source but not in the target. + readonly x = 1; + } + + type Foo = { + readonly a: string | undefined; + readonly b: number | undefined; + }; + + type Bar = { + a: string; + }; + + function f5(foo: Foo, bar: Bar) { + return foo === bar; + } + + const y1: { a: string; b: number } = { a: "hello", b: 42 } as const; + const y2: { a: string; readonly b: number } = { a: "hello", b: 42 } as const; + const y3: Record = { a: 1, b: 2 } as const; + + declare function f10(obj: T): T; + declare function f11(obj: T): T; + + f10({ a: "hello", b: 42 }); + f11({ a: "hello", b: 42 }); + + interface MutableValue { + value: T; + } + + interface ImmutableValue { + readonly value: T; + } + + let i: ImmutableValue = { value: "hi" }; + i.value = "Excellent, I can't change it"; // Error + ~~~~~ +!!! error TS2540: Cannot assign to 'value' because it is a read-only property. + + let m: MutableValue = i; // Error + ~ +!!! error TS2322: Type 'ImmutableValue' is not assignable to type 'MutableValue'. +!!! error TS2322: Property 'value' is 'readonly' in the source but not in the target. + m.value = "Oh dear, I can change it"; + \ No newline at end of file diff --git a/tsc/testdata/baselines/reference/compiler/enforceReadonly1.symbols b/tsc/testdata/baselines/reference/compiler/enforceReadonly1.symbols new file mode 100644 index 0000000000000..2bf7703902f93 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/enforceReadonly1.symbols @@ -0,0 +1,279 @@ +//// [tests/cases/compiler/enforceReadonly1.ts] //// + +=== enforceReadonly1.ts === +function f1(mp: { x: string }, rp: { readonly x: string }, mx: { [x: string]: string }, rx: { readonly [x: string]: string }) { +>f1 : Symbol(f1, Decl(enforceReadonly1.ts, 0, 0)) +>mp : Symbol(mp, Decl(enforceReadonly1.ts, 0, 12)) +>x : Symbol(x, Decl(enforceReadonly1.ts, 0, 17)) +>rp : Symbol(rp, Decl(enforceReadonly1.ts, 0, 30)) +>x : Symbol(x, Decl(enforceReadonly1.ts, 0, 36)) +>mx : Symbol(mx, Decl(enforceReadonly1.ts, 0, 58)) +>x : Symbol(x, Decl(enforceReadonly1.ts, 0, 66)) +>rx : Symbol(rx, Decl(enforceReadonly1.ts, 0, 87)) +>x : Symbol(x, Decl(enforceReadonly1.ts, 0, 104)) + + mp = rp; // Error +>mp : Symbol(mp, Decl(enforceReadonly1.ts, 0, 12)) +>rp : Symbol(rp, Decl(enforceReadonly1.ts, 0, 30)) + + rp = mp; +>rp : Symbol(rp, Decl(enforceReadonly1.ts, 0, 30)) +>mp : Symbol(mp, Decl(enforceReadonly1.ts, 0, 12)) + + mx = mp; +>mx : Symbol(mx, Decl(enforceReadonly1.ts, 0, 58)) +>mp : Symbol(mp, Decl(enforceReadonly1.ts, 0, 12)) + + mx = rp; +>mx : Symbol(mx, Decl(enforceReadonly1.ts, 0, 58)) +>rp : Symbol(rp, Decl(enforceReadonly1.ts, 0, 30)) + + mx = rx; // Error +>mx : Symbol(mx, Decl(enforceReadonly1.ts, 0, 58)) +>rx : Symbol(rx, Decl(enforceReadonly1.ts, 0, 87)) + + rx = mp; +>rx : Symbol(rx, Decl(enforceReadonly1.ts, 0, 87)) +>mp : Symbol(mp, Decl(enforceReadonly1.ts, 0, 12)) + + rx = rp; +>rx : Symbol(rx, Decl(enforceReadonly1.ts, 0, 87)) +>rp : Symbol(rp, Decl(enforceReadonly1.ts, 0, 30)) + + rx = mx; +>rx : Symbol(rx, Decl(enforceReadonly1.ts, 0, 87)) +>mx : Symbol(mx, Decl(enforceReadonly1.ts, 0, 58)) +} + +type Mutable = { -readonly [P in keyof T]: T[P] }; +>Mutable : Symbol(Mutable, Decl(enforceReadonly1.ts, 9, 1)) +>T : Symbol(T, Decl(enforceReadonly1.ts, 11, 13)) +>P : Symbol(P, Decl(enforceReadonly1.ts, 11, 31)) +>T : Symbol(T, Decl(enforceReadonly1.ts, 11, 13)) +>T : Symbol(T, Decl(enforceReadonly1.ts, 11, 13)) +>P : Symbol(P, Decl(enforceReadonly1.ts, 11, 31)) + +function f2(mt: Mutable, tt: T, rt: Readonly) { +>f2 : Symbol(f2, Decl(enforceReadonly1.ts, 11, 53)) +>T : Symbol(T, Decl(enforceReadonly1.ts, 13, 12)) +>mt : Symbol(mt, Decl(enforceReadonly1.ts, 13, 15)) +>Mutable : Symbol(Mutable, Decl(enforceReadonly1.ts, 9, 1)) +>T : Symbol(T, Decl(enforceReadonly1.ts, 13, 12)) +>tt : Symbol(tt, Decl(enforceReadonly1.ts, 13, 30)) +>T : Symbol(T, Decl(enforceReadonly1.ts, 13, 12)) +>rt : Symbol(rt, Decl(enforceReadonly1.ts, 13, 37)) +>Readonly : Symbol(Readonly, Decl(lib.es5.d.ts, --, --)) +>T : Symbol(T, Decl(enforceReadonly1.ts, 13, 12)) + + mt = tt; // Error +>mt : Symbol(mt, Decl(enforceReadonly1.ts, 13, 15)) +>tt : Symbol(tt, Decl(enforceReadonly1.ts, 13, 30)) + + mt = rt; // Error +>mt : Symbol(mt, Decl(enforceReadonly1.ts, 13, 15)) +>rt : Symbol(rt, Decl(enforceReadonly1.ts, 13, 37)) + + tt = mt; +>tt : Symbol(tt, Decl(enforceReadonly1.ts, 13, 30)) +>mt : Symbol(mt, Decl(enforceReadonly1.ts, 13, 15)) + + tt = rt; // Error +>tt : Symbol(tt, Decl(enforceReadonly1.ts, 13, 30)) +>rt : Symbol(rt, Decl(enforceReadonly1.ts, 13, 37)) + + rt = mt; +>rt : Symbol(rt, Decl(enforceReadonly1.ts, 13, 37)) +>mt : Symbol(mt, Decl(enforceReadonly1.ts, 13, 15)) + + rt = tt; +>rt : Symbol(rt, Decl(enforceReadonly1.ts, 13, 37)) +>tt : Symbol(tt, Decl(enforceReadonly1.ts, 13, 30)) +} + +function f3(m: { foo(): void }, p: { foo: () => void }, r: { readonly foo: () => void }) { +>f3 : Symbol(f3, Decl(enforceReadonly1.ts, 20, 1)) +>m : Symbol(m, Decl(enforceReadonly1.ts, 22, 12)) +>foo : Symbol(foo, Decl(enforceReadonly1.ts, 22, 16)) +>p : Symbol(p, Decl(enforceReadonly1.ts, 22, 31)) +>foo : Symbol(foo, Decl(enforceReadonly1.ts, 22, 36)) +>r : Symbol(r, Decl(enforceReadonly1.ts, 22, 55)) +>foo : Symbol(foo, Decl(enforceReadonly1.ts, 22, 60)) + + m = r; +>m : Symbol(m, Decl(enforceReadonly1.ts, 22, 12)) +>r : Symbol(r, Decl(enforceReadonly1.ts, 22, 55)) + + p = r; // Error +>p : Symbol(p, Decl(enforceReadonly1.ts, 22, 31)) +>r : Symbol(r, Decl(enforceReadonly1.ts, 22, 55)) +} + +interface B1 { +>B1 : Symbol(B1, Decl(enforceReadonly1.ts, 25, 1)) + + x: number; +>x : Symbol(B1.x, Decl(enforceReadonly1.ts, 27, 14)) +} + +interface D1 extends B1 { // Error +>D1 : Symbol(D1, Decl(enforceReadonly1.ts, 29, 1)) +>B1 : Symbol(B1, Decl(enforceReadonly1.ts, 25, 1)) + + readonly x: number; +>x : Symbol(D1.x, Decl(enforceReadonly1.ts, 31, 25)) +} + +interface B2 { +>B2 : Symbol(B2, Decl(enforceReadonly1.ts, 33, 1)) + + get x(): number; +>x : Symbol(B2.x, Decl(enforceReadonly1.ts, 35, 14), Decl(enforceReadonly1.ts, 36, 20)) + + set x(value: number); +>x : Symbol(B2.x, Decl(enforceReadonly1.ts, 35, 14), Decl(enforceReadonly1.ts, 36, 20)) +>value : Symbol(value, Decl(enforceReadonly1.ts, 37, 10)) +} + +interface D2 extends B2 { // Error +>D2 : Symbol(D2, Decl(enforceReadonly1.ts, 38, 1)) +>B2 : Symbol(B2, Decl(enforceReadonly1.ts, 33, 1)) + + get x(): number; +>x : Symbol(D2.x, Decl(enforceReadonly1.ts, 40, 25)) +} + +class B3 { +>B3 : Symbol(B3, Decl(enforceReadonly1.ts, 42, 1)) + + x = 0; +>x : Symbol(B3.x, Decl(enforceReadonly1.ts, 44, 10)) +} + +class D3 extends B3 { // Error +>D3 : Symbol(D3, Decl(enforceReadonly1.ts, 46, 1)) +>B3 : Symbol(B3, Decl(enforceReadonly1.ts, 42, 1)) + + readonly x = 1; +>x : Symbol(D3.x, Decl(enforceReadonly1.ts, 48, 21)) +} + +type Foo = { +>Foo : Symbol(Foo, Decl(enforceReadonly1.ts, 50, 1)) + + readonly a: string | undefined; +>a : Symbol(a, Decl(enforceReadonly1.ts, 52, 12)) + + readonly b: number | undefined; +>b : Symbol(b, Decl(enforceReadonly1.ts, 53, 35)) + +}; + +type Bar = { +>Bar : Symbol(Bar, Decl(enforceReadonly1.ts, 55, 2)) + + a: string; +>a : Symbol(a, Decl(enforceReadonly1.ts, 57, 12)) + +}; + +function f5(foo: Foo, bar: Bar) { +>f5 : Symbol(f5, Decl(enforceReadonly1.ts, 59, 2)) +>foo : Symbol(foo, Decl(enforceReadonly1.ts, 61, 12)) +>Foo : Symbol(Foo, Decl(enforceReadonly1.ts, 50, 1)) +>bar : Symbol(bar, Decl(enforceReadonly1.ts, 61, 21)) +>Bar : Symbol(Bar, Decl(enforceReadonly1.ts, 55, 2)) + + return foo === bar; +>foo : Symbol(foo, Decl(enforceReadonly1.ts, 61, 12)) +>bar : Symbol(bar, Decl(enforceReadonly1.ts, 61, 21)) +} + +const y1: { a: string; b: number } = { a: "hello", b: 42 } as const; +>y1 : Symbol(y1, Decl(enforceReadonly1.ts, 65, 5)) +>a : Symbol(a, Decl(enforceReadonly1.ts, 65, 11)) +>b : Symbol(b, Decl(enforceReadonly1.ts, 65, 22)) +>a : Symbol(a, Decl(enforceReadonly1.ts, 65, 38)) +>b : Symbol(b, Decl(enforceReadonly1.ts, 65, 50)) +>const : Symbol(const) + +const y2: { a: string; readonly b: number } = { a: "hello", b: 42 } as const; +>y2 : Symbol(y2, Decl(enforceReadonly1.ts, 66, 5)) +>a : Symbol(a, Decl(enforceReadonly1.ts, 66, 11)) +>b : Symbol(b, Decl(enforceReadonly1.ts, 66, 22)) +>a : Symbol(a, Decl(enforceReadonly1.ts, 66, 47)) +>b : Symbol(b, Decl(enforceReadonly1.ts, 66, 59)) +>const : Symbol(const) + +const y3: Record = { a: 1, b: 2 } as const; +>y3 : Symbol(y3, Decl(enforceReadonly1.ts, 67, 5)) +>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --)) +>a : Symbol(a, Decl(enforceReadonly1.ts, 67, 37)) +>b : Symbol(b, Decl(enforceReadonly1.ts, 67, 43)) +>const : Symbol(const) + +declare function f10(obj: T): T; +>f10 : Symbol(f10, Decl(enforceReadonly1.ts, 67, 60)) +>T : Symbol(T, Decl(enforceReadonly1.ts, 69, 21)) +>a : Symbol(a, Decl(enforceReadonly1.ts, 69, 38)) +>b : Symbol(b, Decl(enforceReadonly1.ts, 69, 49)) +>obj : Symbol(obj, Decl(enforceReadonly1.ts, 69, 63)) +>T : Symbol(T, Decl(enforceReadonly1.ts, 69, 21)) +>T : Symbol(T, Decl(enforceReadonly1.ts, 69, 21)) + +declare function f11(obj: T): T; +>f11 : Symbol(f11, Decl(enforceReadonly1.ts, 69, 74)) +>T : Symbol(T, Decl(enforceReadonly1.ts, 70, 21)) +>a : Symbol(a, Decl(enforceReadonly1.ts, 70, 38)) +>b : Symbol(b, Decl(enforceReadonly1.ts, 70, 49)) +>obj : Symbol(obj, Decl(enforceReadonly1.ts, 70, 72)) +>T : Symbol(T, Decl(enforceReadonly1.ts, 70, 21)) +>T : Symbol(T, Decl(enforceReadonly1.ts, 70, 21)) + +f10({ a: "hello", b: 42 }); +>f10 : Symbol(f10, Decl(enforceReadonly1.ts, 67, 60)) +>a : Symbol(a, Decl(enforceReadonly1.ts, 72, 5)) +>b : Symbol(b, Decl(enforceReadonly1.ts, 72, 17)) + +f11({ a: "hello", b: 42 }); +>f11 : Symbol(f11, Decl(enforceReadonly1.ts, 69, 74)) +>a : Symbol(a, Decl(enforceReadonly1.ts, 73, 5)) +>b : Symbol(b, Decl(enforceReadonly1.ts, 73, 17)) + +interface MutableValue { +>MutableValue : Symbol(MutableValue, Decl(enforceReadonly1.ts, 73, 27)) +>T : Symbol(T, Decl(enforceReadonly1.ts, 75, 23)) + + value: T; +>value : Symbol(MutableValue.value, Decl(enforceReadonly1.ts, 75, 27)) +>T : Symbol(T, Decl(enforceReadonly1.ts, 75, 23)) +} + +interface ImmutableValue { +>ImmutableValue : Symbol(ImmutableValue, Decl(enforceReadonly1.ts, 77, 1)) +>T : Symbol(T, Decl(enforceReadonly1.ts, 79, 25)) + + readonly value: T; +>value : Symbol(ImmutableValue.value, Decl(enforceReadonly1.ts, 79, 29)) +>T : Symbol(T, Decl(enforceReadonly1.ts, 79, 25)) +} + +let i: ImmutableValue = { value: "hi" }; +>i : Symbol(i, Decl(enforceReadonly1.ts, 83, 3)) +>ImmutableValue : Symbol(ImmutableValue, Decl(enforceReadonly1.ts, 77, 1)) +>value : Symbol(value, Decl(enforceReadonly1.ts, 83, 33)) + +i.value = "Excellent, I can't change it"; // Error +>i.value : Symbol(ImmutableValue.value, Decl(enforceReadonly1.ts, 79, 29)) +>i : Symbol(i, Decl(enforceReadonly1.ts, 83, 3)) +>value : Symbol(ImmutableValue.value, Decl(enforceReadonly1.ts, 79, 29)) + +let m: MutableValue = i; // Error +>m : Symbol(m, Decl(enforceReadonly1.ts, 86, 3)) +>MutableValue : Symbol(MutableValue, Decl(enforceReadonly1.ts, 73, 27)) +>i : Symbol(i, Decl(enforceReadonly1.ts, 83, 3)) + +m.value = "Oh dear, I can change it"; +>m.value : Symbol(MutableValue.value, Decl(enforceReadonly1.ts, 75, 27)) +>m : Symbol(m, Decl(enforceReadonly1.ts, 86, 3)) +>value : Symbol(MutableValue.value, Decl(enforceReadonly1.ts, 75, 27)) + diff --git a/tsc/testdata/baselines/reference/compiler/enforceReadonly1.types b/tsc/testdata/baselines/reference/compiler/enforceReadonly1.types new file mode 100644 index 0000000000000..6a67e6fd98622 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/enforceReadonly1.types @@ -0,0 +1,281 @@ +//// [tests/cases/compiler/enforceReadonly1.ts] //// + +=== enforceReadonly1.ts === +function f1(mp: { x: string }, rp: { readonly x: string }, mx: { [x: string]: string }, rx: { readonly [x: string]: string }) { +>f1 : (mp: { x: string; }, rp: { readonly x: string; }, mx: { [x: string]: string; }, rx: { readonly [x: string]: string; }) => void +>mp : { x: string; } +>x : string +>rp : { readonly x: string; } +>x : string +>mx : { [x: string]: string; } +>x : string +>rx : { readonly [x: string]: string; } +>x : string + + mp = rp; // Error +>mp = rp : { readonly x: string; } +>mp : { x: string; } +>rp : { readonly x: string; } + + rp = mp; +>rp = mp : { x: string; } +>rp : { readonly x: string; } +>mp : { x: string; } + + mx = mp; +>mx = mp : { x: string; } +>mx : { [x: string]: string; } +>mp : { x: string; } + + mx = rp; +>mx = rp : { readonly x: string; } +>mx : { [x: string]: string; } +>rp : { readonly x: string; } + + mx = rx; // Error +>mx = rx : { readonly [x: string]: string; } +>mx : { [x: string]: string; } +>rx : { readonly [x: string]: string; } + + rx = mp; +>rx = mp : { x: string; } +>rx : { readonly [x: string]: string; } +>mp : { x: string; } + + rx = rp; +>rx = rp : { readonly x: string; } +>rx : { readonly [x: string]: string; } +>rp : { readonly x: string; } + + rx = mx; +>rx = mx : { [x: string]: string; } +>rx : { readonly [x: string]: string; } +>mx : { [x: string]: string; } +} + +type Mutable = { -readonly [P in keyof T]: T[P] }; +>Mutable : Mutable + +function f2(mt: Mutable, tt: T, rt: Readonly) { +>f2 : (mt: Mutable, tt: T, rt: Readonly) => void +>mt : Mutable +>tt : T +>rt : Readonly + + mt = tt; // Error +>mt = tt : T +>mt : Mutable +>tt : T + + mt = rt; // Error +>mt = rt : Readonly +>mt : Mutable +>rt : Readonly + + tt = mt; +>tt = mt : Mutable +>tt : T +>mt : Mutable + + tt = rt; // Error +>tt = rt : Readonly +>tt : T +>rt : Readonly + + rt = mt; +>rt = mt : Mutable +>rt : Readonly +>mt : Mutable + + rt = tt; +>rt = tt : T +>rt : Readonly +>tt : T +} + +function f3(m: { foo(): void }, p: { foo: () => void }, r: { readonly foo: () => void }) { +>f3 : (m: { foo(): void; }, p: { foo: () => void; }, r: { readonly foo: () => void; }) => void +>m : { foo(): void; } +>foo : () => void +>p : { foo: () => void; } +>foo : () => void +>r : { readonly foo: () => void; } +>foo : () => void + + m = r; +>m = r : { readonly foo: () => void; } +>m : { foo(): void; } +>r : { readonly foo: () => void; } + + p = r; // Error +>p = r : { readonly foo: () => void; } +>p : { foo: () => void; } +>r : { readonly foo: () => void; } +} + +interface B1 { + x: number; +>x : number +} + +interface D1 extends B1 { // Error + readonly x: number; +>x : number +} + +interface B2 { + get x(): number; +>x : number + + set x(value: number); +>x : number +>value : number +} + +interface D2 extends B2 { // Error + get x(): number; +>x : number +} + +class B3 { +>B3 : B3 + + x = 0; +>x : number +>0 : 0 +} + +class D3 extends B3 { // Error +>D3 : D3 +>B3 : B3 + + readonly x = 1; +>x : 1 +>1 : 1 +} + +type Foo = { +>Foo : Foo + + readonly a: string | undefined; +>a : string | undefined + + readonly b: number | undefined; +>b : number | undefined + +}; + +type Bar = { +>Bar : Bar + + a: string; +>a : string + +}; + +function f5(foo: Foo, bar: Bar) { +>f5 : (foo: Foo, bar: Bar) => boolean +>foo : Foo +>bar : Bar + + return foo === bar; +>foo === bar : boolean +>foo : Foo +>bar : Bar +} + +const y1: { a: string; b: number } = { a: "hello", b: 42 } as const; +>y1 : { a: string; b: number; } +>a : string +>b : number +>{ a: "hello", b: 42 } as const : { a: "hello"; b: 42; } +>{ a: "hello", b: 42 } : { a: "hello"; b: 42; } +>a : "hello" +>"hello" : "hello" +>b : 42 +>42 : 42 + +const y2: { a: string; readonly b: number } = { a: "hello", b: 42 } as const; +>y2 : { a: string; readonly b: number; } +>a : string +>b : number +>{ a: "hello", b: 42 } as const : { a: "hello"; readonly b: 42; } +>{ a: "hello", b: 42 } : { a: "hello"; readonly b: 42; } +>a : "hello" +>"hello" : "hello" +>b : 42 +>42 : 42 + +const y3: Record = { a: 1, b: 2 } as const; +>y3 : Record +>{ a: 1, b: 2 } as const : { a: 1; b: 2; } +>{ a: 1, b: 2 } : { a: 1; b: 2; } +>a : 1 +>1 : 1 +>b : 2 +>2 : 2 + +declare function f10(obj: T): T; +>f10 : (obj: T) => T +>a : string +>b : number +>obj : T + +declare function f11(obj: T): T; +>f11 : (obj: T) => T +>a : string +>b : number +>obj : T + +f10({ a: "hello", b: 42 }); +>f10({ a: "hello", b: 42 }) : { a: "hello"; b: 42; } +>f10 : (obj: T) => T +>{ a: "hello", b: 42 } : { a: "hello"; b: 42; } +>a : "hello" +>"hello" : "hello" +>b : 42 +>42 : 42 + +f11({ a: "hello", b: 42 }); +>f11({ a: "hello", b: 42 }) : { a: "hello"; readonly b: 42; } +>f11 : (obj: T) => T +>{ a: "hello", b: 42 } : { a: "hello"; b: 42; } +>a : "hello" +>"hello" : "hello" +>b : 42 +>42 : 42 + +interface MutableValue { + value: T; +>value : T +} + +interface ImmutableValue { + readonly value: T; +>value : T +} + +let i: ImmutableValue = { value: "hi" }; +>i : ImmutableValue +>{ value: "hi" } : { value: string; } +>value : string +>"hi" : "hi" + +i.value = "Excellent, I can't change it"; // Error +>i.value = "Excellent, I can't change it" : "Excellent, I can't change it" +>i.value : any +>i : ImmutableValue +>value : any +>"Excellent, I can't change it" : "Excellent, I can't change it" + +let m: MutableValue = i; // Error +>m : MutableValue +>i : ImmutableValue + +m.value = "Oh dear, I can change it"; +>m.value = "Oh dear, I can change it" : "Oh dear, I can change it" +>m.value : string +>m : MutableValue +>value : string +>"Oh dear, I can change it" : "Oh dear, I can change it" + diff --git a/tsc/testdata/baselines/reference/tsc/commandLine/Initialized-TSConfig-with---help.js b/tsc/testdata/baselines/reference/tsc/commandLine/Initialized-TSConfig-with---help.js index 45e76c1864bcd..a998b2e685086 100644 --- a/tsc/testdata/baselines/reference/tsc/commandLine/Initialized-TSConfig-with---help.js +++ b/tsc/testdata/baselines/reference/tsc/commandLine/Initialized-TSConfig-with---help.js @@ -34,6 +34,7 @@ You can learn more at https://aka.ms/tsconfig // Stricter Typechecking Options "noUncheckedIndexedAccess": true, + "enforceReadonly": true, "exactOptionalPropertyTypes": true, // Style Options diff --git a/tsc/testdata/baselines/reference/tsc/commandLine/Initialized-TSConfig-with-advanced-options.js b/tsc/testdata/baselines/reference/tsc/commandLine/Initialized-TSConfig-with-advanced-options.js index c930bfeb11f35..fd116209e6627 100644 --- a/tsc/testdata/baselines/reference/tsc/commandLine/Initialized-TSConfig-with-advanced-options.js +++ b/tsc/testdata/baselines/reference/tsc/commandLine/Initialized-TSConfig-with-advanced-options.js @@ -34,6 +34,7 @@ You can learn more at https://aka.ms/tsconfig // Stricter Typechecking Options "noUncheckedIndexedAccess": true, + "enforceReadonly": true, "exactOptionalPropertyTypes": true, // Style Options diff --git a/tsc/testdata/baselines/reference/tsc/commandLine/Initialized-TSConfig-with-boolean-value-compiler-options.js b/tsc/testdata/baselines/reference/tsc/commandLine/Initialized-TSConfig-with-boolean-value-compiler-options.js index 6991ef17a46e5..f8f7e7bfdac5b 100644 --- a/tsc/testdata/baselines/reference/tsc/commandLine/Initialized-TSConfig-with-boolean-value-compiler-options.js +++ b/tsc/testdata/baselines/reference/tsc/commandLine/Initialized-TSConfig-with-boolean-value-compiler-options.js @@ -34,6 +34,7 @@ You can learn more at https://aka.ms/tsconfig // Stricter Typechecking Options "noUncheckedIndexedAccess": true, + "enforceReadonly": true, "exactOptionalPropertyTypes": true, // Style Options diff --git a/tsc/testdata/baselines/reference/tsc/commandLine/Initialized-TSConfig-with-enum-value-compiler-options.js b/tsc/testdata/baselines/reference/tsc/commandLine/Initialized-TSConfig-with-enum-value-compiler-options.js index 5f38857fa08e9..37c6ef64e1869 100644 --- a/tsc/testdata/baselines/reference/tsc/commandLine/Initialized-TSConfig-with-enum-value-compiler-options.js +++ b/tsc/testdata/baselines/reference/tsc/commandLine/Initialized-TSConfig-with-enum-value-compiler-options.js @@ -34,6 +34,7 @@ You can learn more at https://aka.ms/tsconfig // Stricter Typechecking Options "noUncheckedIndexedAccess": true, + "enforceReadonly": true, "exactOptionalPropertyTypes": true, // Style Options diff --git a/tsc/testdata/baselines/reference/tsc/commandLine/Initialized-TSConfig-with-files-options.js b/tsc/testdata/baselines/reference/tsc/commandLine/Initialized-TSConfig-with-files-options.js index b48c72562edb0..9bec3c5a7b2d5 100644 --- a/tsc/testdata/baselines/reference/tsc/commandLine/Initialized-TSConfig-with-files-options.js +++ b/tsc/testdata/baselines/reference/tsc/commandLine/Initialized-TSConfig-with-files-options.js @@ -34,6 +34,7 @@ You can learn more at https://aka.ms/tsconfig // Stricter Typechecking Options "noUncheckedIndexedAccess": true, + "enforceReadonly": true, "exactOptionalPropertyTypes": true, // Style Options diff --git a/tsc/testdata/baselines/reference/tsc/commandLine/Initialized-TSConfig-with-list-compiler-options-with-enum-value.js b/tsc/testdata/baselines/reference/tsc/commandLine/Initialized-TSConfig-with-list-compiler-options-with-enum-value.js index f309647030735..2cf03eb605a2b 100644 --- a/tsc/testdata/baselines/reference/tsc/commandLine/Initialized-TSConfig-with-list-compiler-options-with-enum-value.js +++ b/tsc/testdata/baselines/reference/tsc/commandLine/Initialized-TSConfig-with-list-compiler-options-with-enum-value.js @@ -35,6 +35,7 @@ You can learn more at https://aka.ms/tsconfig // Stricter Typechecking Options "noUncheckedIndexedAccess": true, + "enforceReadonly": true, "exactOptionalPropertyTypes": true, // Style Options diff --git a/tsc/testdata/baselines/reference/tsc/commandLine/Initialized-TSConfig-with-list-compiler-options.js b/tsc/testdata/baselines/reference/tsc/commandLine/Initialized-TSConfig-with-list-compiler-options.js index 7fb35046fa304..e84f7bb50f18a 100644 --- a/tsc/testdata/baselines/reference/tsc/commandLine/Initialized-TSConfig-with-list-compiler-options.js +++ b/tsc/testdata/baselines/reference/tsc/commandLine/Initialized-TSConfig-with-list-compiler-options.js @@ -34,6 +34,7 @@ You can learn more at https://aka.ms/tsconfig // Stricter Typechecking Options "noUncheckedIndexedAccess": true, + "enforceReadonly": true, "exactOptionalPropertyTypes": true, // Style Options diff --git a/tsc/testdata/baselines/reference/tsc/commandLine/help-all.js b/tsc/testdata/baselines/reference/tsc/commandLine/help-all.js index db9dd22705973..af01534dacac4 100644 --- a/tsc/testdata/baselines/reference/tsc/commandLine/help-all.js +++ b/tsc/testdata/baselines/reference/tsc/commandLine/help-all.js @@ -20,12 +20,12 @@ Build one or more projects and their dependencies, if out of date --checkers Set the number of checkers per project. ---help, -h -Print this message. - --help, -? +--help, -h +Print this message. + --ignoreConfig Ignore the tsconfig found and build with commandline options and files. @@ -229,6 +229,11 @@ Deduplicate packages with the same name and version. type: boolean default: true +--enforceReadonly +Ensure that 'readonly' properties remain read-only in type relationships. +type: boolean +default: false + --exactOptionalPropertyTypes Interpret optional property types as written, rather than adding 'undefined'. type: boolean diff --git a/tsc/testdata/baselines/reference/tscWatch/commandLine/Initialized-TSConfig-with---watch.js b/tsc/testdata/baselines/reference/tscWatch/commandLine/Initialized-TSConfig-with---watch.js index 24a3371f420c8..643f8e38380d2 100644 --- a/tsc/testdata/baselines/reference/tscWatch/commandLine/Initialized-TSConfig-with---watch.js +++ b/tsc/testdata/baselines/reference/tscWatch/commandLine/Initialized-TSConfig-with---watch.js @@ -34,6 +34,7 @@ You can learn more at https://aka.ms/tsconfig // Stricter Typechecking Options "noUncheckedIndexedAccess": true, + "enforceReadonly": true, "exactOptionalPropertyTypes": true, // Style Options diff --git a/tsc/testdata/tests/cases/compiler/enforceReadonly1.ts b/tsc/testdata/tests/cases/compiler/enforceReadonly1.ts new file mode 100644 index 0000000000000..71dc44bfd7276 --- /dev/null +++ b/tsc/testdata/tests/cases/compiler/enforceReadonly1.ts @@ -0,0 +1,92 @@ +// @strict: true +// @enforceReadonly: true +// @noEmit: true + +function f1(mp: { x: string }, rp: { readonly x: string }, mx: { [x: string]: string }, rx: { readonly [x: string]: string }) { + mp = rp; // Error + rp = mp; + mx = mp; + mx = rp; + mx = rx; // Error + rx = mp; + rx = rp; + rx = mx; +} + +type Mutable = { -readonly [P in keyof T]: T[P] }; + +function f2(mt: Mutable, tt: T, rt: Readonly) { + mt = tt; // Error + mt = rt; // Error + tt = mt; + tt = rt; // Error + rt = mt; + rt = tt; +} + +function f3(m: { foo(): void }, p: { foo: () => void }, r: { readonly foo: () => void }) { + m = r; + p = r; // Error +} + +interface B1 { + x: number; +} + +interface D1 extends B1 { // Error + readonly x: number; +} + +interface B2 { + get x(): number; + set x(value: number); +} + +interface D2 extends B2 { // Error + get x(): number; +} + +class B3 { + x = 0; +} + +class D3 extends B3 { // Error + readonly x = 1; +} + +type Foo = { + readonly a: string | undefined; + readonly b: number | undefined; +}; + +type Bar = { + a: string; +}; + +function f5(foo: Foo, bar: Bar) { + return foo === bar; +} + +const y1: { a: string; b: number } = { a: "hello", b: 42 } as const; +const y2: { a: string; readonly b: number } = { a: "hello", b: 42 } as const; +const y3: Record = { a: 1, b: 2 } as const; + +declare function f10(obj: T): T; +declare function f11(obj: T): T; + +f10({ a: "hello", b: 42 }); +f11({ a: "hello", b: 42 }); + +interface MutableValue { + value: T; +} + +interface ImmutableValue { + readonly value: T; +} + +let i: ImmutableValue = { value: "hi" }; +i.value = "Excellent, I can't change it"; // Error + +let m: MutableValue = i; // Error +m.value = "Oh dear, I can change it";