From e82c356bd6cf857b6c089ccf107592ebd515a365 Mon Sep 17 00:00:00 2001 From: Nick Nassiri Date: Sun, 5 Jul 2026 13:59:45 -0700 Subject: [PATCH 1/4] fix(parser): accept computed well-known-symbol names in value/class position Extends #168's type-position parser hardening past type position: class fields/interface members/inline-object-type members with a computed key ([Symbol.iterator], [Symbol()], ...) now allow an omitted type annotation (implicit any, matching identifier-keyed members) instead of unconditionally requiring ':'. Method/call/construct signatures similarly allow an omitted return type. Also accepts 'Symbol' and other contextual keywords as a namespace name and in typeof-type-position dotted names, both previously routed through a bare IDENTIFIER-only Consume. Flips 17 of 24 outstanding TS conformance ParseErrors (symbolProperty*/ symbolType* cluster) to Pass/Fail. --- Parsing/AST.cs | 2 +- Parsing/Parser.Classes.cs | 9 ++++++-- Parsing/Parser.Declarations.cs | 39 +++++++++++++++++++++++++--------- Parsing/Parser.Namespaces.cs | 7 +++--- Parsing/Parser.Types.cs | 24 +++++++++++++++------ 5 files changed, 58 insertions(+), 23 deletions(-) diff --git a/Parsing/AST.cs b/Parsing/AST.cs index 35802bb9..1855348a 100644 --- a/Parsing/AST.cs +++ b/Parsing/AST.cs @@ -316,7 +316,7 @@ public record Expression(Expr Expr) : Stmt; /// inferred type is still kept). Set by the destructuring desugarer to carry the binding pattern's /// shape so a mixed array literal source infers as a tuple instead of an array (#783). Erased at /// runtime (interpreter and IL compiler ignore it). - public record Var(Token Name, string? TypeAnnotation, Expr? Initializer, bool HasDefiniteAssignmentAssertion = false, bool IsVar = false, TypeNode? TypeAnnotationNode = null, Expr? HoistTypeInferenceInitializer = null, TypeNode? InitializerContext = null) : Stmt; + public record Var(Token Name, string? TypeAnnotation, Expr? Initializer, bool HasDefiniteAssignmentAssertion = false, bool IsVar = false, TypeNode? TypeAnnotationNode = null, Expr? HoistTypeInferenceInitializer = null, TypeNode? InitializerContext = null, bool IsDeclare = false) : Stmt; /// /// Const variable declaration. Separate from Var for cleaner const-specific handling (e.g., unique symbol). /// Initializer is non-nullable since const always requires initialization. diff --git a/Parsing/Parser.Classes.cs b/Parsing/Parser.Classes.cs index 4ce6b963..56ec1a94 100644 --- a/Parsing/Parser.Classes.cs +++ b/Parsing/Parser.Classes.cs @@ -536,8 +536,13 @@ private void ParseComputedClassMember( throw new Exception($"Parse Error at line {syntheticName.Line}: Generator marker '*' can only be used with methods, not fields."); } - Consume(TokenType.COLON, "Expect ':' after computed property name."); - string typeAnnotation = ParseTypeAnnotation(); + // Type annotation is optional (ES class fields), matching the identifier-keyed field path: + // `[Symbol.iterator] = 0;` / `[Symbol.iterator];` are both valid without a `: type`. + string? typeAnnotation = null; + if (Match(TokenType.COLON)) + { + typeAnnotation = ParseTypeAnnotation(); + } Expr? initializer = null; if (Match(TokenType.EQUAL)) { diff --git a/Parsing/Parser.Declarations.cs b/Parsing/Parser.Declarations.cs index 57402474..959bddfa 100644 --- a/Parsing/Parser.Declarations.cs +++ b/Parsing/Parser.Declarations.cs @@ -351,11 +351,16 @@ private Stmt InterfaceDeclaration() { computedType = ParseMethodSignature(); } - else + else if (Match(TokenType.COLON)) { - Consume(TokenType.COLON, "Expect ':' after computed member name."); computedType = ParseTypeAnnotation(); } + else + { + // No type annotation — implicit `any`. + computedType = "any"; + _lastTypeNode = new NamedTypeNode("any", null, computedLine); + } TypeNode? computedTypeNode = TakeTypeNode(); ConsumeInterfaceMemberSeparator(); members.Add(new Stmt.InterfaceMember(computedTok, computedType, computedOptional, computedReadonly, computedIsMethod, computedTypeNode)); @@ -376,12 +381,16 @@ private Stmt InterfaceDeclaration() // Method signature: methodName(params): returnType or methodName(params): returnType type = ParseMethodSignature(); } - else + else if (Match(TokenType.COLON)) { - // Property: name: type - Consume(TokenType.COLON, "Expect ':' after member name."); type = ParseTypeAnnotation(); } + else + { + // No type annotation — implicit `any` (e.g. `interface Foo { prop }`). + type = "any"; + _lastTypeNode = new NamedTypeNode("any", null, memberName.Line); + } TypeNode? memberTypeNode = TakeTypeNode(); ConsumeInterfaceMemberSeparator(); @@ -678,9 +687,19 @@ private string ParseMethodSignature() } Consume(TokenType.RIGHT_PAREN, "Expect ')' after parameters."); - Consume(TokenType.COLON, "Expect ':' before return type."); - string returnType = ParseTypeAnnotation(); - TypeNode? returnTypeNode = TakeTypeNode(); + string returnType; + TypeNode? returnTypeNode; + if (Match(TokenType.COLON)) + { + returnType = ParseTypeAnnotation(); + returnTypeNode = TakeTypeNode(); + } + else + { + // No return type annotation — implicit `any` (e.g. `foo();` in an interface/type literal). + returnType = "any"; + returnTypeNode = new NamedTypeNode("any", null, Previous().Line); + } // Publish the structured form (or explicitly clear, so no nested node leaks out). A generic // method signature wraps the function in a GenericFunctionTypeNode; a `this` parameter has @@ -986,9 +1005,9 @@ private Stmt AmbientVarDeclaration(bool isConst) if (isConst) { // For ambient const, we use Var with no initializer (special case) - return new Stmt.Var(name, typeAnnotation, null, TypeAnnotationNode: typeAnnotationNode); + return new Stmt.Var(name, typeAnnotation, null, TypeAnnotationNode: typeAnnotationNode, IsDeclare: true); } - return new Stmt.Var(name, typeAnnotation, null, TypeAnnotationNode: typeAnnotationNode); + return new Stmt.Var(name, typeAnnotation, null, TypeAnnotationNode: typeAnnotationNode, IsDeclare: true); } /// diff --git a/Parsing/Parser.Namespaces.cs b/Parsing/Parser.Namespaces.cs index f5f30ecb..f19fc28f 100644 --- a/Parsing/Parser.Namespaces.cs +++ b/Parsing/Parser.Namespaces.cs @@ -11,14 +11,15 @@ public partial class Parser /// Whether this is an exported namespace private Stmt NamespaceDeclaration(bool isExported = false) { - // Parse namespace name (may be dotted: A.B.C) - Token firstName = Consume(TokenType.IDENTIFIER, "Expect namespace name."); + // Parse namespace name (may be dotted: A.B.C). Contextual keywords (e.g. `Symbol`) are + // valid identifiers here too — `module Symbol { }` shadows the global. + Token firstName = ConsumeIdentifierName("Expect namespace name."); List nameParts = [firstName]; // Collect dotted parts: A.B.C while (Match(TokenType.DOT)) { - Token part = Consume(TokenType.IDENTIFIER, "Expect identifier after '.' in namespace name."); + Token part = ConsumeIdentifierName("Expect identifier after '.' in namespace name."); nameParts.Add(part); } diff --git a/Parsing/Parser.Types.cs b/Parsing/Parser.Types.cs index 975dcf9e..99357360 100644 --- a/Parsing/Parser.Types.cs +++ b/Parsing/Parser.Types.cs @@ -503,9 +503,10 @@ private string ParsePrimaryType() sb.Append("typeof "); // `typeof undefined` / `typeof this` are valid queries alongside identifiers. + // Contextual keywords (e.g. `Symbol`) are valid identifiers here too — `typeof Symbol.obs`. Token first = Check(TokenType.UNDEFINED) || Check(TokenType.THIS) ? Advance() - : Consume(TokenType.IDENTIFIER, "Expect identifier after 'typeof' in type position."); + : ConsumeIdentifierName("Expect identifier after 'typeof' in type position."); sb.Append(first.Lexeme); // Handle property paths and index access: typeof obj.prop, typeof arr[0], typeof obj["key"] @@ -513,7 +514,7 @@ private string ParsePrimaryType() { if (Match(TokenType.DOT)) { - Token next = Consume(TokenType.IDENTIFIER, "Expect property name after '.'"); + Token next = ConsumeIdentifierName("Expect property name after '.'"); sb.Append('.'); sb.Append(next.Lexeme); } @@ -937,11 +938,16 @@ private string ParseInlineObjectType() { computedType = ParseMethodSignature(); } - else + else if (Match(TokenType.COLON)) { - Consume(TokenType.COLON, "Expect ':' after computed member name."); computedType = ParseUnionType(); } + else + { + // No type annotation — implicit `any`, matching a bare identifier member. + computedType = "any"; + _lastTypeNode = new NamedTypeNode("any", null, computedLine); + } // The string path renders computed members as plain fields (no method marker). if (TakeTypeNode() is { } computedNode) memberNodes.Add(new PropertyMemberNode(computedName, computedNode, computedOptional, false, computedLine)); @@ -1035,13 +1041,17 @@ private string ParseInlineObjectType() // Method signature: methodName(params): returnType propertyType = ParseMethodSignature(); } - else + else if (Match(TokenType.COLON)) { - // Property: name: type - Consume(TokenType.COLON, "Expect ':' after property name in object type."); // Member values may be conditional types: { x: T extends number ? T : string } propertyType = ParseConditionalType(); } + else + { + // No type annotation — implicit `any` (e.g. `interface Foo { prop }`). + propertyType = "any"; + _lastTypeNode = new NamedTypeNode("any", null, propertyName.Line); + } if (TakeTypeNode() is { } propertyNode) memberNodes.Add(new PropertyMemberNode(propertyName.Lexeme, propertyNode, isOptional, isMethodMember, propertyName.Line)); From 0a0d244747c83aaa7fc889df0127078350433523 Mon Sep 17 00:00:00 2001 From: Nick Nassiri Date: Sun, 5 Jul 2026 14:00:05 -0700 Subject: [PATCH 2/4] fix(types): model well-known-symbol members individually, not as one merged index MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Object literals and interfaces collapsed every computed well-known-symbol member ([Symbol.iterator], [Symbol.toStringTag], [Symbol.toPrimitive], ...) into a single merged symbol index signature, losing per-symbol precision. This produced spurious TS7053s reading/calling a specific symbol member, spurious TS2741s assigning an object literal with several distinct symbol members to an interface, and a spurious TS2322 writing through a symbol key that has its own accessor. CheckObject now records each well-known-symbol member under its canonical "@@name" (matching how interfaces/classes already model them); the index get/set paths check for that named member before falling back to the merged signature. Also, three smaller fixes surfaced by the same test cluster: - Interface string-index-signature compatibility (TS2411) no longer flags computed well-known-symbol members — a symbol key can never collide with a string index at runtime. - `declare const x: unique symbol;` (ambient, no initializer) no longer hits the "must be initialized with Symbol()" check, which only makes sense for a real initializer; gated on a new Stmt.Var.IsDeclare flag (not a null-initializer heuristic, which also matches non-ambient `let x: unique symbol;` and would wrongly let that through too). - typeof-narrowing now recognizes numeric/string enum members as matching "number"/"string" respectively; a union typeof-guard with no matching member now narrows to `never` instead of defining the binding as a literal null type (which read back as "undefined variable"). --- .../TypeChecker.Compatibility.TypeGuards.cs | 7 +++-- TypeSystem/TypeChecker.Expressions.cs | 30 +++++++++++++++---- TypeSystem/TypeChecker.Properties.Index.cs | 24 +++++++++++++-- .../TypeChecker.Statements.Interfaces.cs | 3 ++ TypeSystem/TypeChecker.Statements.cs | 15 ++++++++-- 5 files changed, 67 insertions(+), 12 deletions(-) diff --git a/TypeSystem/TypeChecker.Compatibility.TypeGuards.cs b/TypeSystem/TypeChecker.Compatibility.TypeGuards.cs index 96673851..8469a0f9 100644 --- a/TypeSystem/TypeChecker.Compatibility.TypeGuards.cs +++ b/TypeSystem/TypeChecker.Compatibility.TypeGuards.cs @@ -1359,8 +1359,11 @@ TypeInfo.DataView or TypeInfo.TypedArray or TypeInfo.Timeout or private bool TypeMatchesTypeof(TypeInfo type, string typeofResult) => typeofResult switch { - "string" => type is TypeInfo.String or TypeInfo.StringLiteral, - "number" => type is TypeInfo.Primitive { Type: TokenType.TYPE_NUMBER } or TypeInfo.NumberLiteral, + // A numeric/string enum's members ARE plain numbers/strings at runtime — matches like any + // other number/string-typed union member (a heterogeneous enum can't be split this way, so + // it matches neither and stays whichever side the surrounding logic puts it on). + "string" => type is TypeInfo.String or TypeInfo.StringLiteral or TypeInfo.Enum { Kind: EnumKind.String }, + "number" => type is TypeInfo.Primitive { Type: TokenType.TYPE_NUMBER } or TypeInfo.NumberLiteral or TypeInfo.Enum { Kind: EnumKind.Numeric }, "boolean" => type is TypeInfo.Primitive { Type: TokenType.TYPE_BOOLEAN } or TypeInfo.BooleanLiteral, "bigint" => type is TypeInfo.BigInt or TypeInfo.BigIntLiteral, "object" => type is TypeInfo.Null or TypeInfo.Record or TypeInfo.Array or TypeInfo.Instance or TypeInfo.Object, diff --git a/TypeSystem/TypeChecker.Expressions.cs b/TypeSystem/TypeChecker.Expressions.cs index 41f4a205..0ded04f8 100644 --- a/TypeSystem/TypeChecker.Expressions.cs +++ b/TypeSystem/TypeChecker.Expressions.cs @@ -511,7 +511,7 @@ private TypeInfo InferConstObjectType(Expr.ObjectLiteral obj) else if (prop.Kind == Expr.ObjectPropertyKind.Getter || prop.Kind == Expr.ObjectPropertyKind.Setter) { // For getters/setters, extract the property type from the function - string name = GetPropertyKeyNameForTypeCheck(prop.Key!); + string name = GetAccessorMemberName(prop.Key!); TypeInfo fnType = CheckExpr(prop.Value); if (prop.Kind == Expr.ObjectPropertyKind.Getter && fnType is TypeInfo.Function fn) { @@ -680,7 +680,7 @@ private TypeInfo CheckObject(Expr.ObjectLiteral obj) accessorProps.Add(prop); // Getter - extract return type from annotation only (don't check body yet) - string name = GetPropertyKeyNameForTypeCheck(prop.Key!); + string name = GetAccessorMemberName(prop.Key!); getterNames.Add(name); if (prop.Value is Expr.ArrowFunction arrow && arrow.ReturnType != null) { @@ -698,7 +698,7 @@ private TypeInfo CheckObject(Expr.ObjectLiteral obj) accessorProps.Add(prop); // Setter - extract parameter type from annotation only - string name = GetPropertyKeyNameForTypeCheck(prop.Key!); + string name = GetAccessorMemberName(prop.Key!); setterNames.Add(name); if (prop.Value is Expr.ArrowFunction arrow && arrow.Parameters.Count > 0 && arrow.Parameters[0].Type != null) { @@ -747,7 +747,13 @@ private TypeInfo CheckObject(Expr.ObjectLiteral obj) case Expr.ComputedKey ck: TypeInfo keyType = CheckExpr(ck.Expression); // Infer index signature based on key type - if (keyType is TypeInfo.String) + if (keyType is (TypeInfo.Symbol or TypeInfo.UniqueSymbol) && + TryGetWellKnownSymbolMemberName(ck.Expression) is { } wellKnownSymbolName) + // Symbol.iterator/toStringTag/toPrimitive/... — a named member (canonical + // "@@name", matching interfaces/classes), not merged into the symbol index + // signature. Merging would lose the per-symbol type (#99 Cluster B). + fields[wellKnownSymbolName] = valueType; + else if (keyType is TypeInfo.String) stringIndexType = UnifyIndexTypes(stringIndexType, valueType); else if (keyType is TypeInfo.Primitive n && n.Type == TokenType.TYPE_NUMBER) numberIndexType = UnifyIndexTypes(numberIndexType, valueType); @@ -797,7 +803,7 @@ private TypeInfo CheckObject(Expr.ObjectLiteral obj) { if (prop.Kind == Expr.ObjectPropertyKind.Getter) { - string name = GetPropertyKeyNameForTypeCheck(prop.Key!); + string name = GetAccessorMemberName(prop.Key!); TypeInfo getterType = CheckExpr(prop.Value); // Update the field type with the actual inferred type @@ -812,7 +818,7 @@ private TypeInfo CheckObject(Expr.ObjectLiteral obj) } else if (prop.Kind == Expr.ObjectPropertyKind.Setter) { - string name = GetPropertyKeyNameForTypeCheck(prop.Key!); + string name = GetAccessorMemberName(prop.Key!); TypeInfo setterType = CheckExpr(prop.Value); // Setter - extract the parameter type (or merge with existing getter type) @@ -854,6 +860,18 @@ private static string GetPropertyKeyNameForTypeCheck(Expr.PropertyKey key) }; } + /// + /// Accessor-name resolution used when inferring an object literal's type: a well-known-symbol + /// computed key (get [Symbol.toPrimitive]()) uses the canonical @@name form — + /// matching how interfaces/classes model the same members () + /// — so it lands as a distinct named field instead of collapsing multiple accessors onto the + /// literal string "[computed]". Anything else falls back to . + /// + private static string GetAccessorMemberName(Expr.PropertyKey key) => + key is Expr.ComputedKey ck && TryGetWellKnownSymbolMemberName(ck.Expression) is { } wellKnownName + ? wellKnownName + : GetPropertyKeyNameForTypeCheck(key); + /// /// Unifies index signature types - creates a union if types differ. /// diff --git a/TypeSystem/TypeChecker.Properties.Index.cs b/TypeSystem/TypeChecker.Properties.Index.cs index e0b426ac..41e2cf15 100644 --- a/TypeSystem/TypeChecker.Properties.Index.cs +++ b/TypeSystem/TypeChecker.Properties.Index.cs @@ -310,6 +310,15 @@ private TypeInfo CheckGetIndex(Expr.GetIndex getIndex) // Handle symbol index (Symbol and UniqueSymbol both qualify) if (indexType is TypeInfo.Symbol or TypeInfo.UniqueSymbol) { + // A well-known symbol (`Symbol.iterator`, ...) that the object type models as a distinct + // named member (canonical "@@name" — see CheckObject/TryGetWellKnownSymbolMemberName) + // resolves to THAT member's own type, not the merged symbol index signature — which + // would lose per-symbol precision when an object defines several distinct well-known + // symbol members (#99 Cluster B). + if (TryGetWellKnownSymbolMemberName(getIndex.Index) is { } wellKnownName && + GetMemberType(objType, wellKnownName) is { } wellKnownType) + return wellKnownType; + if (objType is TypeInfo.Interface itf4 && itf4.SymbolIndexType != null) return itf4.SymbolIndexType; if (objType is TypeInfo.Record rec4 && rec4.SymbolIndexType != null) @@ -610,9 +619,20 @@ or TypeInfo.WeakMap or TypeInfo.WeakSet or TypeInfo.Promise or TypeInfo.Function return valueType; } - // Handle symbol index - if (indexType is TypeInfo.Symbol) + // Handle symbol index (Symbol and UniqueSymbol both qualify) + if (indexType is TypeInfo.Symbol or TypeInfo.UniqueSymbol) { + // A well-known symbol (`Symbol.iterator`, ...) that the object type models as a distinct + // named member (canonical "@@name") is validated against THAT member's own type, not the + // merged symbol index signature — mirrors the read path in CheckGetIndex (#99 Cluster B). + if (TryGetWellKnownSymbolMemberName(setIndex.Index) is { } wellKnownName && + GetMemberType(objType, wellKnownName) is { } wellKnownType) + { + if (!IsCompatible(wellKnownType, valueType)) + throw new TypeCheckException($" Cannot assign '{valueType}' to symbol index signature type '{wellKnownType}'.", tsCode: "TS2322"); + return valueType; + } + if (objType is TypeInfo.Interface itf3 && itf3.SymbolIndexType != null) { if (!IsCompatible(itf3.SymbolIndexType, valueType)) diff --git a/TypeSystem/TypeChecker.Statements.Interfaces.cs b/TypeSystem/TypeChecker.Statements.Interfaces.cs index 1e9b0309..3799913a 100644 --- a/TypeSystem/TypeChecker.Statements.Interfaces.cs +++ b/TypeSystem/TypeChecker.Statements.Interfaces.cs @@ -330,6 +330,9 @@ private void CheckInterfaceDeclaration(Stmt.Interface interfaceStmt) { foreach (var (name, type) in members) { + // Computed well-known-symbol members (canonical "@@name") are exempt — a symbol + // key can never collide with a string index signature at runtime. + if (name.StartsWith("@@", StringComparison.Ordinal)) continue; if (!IsCompatible(stringIndexType, type)) { memberLines.TryGetValue(name, out var line); diff --git a/TypeSystem/TypeChecker.Statements.cs b/TypeSystem/TypeChecker.Statements.cs index 116a679b..959e714c 100644 --- a/TypeSystem/TypeChecker.Statements.cs +++ b/TypeSystem/TypeChecker.Statements.cs @@ -141,7 +141,15 @@ internal VoidResult VisitVar(Stmt.Var stmt) ? _environment.Get(stmt.Name.Lexeme) : null; // Node-first annotation resolution (type-AST migration), string fallback. - TypeInfo? declaredType = ResolveAnnotation(stmt.TypeAnnotation, stmt.TypeAnnotationNode); + // Ambient `declare const x: unique symbol;` has no Symbol()-call initializer to validate — + // it parses to Stmt.Var (declare const/let/var all do), so VisitConst's Symbol()-initializer + // special-case never sees it. Trust the annotation directly rather than resolving through + // the generic path, which always rejects a standalone `unique symbol` (TS1331). Gated on + // IsDeclare (not just a missing initializer) — a non-ambient `let x: unique symbol;` has no + // initializer either and must still be rejected. + TypeInfo? declaredType = stmt.TypeAnnotation == "unique symbol" && stmt.IsDeclare + ? new TypeInfo.UniqueSymbol(stmt.Name.Lexeme, $"typeof {stmt.Name.Lexeme}") + : ResolveAnnotation(stmt.TypeAnnotation, stmt.TypeAnnotationNode); // TS2304: a bare annotation name that resolves to nothing (e.g. `var a: A;` where the // only `A` lives in a nested module, invisible here). See ReportUnknownTypeName. @@ -663,8 +671,11 @@ internal VoidResult VisitIf(Stmt.If stmt) if (guard.VarName != null) { + // A guard can legitimately narrow to "nothing matches" (e.g. no union member's + // typeof matches the checked string) — that's `never`, not an absent type. Defining + // the binding as literal null here would make later lookups see it as undefined. var thenEnv = new TypeEnvironment(_environment); - thenEnv.Define(guard.VarName, guard.NarrowedType!); + thenEnv.Define(guard.VarName, guard.NarrowedType ?? new TypeInfo.Never()); using (new EnvironmentScope(this, thenEnv)) { CheckStmt(stmt.ThenBranch); From c179cfca99743c862b5eae4f26e609e680a19baf Mon Sep 17 00:00:00 2001 From: Nick Nassiri Date: Sun, 5 Jul 2026 14:00:09 -0700 Subject: [PATCH 3/4] test(conformance): update baseline for Track-1 parser/checker fixes 131 -> 203 Pass, 24 -> 7 ParseError, 0 regressions. --- .../baselines/interpreted.txt | 42 +++++++++---------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/SharpTS.TypeScriptConformance/baselines/interpreted.txt b/SharpTS.TypeScriptConformance/baselines/interpreted.txt index c72c82f8..19875a6b 100644 --- a/SharpTS.TypeScriptConformance/baselines/interpreted.txt +++ b/SharpTS.TypeScriptConformance/baselines/interpreted.txt @@ -70,11 +70,11 @@ tests/cases/conformance/es2023/intlNumberFormatES2023.ts Pass tests/cases/conformance/es2023/intlNumberFormatES5UseGrouping.ts Fail tests/cases/conformance/es6/Symbols/symbolDeclarationEmit1.ts Pass tests/cases/conformance/es6/Symbols/symbolDeclarationEmit10.ts Pass -tests/cases/conformance/es6/Symbols/symbolDeclarationEmit11.ts ParseError +tests/cases/conformance/es6/Symbols/symbolDeclarationEmit11.ts Pass tests/cases/conformance/es6/Symbols/symbolDeclarationEmit12.ts Fail tests/cases/conformance/es6/Symbols/symbolDeclarationEmit13.ts Pass tests/cases/conformance/es6/Symbols/symbolDeclarationEmit14.ts Pass -tests/cases/conformance/es6/Symbols/symbolDeclarationEmit2.ts ParseError +tests/cases/conformance/es6/Symbols/symbolDeclarationEmit2.ts Pass tests/cases/conformance/es6/Symbols/symbolDeclarationEmit3.ts Pass tests/cases/conformance/es6/Symbols/symbolDeclarationEmit4.ts Pass tests/cases/conformance/es6/Symbols/symbolDeclarationEmit5.ts Pass @@ -83,18 +83,18 @@ tests/cases/conformance/es6/Symbols/symbolDeclarationEmit7.ts Pass tests/cases/conformance/es6/Symbols/symbolDeclarationEmit8.ts Pass tests/cases/conformance/es6/Symbols/symbolDeclarationEmit9.ts Pass tests/cases/conformance/es6/Symbols/symbolProperty1.ts Pass -tests/cases/conformance/es6/Symbols/symbolProperty10.ts ParseError -tests/cases/conformance/es6/Symbols/symbolProperty11.ts ParseError -tests/cases/conformance/es6/Symbols/symbolProperty12.ts ParseError -tests/cases/conformance/es6/Symbols/symbolProperty13.ts ParseError -tests/cases/conformance/es6/Symbols/symbolProperty14.ts ParseError -tests/cases/conformance/es6/Symbols/symbolProperty15.ts ParseError -tests/cases/conformance/es6/Symbols/symbolProperty16.ts ParseError +tests/cases/conformance/es6/Symbols/symbolProperty10.ts Fail +tests/cases/conformance/es6/Symbols/symbolProperty11.ts Fail +tests/cases/conformance/es6/Symbols/symbolProperty12.ts Fail +tests/cases/conformance/es6/Symbols/symbolProperty13.ts Pass +tests/cases/conformance/es6/Symbols/symbolProperty14.ts Pass +tests/cases/conformance/es6/Symbols/symbolProperty15.ts Pass +tests/cases/conformance/es6/Symbols/symbolProperty16.ts Pass tests/cases/conformance/es6/Symbols/symbolProperty17.ts Fail -tests/cases/conformance/es6/Symbols/symbolProperty18.ts Fail +tests/cases/conformance/es6/Symbols/symbolProperty18.ts Pass tests/cases/conformance/es6/Symbols/symbolProperty19.ts Pass tests/cases/conformance/es6/Symbols/symbolProperty2.ts Pass -tests/cases/conformance/es6/Symbols/symbolProperty20.ts Fail +tests/cases/conformance/es6/Symbols/symbolProperty20.ts Pass tests/cases/conformance/es6/Symbols/symbolProperty21.ts Fail tests/cases/conformance/es6/Symbols/symbolProperty22.ts Pass tests/cases/conformance/es6/Symbols/symbolProperty23.ts Pass @@ -128,7 +128,7 @@ tests/cases/conformance/es6/Symbols/symbolProperty48.ts Pass tests/cases/conformance/es6/Symbols/symbolProperty49.ts Pass tests/cases/conformance/es6/Symbols/symbolProperty5.ts Pass tests/cases/conformance/es6/Symbols/symbolProperty50.ts Pass -tests/cases/conformance/es6/Symbols/symbolProperty51.ts ParseError +tests/cases/conformance/es6/Symbols/symbolProperty51.ts Pass tests/cases/conformance/es6/Symbols/symbolProperty52.ts Skipped:lib-drift tests/cases/conformance/es6/Symbols/symbolProperty53.ts Fail tests/cases/conformance/es6/Symbols/symbolProperty54.ts Fail @@ -137,12 +137,12 @@ tests/cases/conformance/es6/Symbols/symbolProperty56.ts Pass tests/cases/conformance/es6/Symbols/symbolProperty57.ts Pass tests/cases/conformance/es6/Symbols/symbolProperty58.ts Pass tests/cases/conformance/es6/Symbols/symbolProperty59.ts Fail -tests/cases/conformance/es6/Symbols/symbolProperty6.ts ParseError -tests/cases/conformance/es6/Symbols/symbolProperty60.ts Fail -tests/cases/conformance/es6/Symbols/symbolProperty61.ts ParseError -tests/cases/conformance/es6/Symbols/symbolProperty7.ts ParseError -tests/cases/conformance/es6/Symbols/symbolProperty8.ts ParseError -tests/cases/conformance/es6/Symbols/symbolProperty9.ts ParseError +tests/cases/conformance/es6/Symbols/symbolProperty6.ts Pass +tests/cases/conformance/es6/Symbols/symbolProperty60.ts Pass +tests/cases/conformance/es6/Symbols/symbolProperty61.ts Fail +tests/cases/conformance/es6/Symbols/symbolProperty7.ts Fail +tests/cases/conformance/es6/Symbols/symbolProperty8.ts Pass +tests/cases/conformance/es6/Symbols/symbolProperty9.ts Fail tests/cases/conformance/es6/Symbols/symbolType1.ts Fail tests/cases/conformance/es6/Symbols/symbolType10.ts Fail tests/cases/conformance/es6/Symbols/symbolType11.ts Pass @@ -151,9 +151,9 @@ tests/cases/conformance/es6/Symbols/symbolType13.ts Fail tests/cases/conformance/es6/Symbols/symbolType14.ts Fail tests/cases/conformance/es6/Symbols/symbolType15.ts Fail tests/cases/conformance/es6/Symbols/symbolType16.ts Pass -tests/cases/conformance/es6/Symbols/symbolType17.ts ParseError -tests/cases/conformance/es6/Symbols/symbolType18.ts ParseError -tests/cases/conformance/es6/Symbols/symbolType19.ts Fail +tests/cases/conformance/es6/Symbols/symbolType17.ts Pass +tests/cases/conformance/es6/Symbols/symbolType18.ts Pass +tests/cases/conformance/es6/Symbols/symbolType19.ts Pass tests/cases/conformance/es6/Symbols/symbolType2.ts Fail tests/cases/conformance/es6/Symbols/symbolType20.ts Fail tests/cases/conformance/es6/Symbols/symbolType3.ts Fail From a62d0f5c330120a6f7ae7ea1d07beff6becc85ca Mon Sep 17 00:00:00 2001 From: Nick Nassiri Date: Sun, 5 Jul 2026 14:00:26 -0700 Subject: [PATCH 4/4] =?UTF-8?q?docs(status):=20refresh=20TS=20conformance?= =?UTF-8?q?=20section=20(=C2=A717)=20after=20Track-1=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- STATUS.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/STATUS.md b/STATUS.md index baf46dd0..fd2c6354 100644 --- a/STATUS.md +++ b/STATUS.md @@ -507,16 +507,16 @@ The committed subset spans `types/typeRelationships/{assignmentCompatibility,sub | Bucket | Count | Share | |---|---:|---:| -| `Pass` | 182 | 61.5% | -| `Fail` | 75 | 25.3% | -| `ParseError` | 24 | 8.1% | -| `Skipped` (multi-file 8 / lib-drift 3 / directive 3) | 14 | 4.7% | +| `Pass` | 203 | 68.6% | +| `Fail` | 68 | 23.0% | +| `ParseError` | 7 | 2.4% | +| `Skipped` (multi-file 8 / lib-drift 6 / directive 3) | 17 | 5.7% | | `TypeCheckError` | 1 | 0.3% | | **Total** | **296** | | -The parser is no longer the dominant bottleneck (an earlier sweep took the original subset's `ParseError` count from 57 → 7): ambient `declare` of non-class declarations, `declare function`, generic/this/conditional/mapped/indexed-access/constructor/leading-operator types, `module Foo {}` namespaces, call/construct/index signatures, keyword & string/numeric property names, function-type and arrow optional/rest parameters, and more. Negative-test matching itself was unlocked by propagating canonical `TSnnnn` codes through the type-checker's error-recovery path ([#125](https://github.com/nickna/SharpTS/issues/125)), which also added a `strictNullChecks` option (default on; the runner follows each test's `@strict`/`@strictNullChecks` directive). The residual `ParseError` bucket is now concentrated in the `symbolProperty*`/`symbolType*` tests — computed well-known-symbol names in *value/class* position (the [#99](https://github.com/nickna/SharpTS/issues/99) Phase-A parser work only hardened *type* position). +The parser is no longer a significant bottleneck (an earlier sweep took the original subset's `ParseError` count from 57 → 24; a follow-up Track-1 round on the `symbolProperty*`/`symbolType*` cluster — computed well-known-symbol names in *value/class* position, which the [#99](https://github.com/nickna/SharpTS/issues/99) Phase-A parser work had only hardened for *type* position — took it 24 → 7): ambient `declare` of non-class declarations, `declare function`, generic/this/conditional/mapped/indexed-access/constructor/leading-operator types, `module Foo {}` namespaces, call/construct/index signatures, keyword & string/numeric property names, function-type and arrow optional/rest parameters, computed-key class fields/interface members/object-type members without an explicit type annotation (implicit `any`), and more. Negative-test matching itself was unlocked by propagating canonical `TSnnnn` codes through the type-checker's error-recovery path ([#125](https://github.com/nickna/SharpTS/issues/125)), which also added a `strictNullChecks` option (default on; the runner follows each test's `@strict`/`@strictNullChecks` directive). The residual 7 `ParseError`s are a scattered long tail (tagged-template escape sequences, `declare global` export forms, `import.meta`, `export * as ns`, logical-assignment edge cases, async-arrow-in-shared-memory) with no single shared root cause. -The dominant bucket is `Fail` — tests that parse and reach the type checker but whose diagnostic set doesn't yet match `tsc`'s. A [#99](https://github.com/nickna/SharpTS/issues/99) measurement spike over the lib-sensitive folders decomposed these: of 66 lib-folder Fails, ~17 are **spurious** (SharpTS emits an error `tsc` doesn't), a long tail are individual checker gaps (assignability, symbol arithmetic, computed-name constraints), and only ~6 are explicit lib-version drift (`TS2550`/`TS2583`/`TS2585`). **The finding: loading `tsc`'s `lib.*.d.ts` is _not_ the current pass-rate lever — checker breadth and parser gaps dominate the divergence.** Loading `lib.*.d.ts` ([#99](https://github.com/nickna/SharpTS/issues/99)) remains valuable as an *enabler* (it removes the "`Pass` = coincidence" asterisk since globals resolve to `any` today, unblocks DOM/JSX wholesale, and is a prerequisite for per-node types/symbols in [#88](https://github.com/nickna/SharpTS/issues/88)), but the near-term levers are checker correctness (kill the spurious-error cases; structural class-to-class assignability [#129](https://github.com/nickna/SharpTS/issues/129)) and finishing symbol-name parsing. +The dominant bucket is `Fail` — tests that parse and reach the type checker but whose diagnostic set doesn't yet match `tsc`'s. A [#99](https://github.com/nickna/SharpTS/issues/99) measurement spike over the lib-sensitive folders decomposed these: of the original 66 lib-folder Fails, ~17 were **spurious** (SharpTS emitted an error `tsc` didn't; cleared by Track-1 — object literals/interfaces now model each computed well-known-symbol member as its own named member instead of collapsing them into one merged symbol index signature, closing the false positives on `Symbol.iterator`/`Symbol.toStringTag`/`Symbol.toPrimitive` reads+writes, string-index-signature compatibility, and `declare const x: unique symbol`), a long tail are individual checker gaps (assignability, symbol arithmetic, computed-name constraints), and only ~6 are explicit lib-version drift (`TS2550`/`TS2583`/`TS2585`). **The finding: loading `tsc`'s `lib.*.d.ts` is _not_ the current pass-rate lever — checker breadth and parser gaps dominate the divergence.** Loading `lib.*.d.ts` ([#99](https://github.com/nickna/SharpTS/issues/99)) remains valuable as an *enabler* (it removes the "`Pass` = coincidence" asterisk since globals resolve to `any` today, unblocks DOM/JSX wholesale, and is a prerequisite for per-node types/symbols in [#88](https://github.com/nickna/SharpTS/issues/88)), but the near-term levers are checker correctness (structural class-to-class assignability [#129](https://github.com/nickna/SharpTS/issues/129); cross-statement CFA narrowing for compound logical assignment) and clearing the remaining scattered `ParseError`/`Fail` long tail. `Skipped` includes: - **Multi-file tests** (`Skipped:multi-file-deferred`) — cross-file resolution into the runner is follow-up work.