diff --git a/tsc/internal/pseudochecker/lookup.go b/tsc/internal/pseudochecker/lookup.go index 3fc7243df41f5..e697c1ec0fcae 100644 --- a/tsc/internal/pseudochecker/lookup.go +++ b/tsc/internal/pseudochecker/lookup.go @@ -49,14 +49,14 @@ func (ch *PseudoChecker) GetTypeOfDeclaration(node *ast.Node) *PseudoType { return ch.typeFromPropertyAssignment(node) case ast.KindCallExpression: switch ast.GetAssignmentDeclarationKind(node) { - // TODO: How much of the checker's getTypeFromPropertyDescriptor is worth trying to emulate over ASTs? + case ast.JSDeclarationKindObjectDefinePropertyValue: { - // !!! + } case ast.JSDeclarationKindObjectDefinePropertyExports: { - // !!! + } } return NewPseudoTypeNoResult(node) @@ -78,20 +78,18 @@ func (ch *PseudoChecker) typeFromPropertyAssignment(node *ast.Node) *PseudoType if expr != nil && (expr.Kind != PseudoTypeKindInferred || len(expr.AsPseudoTypeInferred().ErrorNodes) > 0) { return expr } - // fallback to NoResult if PseudoTypeKindInferred without error nodes + } } return NewPseudoTypeNoResult(node) } -// This is _not_ redundant with the reparser; see how expandoFunctionSymbolProperty.ts and similar behaves func (ch *PseudoChecker) typeFromExpandoProperty(node *ast.Node) *PseudoType { declaredType := node.Type() if declaredType != nil { return NewPseudoTypeDirect(declaredType) } - // While `node` is an expression, as an expando, it should also always be a - // declaration with a `.Symbol()` which requires declaration fallback handling + return NewPseudoTypeNoResult(node) } @@ -103,19 +101,19 @@ func (ch *PseudoChecker) typeFromProperty(node *ast.Node) *PseudoType { if ast.IsPropertyDeclaration(node) { init := node.Initializer() if init != nil && !isContextuallyTyped(node) { - // explicit fail on readonly template literals to allow for literal freshness in the future + if ast.HasModifier(node, ast.ModifierFlagsReadonly) && ast.IsTemplateExpression(init) { return NewPseudoTypeNoResult(node) } expr := ch.typeFromExpression(init) if expr != nil && (expr.Kind != PseudoTypeKindInferred || len(expr.AsPseudoTypeInferred().ErrorNodes) > 0) { if expr.Kind != PseudoTypeKindDirect && node.AsPropertyDeclaration().PostfixToken != nil && node.AsPropertyDeclaration().PostfixToken.Kind == ast.KindQuestionToken { - // type comes from the initializer expression on a property with a `?` - add `| undefined` to the type + return addUndefinedIfDefinitelyRequired(expr) } return expr } - // fallback to NoResult if PseudoTypeKindInferred without error nodes + } } return NewPseudoTypeNoResult(node) @@ -128,8 +126,8 @@ func (ch *PseudoChecker) typeFromVariable(declaration *ast.VariableDeclaration) } init := declaration.Initializer if init != nil && declaration.Symbol != nil && (len(declaration.Symbol.Declarations()) == 1 || core.CountWhere(declaration.Symbol.Declarations(), ast.IsVariableDeclaration) == 1) { - if !isContextuallyTyped(declaration.AsNode()) { // TODO: also should bail on expando declarations; reuse syntactic expando check used in declaration emit - // TODO: Strada forces an inference fallback on `const` variables with template expression initializers, to leave space for template literal freshness in the future + if !isContextuallyTyped(declaration.AsNode()) { + if ast.IsVarConst(declaration.AsNode()) && ast.IsTemplateExpression(init) { return NewPseudoTypeNoResult(declaration.AsNode()) } @@ -137,7 +135,7 @@ func (ch *PseudoChecker) typeFromVariable(declaration *ast.VariableDeclaration) if expr != nil && (expr.Kind != PseudoTypeKindInferred || len(expr.AsPseudoTypeInferred().ErrorNodes) > 0) { return expr } - // fallback to NoResult if PseudoTypeKindInferred without error nodes + } } return NewPseudoTypeNoResult(declaration.AsNode()) @@ -156,7 +154,7 @@ func (ch *PseudoChecker) typeFromAccessor(accessor *ast.Node) *PseudoType { if accessorDeclarations.SetAccessor != nil { errorNodes = append(errorNodes, accessorDeclarations.SetAccessor.AsNode()) } - res = NewPseudoTypeInferredWithErrors(res.AsPseudoTypeInferred().Expression, res.AsPseudoTypeInferred().IsSignatureReturn, errorNodes) // Move error up to the accessor + res = NewPseudoTypeInferredWithErrors(res.AsPseudoTypeInferred().Expression, res.AsPseudoTypeInferred().IsSignatureReturn, errorNodes) } return res } @@ -178,7 +176,7 @@ func (ch *PseudoChecker) getTypeAnnotationFromAccessor(node *ast.Node) *ast.Node if node == nil { return nil } - // !!! TODO: support ripping return type off of .FullSignature + if node.Kind == ast.KindGetAccessor { return node.AsGetAccessorDeclaration().Type } @@ -197,11 +195,10 @@ func isValueSignatureDeclaration(node *ast.Node) bool { return ast.IsFunctionExpression(node) || ast.IsArrowFunction(node) || ast.IsMethodDeclaration(node) || ast.IsAccessor(node) || ast.IsFunctionDeclaration(node) || ast.IsConstructorDeclaration(node) } -// does not return `nil`, returns a `NoResult` pseudotype instead func (ch *PseudoChecker) createReturnFromSignature(fn *ast.Node) *PseudoType { if ast.IsFunctionLike(fn) { d := fn.FunctionLikeData() - // !!! TODO: support ripping return type off of .FullSignature + r := d.Type if r != nil { return NewPseudoTypeDirect(r) @@ -224,7 +221,7 @@ func (ch *PseudoChecker) typeFromSingleReturnExpression(fn *ast.Node) *PseudoTyp body := fn.Body() if ast.IsBlock(body) { ast.ForEachReturnStatement(body, func(stmt *ast.Node) bool { - if stmt.Parent != body { // Why bail on nested return statements? + if stmt.Parent != body { candidateExpr = nil return true } @@ -252,23 +249,24 @@ func (ch *PseudoChecker) typeFromSingleReturnExpression(fn *ast.Node) *PseudoTyp return NewPseudoTypeDirect(t) } } else { - return ch.typeFromExpression(candidateExpr) + expr := ch.typeFromExpression(candidateExpr) + if expr != nil && (expr.Kind != PseudoTypeKindInferred || len(expr.AsPseudoTypeInferred().ErrorNodes) > 0) { + return expr + } } } return NewPseudoTypeInferred(fn, true) } -// This is basically `checkExpression` for pseudotypes func (ch *PseudoChecker) typeFromExpression(node *ast.Node) *PseudoType { switch node.Kind { case ast.KindOmittedExpression: return PseudoTypeUndefined case ast.KindParenthesizedExpression: - // assertions transformed on reparse, just unwrap + return ch.typeFromExpression(node.AsParenthesizedExpression().Expression) case ast.KindIdentifier: - // !!! TODO: in strada, this uses symbol information to ensure `node` refers to the global `undefined` symbol instead - // we should probably import `resolveName` and use it here to check for the same; but we have to setup some barebones pseudoglobals for that to work! + if node.AsIdentifier().Text == "undefined" { return PseudoTypeUndefined } @@ -289,9 +287,9 @@ func (ch *PseudoChecker) typeFromExpression(node *ast.Node) *PseudoType { case ast.KindObjectLiteralExpression: return ch.typeFromObjectLiteral(node.AsObjectLiteralExpression()) case ast.KindClassExpression: - return NewPseudoTypeInferredWithErrors(node, false, []*ast.Node{node}) // No possible annotation/directly mappable syntax + return NewPseudoTypeInferredWithErrors(node, false, []*ast.Node{node}) case ast.KindTemplateExpression: - // templateLitWithHoles as const, not supported + if IsInConstContext(node) { return NewPseudoTypeInferred(node, false) } @@ -316,7 +314,7 @@ func (ch *PseudoChecker) typeFromObjectLiteral(node *ast.ObjectLiteralExpression if errorNodes := ch.canGetTypeFromObjectLiteral(node); errorNodes != nil { return NewPseudoTypeInferredWithErrors(node.AsNode(), false, errorNodes) } - // we are in a const context producing an object literal type, there are no shorthand or spread assignments + if node.Properties == nil || len(node.Properties.Nodes) == 0 { return NewPseudoTypeObjectLiteral(nil) } @@ -359,14 +357,11 @@ func (ch *PseudoChecker) typeFromObjectLiteral(node *ast.ObjectLiteralExpression return NewPseudoTypeObjectLiteral(results) } -// roughly analogous to typeFromObjectLiteralAccessor in strada func (ch *PseudoChecker) getAccessorMember(accessor *ast.Node, name *ast.Node) *PseudoObjectElement { - allAccessors := ast.GetAllAccessorDeclarationsForDeclaration(accessor, accessor.Symbol().Declarations()) // TODO: node preservation for late-bound accessor pairs? + allAccessors := ast.GetAllAccessorDeclarationsForDeclaration(accessor, accessor.Symbol().Declarations()) - // TODO: handle pseudo-annotations from get accessor return positions? if allAccessors.GetAccessor != nil && allAccessors.GetAccessor.Type != nil && allAccessors.SetAccessor != nil && len(allAccessors.SetAccessor.Parameters.Nodes) > 0 && allAccessors.SetAccessor.Parameters.Nodes[0].AsParameterDeclaration().Type != nil { - // We have possible types for both accessors, we can't know if they are the same type so we keep both accessors if ast.IsGetAccessorDeclaration(accessor) { return NewPseudoGetAccessor( @@ -386,7 +381,6 @@ func (ch *PseudoChecker) getAccessorMember(accessor *ast.Node, name *ast.Node) * } if accessor == allAccessors.FirstAccessor { - // only one annotated accessor; output a property - `readonly` for a single `get` accessor accessorType := ch.typeFromAccessor(accessor) readonly := ast.IsGetAccessorDeclaration(accessor) && allAccessors.SecondAccessor == nil @@ -400,12 +394,9 @@ func (ch *PseudoChecker) getAccessorMember(accessor *ast.Node, name *ast.Node) * return nil } -// canGetTypeFromObjectLiteral checks whether an object literal can be typed by the pseudochecker. -// Returns nil if the object can be typed, or a slice of error nodes (shorthand/spread properties, -// non-literal computed names) that prevent typing. func (ch *PseudoChecker) canGetTypeFromObjectLiteral(node *ast.ObjectLiteralExpression) []*ast.Node { if node.Properties == nil || len(node.Properties.Nodes) == 0 { - return nil // empty object, ok + return nil } var errorNodes []*ast.Node for _, e := range node.Properties.Nodes { @@ -440,9 +431,9 @@ func (ch *PseudoChecker) typeFromArrayLiteral(node *ast.ArrayLiteralExpression) return NewPseudoTypeInferredWithErrors(node.AsNode(), false, errorNodes) } if IsInConstContext(node.AsNode()) && isContextuallyTyped(node.AsNode()) { - return NewPseudoTypeInferred(node.AsNode(), false) // expr in an as const cast with a contextual type has variable readonly state, bail + return NewPseudoTypeInferred(node.AsNode(), false) } - // we are in a const context producing a tuple type, there are no spread elements + results := make([]*PseudoType, 0, len(node.Elements.Nodes)) for _, e := range node.Elements.Nodes { results = append(results, ch.typeFromExpression(e)) @@ -450,10 +441,6 @@ func (ch *PseudoChecker) typeFromArrayLiteral(node *ast.ArrayLiteralExpression) return NewPseudoTypeTuple(results) } -// canGetTypeFromArrayLiteral checks whether an array literal can be typed by the pseudochecker. -// Returns nil if the array can be typed, or a slice of error nodes that prevent typing. -// For non-const arrays, the error node is the array expression itself. -// For const arrays with spreads, the error node is the spread element. func (ch *PseudoChecker) canGetTypeFromArrayLiteral(node *ast.ArrayLiteralExpression) []*ast.Node { if !IsInConstContext(node.AsNode()) { return []*ast.Node{node.AsNode()} @@ -466,7 +453,6 @@ func (ch *PseudoChecker) canGetTypeFromArrayLiteral(node *ast.ArrayLiteralExpres return nil } -// See `isConstContext` in `checker.go` - this is basically any node kind mentioned in that func isConstContextPropagatingKind(kind ast.Kind) bool { switch kind { case ast.KindArrayLiteralExpression, ast.KindObjectLiteralExpression, @@ -477,14 +463,12 @@ func isConstContextPropagatingKind(kind ast.Kind) bool { return false } -// IsInConstContext traverses up the parent chain to determine if the node is within a const context without needing any -// persistent traversal scope tracking (which could be unreliable in the presence of `typeof` queries anyway!) func IsInConstContext(node *ast.Node) bool { - // An expression is in a const context if an ancestor is a const type maybeAssertion expression + maybeAssertion := ast.FindAncestor( node.Parent, func(n *ast.Node) bool { - // stop traversing at assertions or anything not an array/object literal, since only those create or transfer const-ness + return ast.IsAssertionExpression(n) || !isConstContextPropagatingKind(n.Kind) }, ) @@ -552,39 +536,37 @@ func typeNodeCouldReferToUndefined(node *ast.Node) bool { node = node.AsParenthesizedTypeNode().Type } switch node.Kind { - // these types require symbolic/type resolution to know if they definitely do or do not refer to `undefined`, so might (or definitely do) + case ast.KindTypeReference, ast.KindIndexedAccessType, ast.KindTypeQuery, ast.KindOptionalType, ast.KindRestType, ast.KindImportType: return true case ast.KindIntersectionType: - // TODO: why is this not `core.Every`? strada treated unions and intersections the same, but logically every intersection member needs to contain a possible `undefined` - // for the result type to contain `undefined`. Likely a bug persisting from strada. + return core.Some(node.AsIntersectionTypeNode().Types.Nodes, typeNodeCouldReferToUndefined) case ast.KindUnionType: return core.Some(node.AsUnionTypeNode().Types.Nodes, typeNodeCouldReferToUndefined) - case ast.KindConditionalType: // suspect - should be treated as a union of both branches instead, likely a bug persisted from strada + case ast.KindConditionalType: return true - case ast.KindTypeOperator: // suspect - always refers to a subset of `string | number | symbol` for `keyof` or `symbol` for `unique` + case ast.KindTypeOperator: return true - case ast.KindTypePredicate: // suspect - always refers to `never` or `boolean`, depending on kind - considered possibly-`undefined` referencing for strada compat + case ast.KindTypePredicate: return true case ast.KindUndefinedKeyword: return true - default: // all other keywords, literal types, function-y types, array/tuple types, type literals, template types, this types + default: return false } } -// see this as the inverse of `canAddUndefined` in `expressionToTypeNode` in strada func CouldAlreadyReferToUndefinedType(t *PseudoType) bool { if t.Kind == PseudoTypeKindNoResult || t.Kind == PseudoTypeKindInferred || isUndefinedPseudoType(t) { return true } if t.Kind == PseudoTypeKindMaybeConstLocation { mc := t.AsPseudoTypeMaybeConstLocation() - return CouldAlreadyReferToUndefinedType(mc.RegularType) // if we're even asking this question, it's not a `const` location + return CouldAlreadyReferToUndefinedType(mc.RegularType) } if t.Kind == PseudoTypeKindDirect { - // inspect the direct type node + node := t.AsPseudoTypeDirect().TypeNode return typeNodeCouldReferToUndefined(node) } @@ -602,11 +584,6 @@ func isOptionalInitializedOrRestParameter(node *ast.ParameterDeclarationNode) bo return false } -// lastRequiredParamIndex returns the index just past the last required parameter -// in the list. A parameter is "required" if it has no question token, no initializer, -// and no rest token. This is computed in a single reverse pass so callers can -// determine "has required parameter after index i" with `i+1 < lastRequired` -// (equivalently, `i < lastRequired-1`) in O(1). func lastRequiredParamIndex(params []*ast.Node) int { for i, param := range slices.Backward(params) { if !isOptionalInitializedOrRestParameter(param) { @@ -617,14 +594,11 @@ func lastRequiredParamIndex(params []*ast.Node) int { } func addUndefinedIfDefinitelyRequired(expr *PseudoType) *PseudoType { - // If `expr` doesn't already contain `| undefined` or a direct/inferred type that may contain `undefined`, add `| undefined` - // in Strada, this reached into the checker to see if `undefined` was necessary, using `isRequiredOptionalParameter` from the emit resolver, - // but that's not required on top of the syntactic checks to get the same behavior. (If we get the type wrong, it'll mismatch later and be discarded - // for an inference error since corsa actually validates that pseudotypes semantically match the inferred type the checker produces) + if CouldAlreadyReferToUndefinedType(expr) { - return expr // will just error later, more like than not, unless the `undefined` is explicit in the pseudo + return expr } - // Explicitly add an `| undefined` + return NewPseudoTypeUnion([]*PseudoType{expr, PseudoTypeUndefined}) } @@ -633,7 +607,7 @@ func (ch *PseudoChecker) typeFromParameter(node *ast.ParameterDeclaration) *Pseu if parent.Kind == ast.KindSetAccessor { return ch.GetTypeOfAccessor(parent) } - // Fast path: no initializer means we never need parameter position info. + if node.Initializer == nil { if node.Type != nil { return NewPseudoTypeDirect(node.Type) @@ -655,9 +629,7 @@ func (ch *PseudoChecker) typeFromParameterWorker(node *ast.ParameterDeclaration, declaredType := node.Type if declaredType != nil { result := NewPseudoTypeDirect(declaredType) - // When the parameter has an initializer and strict null checks are enabled, - // check if `| undefined` needs to be added because there are required parameters after this one. - // This mirrors the checker's getTypeOfParameter which adds optionality for initialized parameters. + if ch.strictNullChecks && node.Initializer != nil && hasRequiredAfter { return addUndefinedIfDefinitelyRequired(result) } @@ -666,7 +638,7 @@ func (ch *PseudoChecker) typeFromParameterWorker(node *ast.ParameterDeclaration, if node.Initializer != nil && ast.IsIdentifier(node.Name()) && !isContextuallyTyped(node.AsNode()) { expr := ch.typeFromExpression(node.Initializer) if expr != nil && (expr.Kind == PseudoTypeKindInferred && len(expr.AsPseudoTypeInferred().ErrorNodes) == 0) { - expr = NewPseudoTypeInferredWithErrors(expr.AsPseudoTypeInferred().Expression, false, []*ast.Node{node.AsNode()}) // Move error up to the parameter + expr = NewPseudoTypeInferredWithErrors(expr.AsPseudoTypeInferred().Expression, false, []*ast.Node{node.AsNode()}) } if !ch.strictNullChecks { return expr @@ -674,13 +646,10 @@ func (ch *PseudoChecker) typeFromParameterWorker(node *ast.ParameterDeclaration, if !hasRequiredAfter { return expr } - // if there is a non-optional parameter after this one, a `| undefined` will need to explicitly be emitted on this parameter, if it's not already there + return addUndefinedIfDefinitelyRequired(expr) } - // TODO: In strada, the ID checker doesn't infer a parameter type from binding pattern names, but the real checker _does_! - // This means ID won't let you write, say, `({elem}) => false` without an annotation, even though it's trivially of type - // `(p0: {elem: any}) => boolean` and error-free under `noImplicitAny: false`! - // That limitation is retained here. + return NewPseudoTypeNoResult(node.AsNode()) } @@ -697,9 +666,7 @@ func (ch *PseudoChecker) cloneParameters(nodes *ast.NodeList) []*PseudoParameter p := e.AsParameterDeclaration() optional := p.QuestionToken != nil if !optional && p.Initializer != nil { - // A parameter with an initializer is optional only if all subsequent - // parameters are also optional/have initializers/are rest parameters. - // This matches the checker's isOptionalParameter semantics. + optional = i >= lastRequired-1 } result = append(result, NewPseudoParameter( @@ -714,7 +681,7 @@ func (ch *PseudoChecker) cloneParameters(nodes *ast.NodeList) []*PseudoParameter func isContextuallyTyped(node *ast.Node) bool { return ast.FindAncestor(node.Parent, func(n *ast.Node) bool { - // Functions calls or parent type annotations (but not the return type of a function expression) may impact the inferred type and local inference is unreliable + if ast.IsCallExpression(n) { return true } diff --git a/tsc/internal/transformers/declarations/diagnostics.go b/tsc/internal/transformers/declarations/diagnostics.go index 1050afa933eff..ad73580d8244a 100644 --- a/tsc/internal/transformers/declarations/diagnostics.go +++ b/tsc/internal/transformers/declarations/diagnostics.go @@ -151,12 +151,12 @@ func createGetSymbolAccessibilityDiagnosticForNode(node *ast.Node) GetSymbolAcce } else if ast.IsTypeParameterDeclaration(node) { return wrapSimpleDiagnosticSelector(node, getTypeParameterConstraintVisibilityDiagnosticMessage) } else if ast.IsExpressionWithTypeArguments(node) { - // unique node selection behavior, inline closure + return func(symbolAccessibilityResult printer.SymbolAccessibilityResult) *SymbolAccessibilityDiagnostic { var diagnosticMessage *diagnostics.Message - // Heritage clause is written by user so it can always be named + if ast.IsClassDeclaration(node.Parent.Parent) { - // Class or Interface implemented/extended is inaccessible + if ast.IsHeritageClause(node.Parent) && node.Parent.AsHeritageClause().Token == ast.KindImplementsKeyword { diagnosticMessage = diagnostics.Implements_clause_of_exported_class_0_has_or_is_using_private_name_1 } else { @@ -167,7 +167,7 @@ func createGetSymbolAccessibilityDiagnosticForNode(node *ast.Node) GetSymbolAcce } } } else { - // interface is inaccessible + diagnosticMessage = diagnostics.X_extends_clause_of_exported_interface_0_has_or_is_using_private_name_1 } @@ -182,7 +182,7 @@ func createGetSymbolAccessibilityDiagnosticForNode(node *ast.Node) GetSymbolAcce return diagnostics.Import_declaration_0_is_using_private_name_1 }) } else if ast.IsTypeAliasDeclaration(node) || ast.IsJSTypeAliasDeclaration(node) { - // unique node selection behavior, inline closure + return func(symbolAccessibilityResult printer.SymbolAccessibilityResult) *SymbolAccessibilityDiagnostic { diagnosticMessage := selectDiagnosticBasedOnModuleNameNoNameCheck( symbolAccessibilityResult, @@ -198,8 +198,7 @@ func createGetSymbolAccessibilityDiagnosticForNode(node *ast.Node) GetSymbolAcce } } } else if ast.IsCallExpression(node) { - // JS object.defineProperty call - // unique node selection behavior, inline closure + return func(symbolAccessibilityResult printer.SymbolAccessibilityResult) *SymbolAccessibilityDiagnostic { diagnosticMessage := selectDiagnosticBasedOnModuleName( symbolAccessibilityResult, @@ -229,11 +228,9 @@ func getVariableDeclarationTypeVisibilityDiagnosticMessage(node *ast.Node, symbo diagnostics.Exported_variable_0_has_or_is_using_private_name_1, ) - // This check is to ensure we don't report error on constructor parameter property as that error would be reported during parameter emit - // The only exception here is if the constructor was marked as private. we are not emitting the constructor parameters at all. } else if node.Kind == ast.KindPropertyDeclaration || node.Kind == ast.KindPropertyAccessExpression || node.Kind == ast.KindElementAccessExpression || node.Kind == ast.KindBinaryExpression || node.Kind == ast.KindPropertySignature || (node.Kind == ast.KindParameter && ast.HasSyntacticModifier(node.Parent, ast.ModifierFlagsPrivate)) { - // TODO(jfreeman): Deal with computed properties in error reporting. + if ast.IsStatic(node) { return selectDiagnosticBasedOnModuleName( symbolAccessibilityResult, @@ -249,7 +246,7 @@ func getVariableDeclarationTypeVisibilityDiagnosticMessage(node *ast.Node, symbo diagnostics.Public_property_0_of_exported_class_has_or_is_using_private_name_1, ) } else { - // Interfaces cannot have types that cannot be named + return selectDiagnosticBasedOnModuleNameNoNameCheck( symbolAccessibilityResult, diagnostics.Property_0_of_exported_interface_has_or_is_using_name_1_from_private_module_2, @@ -257,13 +254,12 @@ func getVariableDeclarationTypeVisibilityDiagnosticMessage(node *ast.Node, symbo ) } } - return nil // TODO: Audit behavior - should this panic? potentially silent error state in strada + return nil } func getAccessorDeclarationTypeVisibilityDiagnosticMessage(node *ast.Node, symbolAccessibilityResult printer.SymbolAccessibilityResult) *diagnostics.Message { if node.Kind == ast.KindSetAccessor { - // Getters can infer the return type from the returned expression, but setters cannot, so the - // "_from_external_module_1_but_cannot_be_named" case cannot occur. + if ast.IsStatic(node) { return selectDiagnosticBasedOnModuleNameNoNameCheck( symbolAccessibilityResult, @@ -299,21 +295,21 @@ func getAccessorDeclarationTypeVisibilityDiagnosticMessage(node *ast.Node, symbo func getReturnTypeVisibilityDiagnosticMessage(node *ast.Node, symbolAccessibilityResult printer.SymbolAccessibilityResult) *diagnostics.Message { switch node.Kind { case ast.KindConstructSignature: - // Interfaces cannot have return types that cannot be named + return selectDiagnosticBasedOnModuleNameNoNameCheck( symbolAccessibilityResult, diagnostics.Return_type_of_constructor_signature_from_exported_interface_has_or_is_using_name_0_from_private_module_1, diagnostics.Return_type_of_constructor_signature_from_exported_interface_has_or_is_using_private_name_0, ) case ast.KindCallSignature: - // Interfaces cannot have return types that cannot be named + return selectDiagnosticBasedOnModuleNameNoNameCheck( symbolAccessibilityResult, diagnostics.Return_type_of_call_signature_from_exported_interface_has_or_is_using_name_0_from_private_module_1, diagnostics.Return_type_of_call_signature_from_exported_interface_has_or_is_using_private_name_0, ) case ast.KindIndexSignature: - // Interfaces cannot have return types that cannot be named + return selectDiagnosticBasedOnModuleNameNoNameCheck( symbolAccessibilityResult, diagnostics.Return_type_of_index_signature_from_exported_interface_has_or_is_using_name_0_from_private_module_1, @@ -336,7 +332,7 @@ func getReturnTypeVisibilityDiagnosticMessage(node *ast.Node, symbolAccessibilit diagnostics.Return_type_of_public_method_from_exported_class_has_or_is_using_private_name_0, ) } else { - // Interfaces cannot have return types that cannot be named + return selectDiagnosticBasedOnModuleNameNoNameCheck( symbolAccessibilityResult, diagnostics.Return_type_of_method_from_exported_interface_has_or_is_using_name_0_from_private_module_1, @@ -366,7 +362,7 @@ func getParameterDeclarationTypeVisibilityDiagnosticMessage(node *ast.Node, symb ) case ast.KindConstructSignature, ast.KindConstructorType: - // Interfaces cannot have parameter types that cannot be named + return selectDiagnosticBasedOnModuleNameNoNameCheck( symbolAccessibilityResult, diagnostics.Parameter_0_of_constructor_signature_from_exported_interface_has_or_is_using_name_1_from_private_module_2, @@ -374,7 +370,7 @@ func getParameterDeclarationTypeVisibilityDiagnosticMessage(node *ast.Node, symb ) case ast.KindCallSignature: - // Interfaces cannot have parameter types that cannot be named + return selectDiagnosticBasedOnModuleNameNoNameCheck( symbolAccessibilityResult, diagnostics.Parameter_0_of_call_signature_from_exported_interface_has_or_is_using_name_1_from_private_module_2, @@ -382,7 +378,7 @@ func getParameterDeclarationTypeVisibilityDiagnosticMessage(node *ast.Node, symb ) case ast.KindIndexSignature: - // Interfaces cannot have parameter types that cannot be named + return selectDiagnosticBasedOnModuleNameNoNameCheck( symbolAccessibilityResult, diagnostics.Parameter_0_of_index_signature_from_exported_interface_has_or_is_using_name_1_from_private_module_2, @@ -405,7 +401,7 @@ func getParameterDeclarationTypeVisibilityDiagnosticMessage(node *ast.Node, symb diagnostics.Parameter_0_of_public_method_from_exported_class_has_or_is_using_private_name_1, ) } else { - // Interfaces cannot have parameter types that cannot be named + return selectDiagnosticBasedOnModuleNameNoNameCheck( symbolAccessibilityResult, diagnostics.Parameter_0_of_method_from_exported_interface_has_or_is_using_name_1_from_private_module_2, @@ -434,7 +430,7 @@ func getParameterDeclarationTypeVisibilityDiagnosticMessage(node *ast.Node, symb } func getTypeParameterConstraintVisibilityDiagnosticMessage(node *ast.Node, symbolAccessibilityResult printer.SymbolAccessibilityResult) *diagnostics.Message { - // Type parameter constraints are named by user so we should always be able to name it + switch node.Parent.Kind { case ast.KindClassDeclaration: return diagnostics.Type_parameter_0_of_exported_class_has_or_is_using_private_name_1 @@ -685,7 +681,7 @@ func createGetIsolatedDeclarationErrors(resolver printer.EmitResolver) func(node if ast.IsSetAccessorDeclaration(node.Parent) { return createAccessorTypeError(node.Parent) } - addUndefined := resolver.RequiresAddingImplicitUndefinedUnsafe(node, nil, nil) // skip checker lock - node builder will already have one + addUndefined := resolver.RequiresAddingImplicitUndefinedUnsafe(node, nil, nil) if !addUndefined && node.Initializer() != nil { return createExpressionError(node.Initializer()) } @@ -704,10 +700,8 @@ func createGetIsolatedDeclarationErrors(resolver printer.EmitResolver) func(node if heritageClause != nil { return createDiagnosticForNode(node, diagnostics.Extends_clause_can_t_contain_an_expression_with_isolatedDeclarations) } - if ast.IsPartOfTypeNode(node) || ast.IsTypeQueryNode(node) { - return createEntityInTypeNodeError(node) - } - if ast.IsEntityName(node) || ast.IsEntityNameExpression(node) { + if (ast.IsPartOfTypeNode(node) || node.Parent != nil && ast.IsTypeQueryNode(node.Parent)) && + (ast.IsEntityName(node) || ast.IsEntityNameExpression(node)) { return createEntityInTypeNodeError(node) } switch node.Kind { diff --git a/tsc/testdata/baselines/reference/compiler/isolatedDeclarationsReturnEntityName.errors.txt b/tsc/testdata/baselines/reference/compiler/isolatedDeclarationsReturnEntityName.errors.txt new file mode 100644 index 0000000000000..233821db2cd13 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/isolatedDeclarationsReturnEntityName.errors.txt @@ -0,0 +1,22 @@ +isolatedDeclarationsReturnEntityName.ts(5,17): error TS9007: Function must have an explicit return type annotation with --isolatedDeclarations. +isolatedDeclarationsReturnEntityName.ts(9,5): error TS9008: Method must have an explicit return type annotation with --isolatedDeclarations. + + +==== isolatedDeclarationsReturnEntityName.ts (2 errors) ==== + // Regression for #65106: value-position entity names in returns must produce + // TS9007/TS9008 (explicit return type), not TS9039/TS9013 (private name / expression). + + export const x: { a: number } = { a: 1 }; + export function f() { return x; } + ~ +!!! error TS9007: Function must have an explicit return type annotation with --isolatedDeclarations. +!!! related TS9031 isolatedDeclarationsReturnEntityName.ts:5:17: Add a return type to the function declaration. + + export class C { + x = 1; + method() { return this.x; } + ~~~~~~ +!!! error TS9008: Method must have an explicit return type annotation with --isolatedDeclarations. +!!! related TS9034 isolatedDeclarationsReturnEntityName.ts:9:5: Add a return type to the method + } + \ No newline at end of file diff --git a/tsc/testdata/baselines/reference/compiler/isolatedDeclarationsReturnEntityName.js b/tsc/testdata/baselines/reference/compiler/isolatedDeclarationsReturnEntityName.js new file mode 100644 index 0000000000000..9e3ff10660e73 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/isolatedDeclarationsReturnEntityName.js @@ -0,0 +1,24 @@ +//// [tests/cases/compiler/isolatedDeclarationsReturnEntityName.ts] //// + +//// [isolatedDeclarationsReturnEntityName.ts] +// Regression for #65106: value-position entity names in returns must produce +// TS9007/TS9008 (explicit return type), not TS9039/TS9013 (private name / expression). + +export const x: { a: number } = { a: 1 }; +export function f() { return x; } + +export class C { + x = 1; + method() { return this.x; } +} + + +//// [isolatedDeclarationsReturnEntityName.js] +// Regression for #65106: value-position entity names in returns must produce +// TS9007/TS9008 (explicit return type), not TS9039/TS9013 (private name / expression). +export const x = { a: 1 }; +export function f() { return x; } +export class C { + x = 1; + method() { return this.x; } +} diff --git a/tsc/testdata/baselines/reference/compiler/isolatedDeclarationsReturnEntityName.symbols b/tsc/testdata/baselines/reference/compiler/isolatedDeclarationsReturnEntityName.symbols new file mode 100644 index 0000000000000..135965c0c1b57 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/isolatedDeclarationsReturnEntityName.symbols @@ -0,0 +1,28 @@ +//// [tests/cases/compiler/isolatedDeclarationsReturnEntityName.ts] //// + +=== isolatedDeclarationsReturnEntityName.ts === +// Regression for #65106: value-position entity names in returns must produce +// TS9007/TS9008 (explicit return type), not TS9039/TS9013 (private name / expression). + +export const x: { a: number } = { a: 1 }; +>x : Symbol(x, Decl(isolatedDeclarationsReturnEntityName.ts, 3, 12)) +>a : Symbol(a, Decl(isolatedDeclarationsReturnEntityName.ts, 3, 17)) +>a : Symbol(a, Decl(isolatedDeclarationsReturnEntityName.ts, 3, 33)) + +export function f() { return x; } +>f : Symbol(f, Decl(isolatedDeclarationsReturnEntityName.ts, 3, 41)) +>x : Symbol(x, Decl(isolatedDeclarationsReturnEntityName.ts, 3, 12)) + +export class C { +>C : Symbol(C, Decl(isolatedDeclarationsReturnEntityName.ts, 4, 33)) + + x = 1; +>x : Symbol(C.x, Decl(isolatedDeclarationsReturnEntityName.ts, 6, 16)) + + method() { return this.x; } +>method : Symbol(C.method, Decl(isolatedDeclarationsReturnEntityName.ts, 7, 10)) +>this.x : Symbol(C.x, Decl(isolatedDeclarationsReturnEntityName.ts, 6, 16)) +>this : Symbol(C, Decl(isolatedDeclarationsReturnEntityName.ts, 4, 33)) +>x : Symbol(C.x, Decl(isolatedDeclarationsReturnEntityName.ts, 6, 16)) +} + diff --git a/tsc/testdata/baselines/reference/compiler/isolatedDeclarationsReturnEntityName.types b/tsc/testdata/baselines/reference/compiler/isolatedDeclarationsReturnEntityName.types new file mode 100644 index 0000000000000..d5a61de689517 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/isolatedDeclarationsReturnEntityName.types @@ -0,0 +1,31 @@ +//// [tests/cases/compiler/isolatedDeclarationsReturnEntityName.ts] //// + +=== isolatedDeclarationsReturnEntityName.ts === +// Regression for #65106: value-position entity names in returns must produce +// TS9007/TS9008 (explicit return type), not TS9039/TS9013 (private name / expression). + +export const x: { a: number } = { a: 1 }; +>x : { a: number; } +>a : number +>{ a: 1 } : { a: number; } +>a : number +>1 : 1 + +export function f() { return x; } +>f : () => { a: number; } +>x : { a: number; } + +export class C { +>C : C + + x = 1; +>x : number +>1 : 1 + + method() { return this.x; } +>method : () => number +>this.x : number +>this : this +>x : number +} + diff --git a/tsc/testdata/tests/cases/compiler/isolatedDeclarationsReturnEntityName.ts b/tsc/testdata/tests/cases/compiler/isolatedDeclarationsReturnEntityName.ts new file mode 100644 index 0000000000000..1618e96332359 --- /dev/null +++ b/tsc/testdata/tests/cases/compiler/isolatedDeclarationsReturnEntityName.ts @@ -0,0 +1,15 @@ +// @declaration: true +// @isolatedDeclarations: true +// @declarationMap: false +// @target: ESNext + +// Regression for #65106: value-position entity names in returns must produce +// TS9007/TS9008 (explicit return type), not TS9039/TS9013 (private name / expression). + +export const x: { a: number } = { a: 1 }; +export function f() { return x; } + +export class C { + x = 1; + method() { return this.x; } +}