Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Parsing/AST.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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).</param>
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;
/// <summary>
/// Const variable declaration. Separate from Var for cleaner const-specific handling (e.g., unique symbol).
/// Initializer is non-nullable since const always requires initialization.
Expand Down
9 changes: 7 additions & 2 deletions Parsing/Parser.Classes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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))
{
Expand Down
39 changes: 29 additions & 10 deletions Parsing/Parser.Declarations.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand All @@ -376,12 +381,16 @@ private Stmt InterfaceDeclaration()
// Method signature: methodName(params): returnType or methodName<T>(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();
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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);
}

/// <summary>
Expand Down
7 changes: 4 additions & 3 deletions Parsing/Parser.Namespaces.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,15 @@ public partial class Parser
/// <param name="isExported">Whether this is an exported namespace</param>
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<Token> 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);
}

Expand Down
24 changes: 17 additions & 7 deletions Parsing/Parser.Types.cs
Original file line number Diff line number Diff line change
Expand Up @@ -503,17 +503,18 @@ 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"]
while (true)
{
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);
}
Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -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));
Expand Down
12 changes: 6 additions & 6 deletions STATUS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Loading
Loading