diff --git a/Parsing/Parser.Classes.cs b/Parsing/Parser.Classes.cs index a14df97d..c7cc839a 100644 --- a/Parsing/Parser.Classes.cs +++ b/Parsing/Parser.Classes.cs @@ -306,6 +306,15 @@ private Stmt ClassDeclaration(bool isAbstract, List? 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 @@ -497,6 +506,34 @@ private Stmt ClassDeclaration(bool isAbstract, List? 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); } + /// + /// Probes for a class index signature: [key: string|number|symbol]: ValueType. + /// Must be called positioned at the opening [. 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). + /// + 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; + } + /// /// Synchronizes within a class body, stopping at member boundaries or the closing brace. /// diff --git a/Parsing/Parser.Types.cs b/Parsing/Parser.Types.cs index dfa15640..3b581654 100644 --- a/Parsing/Parser.Types.cs +++ b/Parsing/Parser.Types.cs @@ -562,8 +562,26 @@ private string ParseInlineObjectType() members.Add($"[{keyType}]: {valueType}"); } + // Construct signature: new (params): ReturnType or new (params): ReturnType + else if (Check(TokenType.NEW)) + { + Advance(); // consume 'new' + // ParseMethodSignature consumes optional , 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 (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."); diff --git a/SharpTS.TypeScriptConformance/baselines/interpreted.txt b/SharpTS.TypeScriptConformance/baselines/interpreted.txt index 3c480c78..94db057d 100644 --- a/SharpTS.TypeScriptConformance/baselines/interpreted.txt +++ b/SharpTS.TypeScriptConformance/baselines/interpreted.txt @@ -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 @@ -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 @@ -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 diff --git a/TypeSystem/TypeChecker.TypeParsing.cs b/TypeSystem/TypeChecker.TypeParsing.cs index 599302c0..400d5e23 100644 --- a/TypeSystem/TypeChecker.TypeParsing.cs +++ b/TypeSystem/TypeChecker.TypeParsing.cs @@ -796,6 +796,8 @@ private TypeInfo ParseInlineObjectTypeInfo(string objStr) TypeInfo? stringIndexType = null; TypeInfo? numberIndexType = null; TypeInfo? symbolIndexType = null; + List callSignatures = []; + bool hasConstructSignature = false; // Split by semicolon (the separator used in ParseInlineObjectType) var members = SplitObjectMembers(inner.AsSpan()); @@ -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 "(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("[")) { @@ -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,