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
37 changes: 37 additions & 0 deletions Parsing/Parser.Classes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,15 @@ private Stmt ClassDeclaration(bool isAbstract, List<Decorator>? classDecorators
// Check for computed property name: [expression]: type = value
else if (Check(TokenType.LEFT_BRACKET))
{
// Index signature: [key: string|number|symbol]: ValueType. Distinguished from a
// computed property name ([expr]: T) by an identifier immediately followed by ':'.
// Parsed here so the class body no longer hits a ParseError; full modeling of the
// index type on the class is deferred (see issue #121).
if (TryConsumeClassIndexSignature())
{
continue;
}

// Validate: computed properties cannot have access modifiers in TypeScript
// Actually TypeScript does allow this, so we'll support it

Expand Down Expand Up @@ -497,6 +506,34 @@ private Stmt ClassDeclaration(bool isAbstract, List<Decorator>? classDecorators
return new Stmt.Class(name, typeParams, superclassExpr, superclassTypeArgs, methods, fields, accessors.Count > 0 ? accessors : null, autoAccessors.Count > 0 ? autoAccessors : null, interfaces, interfaceTypeArgs, isAbstract, classDecorators, isDeclare, staticInitializers.Count > 0 ? staticInitializers : null);
}

/// <summary>
/// Probes for a class index signature: <c>[key: string|number|symbol]: ValueType</c>.
/// Must be called positioned at the opening <c>[</c>. On a match, consumes the whole signature
/// (and its terminator) and returns true; on no match, restores position and returns false so
/// the caller can fall through to computed-property-name parsing. The parsed value type is
/// currently discarded — modeling the index type on the class type is deferred (issue #121).
/// </summary>
private bool TryConsumeClassIndexSignature()
{
int saved = _current;

if (!Match(TokenType.LEFT_BRACKET)) { _current = saved; return false; }
if (!Check(TokenType.IDENTIFIER)) { _current = saved; return false; }
Advance(); // key name
if (!Match(TokenType.COLON)) { _current = saved; return false; }
if (!Match(TokenType.TYPE_STRING, TokenType.TYPE_NUMBER, TokenType.TYPE_SYMBOL))
{
_current = saved;
return false;
}
if (!Match(TokenType.RIGHT_BRACKET)) { _current = saved; return false; }
if (!Match(TokenType.COLON)) { _current = saved; return false; }

ParseTypeAnnotation(); // value type (validated, then discarded)
ConsumeSemicolon("Expect ';' after index signature.");
return true;
}

/// <summary>
/// Synchronizes within a class body, stopping at member boundaries or the closing brace.
/// </summary>
Expand Down
18 changes: 18 additions & 0 deletions Parsing/Parser.Types.cs
Original file line number Diff line number Diff line change
Expand Up @@ -562,8 +562,26 @@ private string ParseInlineObjectType()

members.Add($"[{keyType}]: {valueType}");
}
// Construct signature: new (params): ReturnType or new <T>(params): ReturnType
else if (Check(TokenType.NEW))
{
Advance(); // consume 'new'
// ParseMethodSignature consumes optional <generics>, the params, and the return
// type, producing an arrow string "(params) => ret"; prefix "new " for a
// construct signature so ParseInlineObjectTypeInfo can tell the two apart.
members.Add("new " + ParseMethodSignature());
}
// Call signature: (params): ReturnType or <T>(params): ReturnType
else if (Check(TokenType.LEFT_PAREN) || (Check(TokenType.LESS) && IsCallSignatureStart()))
{
members.Add(ParseMethodSignature());
}
else
{
// Optional readonly modifier on a property member
bool isReadonly = Match(TokenType.READONLY);
_ = isReadonly; // readonly is parsed but not yet modeled on inline object types

// Parse property/method name
Token propertyName = Consume(TokenType.IDENTIFIER, "Expect property name in object type.");

Expand Down
10 changes: 5 additions & 5 deletions SharpTS.TypeScriptConformance/baselines/interpreted.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@ tests/cases/conformance/types/typeRelationships/assignmentCompatibility/anyAssig
tests/cases/conformance/types/typeRelationships/assignmentCompatibility/anyAssignableToEveryType.ts Pass
tests/cases/conformance/types/typeRelationships/assignmentCompatibility/anyAssignableToEveryType2.ts ParseError
tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatBetweenTupleAndArray.ts Fail
tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithCallSignatures.ts ParseError
tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithCallSignatures.ts Fail
tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithCallSignatures2.ts Fail
tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithCallSignatures3.ts ParseError
tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithCallSignatures4.ts ParseError
tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithCallSignatures5.ts ParseError
tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithCallSignatures5.ts Fail
tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithCallSignatures6.ts Fail
tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithCallSignaturesWithOptionalParameters.ts ParseError
tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithCallSignaturesWithRestParameters.ts ParseError
tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithConstructSignatures.ts ParseError
tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithConstructSignatures.ts Fail
tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithConstructSignatures2.ts ParseError
tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithConstructSignatures3.ts ParseError
tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithConstructSignatures4.ts ParseError
Expand All @@ -31,7 +31,7 @@ tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignme
tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithEnumIndexer.ts Fail
tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithGenericCallSignatures.ts Pass
tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithGenericCallSignatures2.ts Fail
tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithGenericCallSignatures3.ts ParseError
tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithGenericCallSignatures3.ts Pass
tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithGenericCallSignatures4.ts Fail
tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithGenericCallSignaturesWithOptionalParameters.ts ParseError
tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithNumericIndexer.ts ParseError
Expand All @@ -54,7 +54,7 @@ tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignme
tests/cases/conformance/types/typeRelationships/assignmentCompatibility/callSignatureAssignabilityInInheritance.ts ParseError
tests/cases/conformance/types/typeRelationships/assignmentCompatibility/callSignatureAssignabilityInInheritance2.ts ParseError
tests/cases/conformance/types/typeRelationships/assignmentCompatibility/callSignatureAssignabilityInInheritance3.ts ParseError
tests/cases/conformance/types/typeRelationships/assignmentCompatibility/callSignatureAssignabilityInInheritance4.ts ParseError
tests/cases/conformance/types/typeRelationships/assignmentCompatibility/callSignatureAssignabilityInInheritance4.ts Pass
tests/cases/conformance/types/typeRelationships/assignmentCompatibility/callSignatureAssignabilityInInheritance5.ts ParseError
tests/cases/conformance/types/typeRelationships/assignmentCompatibility/callSignatureAssignabilityInInheritance6.ts Fail
tests/cases/conformance/types/typeRelationships/assignmentCompatibility/constructSignatureAssignabilityInInheritance.ts ParseError
Expand Down
28 changes: 28 additions & 0 deletions TypeSystem/TypeChecker.TypeParsing.cs
Original file line number Diff line number Diff line change
Expand Up @@ -796,6 +796,8 @@ private TypeInfo ParseInlineObjectTypeInfo(string objStr)
TypeInfo? stringIndexType = null;
TypeInfo? numberIndexType = null;
TypeInfo? symbolIndexType = null;
List<string> callSignatures = [];
bool hasConstructSignature = false;

// Split by semicolon (the separator used in ParseInlineObjectType)
var members = SplitObjectMembers(inner.AsSpan());
Expand All @@ -805,6 +807,22 @@ private TypeInfo ParseInlineObjectTypeInfo(string objStr)
string m = member.Trim();
if (string.IsNullOrEmpty(m)) continue;

// Construct signature: "new (params) => ret" (emitted by ParseInlineObjectType).
// Not yet modeled on object types; recognized here so it doesn't mis-parse as a field.
if (m.StartsWith("new ") && m.Contains("=>"))
{
hasConstructSignature = true;
continue;
}

// Call signature: "(params) => ret" or "<T>(params) => ret" — a bare signature with
// no leading "name:". (Method/arrow properties start with their name, so don't collide.)
if ((m.StartsWith("(") || m.StartsWith("<")) && m.Contains("=>"))
{
callSignatures.Add(m);
continue;
}

// Check for index signature: [string]: type, [number]: type, [symbol]: type
if (m.StartsWith("["))
{
Expand Down Expand Up @@ -853,6 +871,16 @@ private TypeInfo ParseInlineObjectTypeInfo(string objStr)
fields[propName] = ToTypeInfo(propType);
}

// A pure single call-signature object type (e.g. `{ (x: number): void }`) is structurally
// a function type — resolve it as such so it type-checks against function types correctly.
// Mixed/overloaded/construct cases aren't modeled yet; their named fields still form a
// Record that reaches the checker (signatures are dropped, not turned into bogus fields).
if (callSignatures.Count == 1 && fields.Count == 0 && !hasConstructSignature
&& stringIndexType == null && numberIndexType == null && symbolIndexType == null)
{
return ToTypeInfo(callSignatures[0]);
}

return new TypeInfo.Record(
fields.ToFrozenDictionary(),
stringIndexType,
Expand Down
Loading