diff --git a/draft.md b/draft/index.md similarity index 100% rename from draft.md rename to draft/index.md diff --git a/draft/object-types.md b/draft/object-types.md new file mode 100644 index 0000000..3104f7b --- /dev/null +++ b/draft/object-types.md @@ -0,0 +1,625 @@ +# Object Types + +Example + +```ts +type A = string; +type B = number; +type C = { + a: string; + b: number; +}; + +type D = { + a: string; + b: number; + c: A; + d: B; + e: C; + f: { + foo: string; + bar: number; + }; +}; +``` + +A type alias can be + +- A literal type (string, number) +- An object type with type literals +- An object type with type literals and identifiers +- An object type with type literals, identifiers, and other object types + +## Type Alias: A literal type (string, number) + +```ts +type A = string; +type B = number; +``` + +AST: + +```ts +{ + kind: TypeAlias, + name: Identifier, + type: string | number +} +``` + +## Type Alias: An object type with type literals + +```ts +type C = { + a: string; + b: number; +}; +``` + +AST: + +```ts +{ + kind: TypeAlias, + name: Identifier, + type: { + kind: TypeLiteral, + members: { + kind: PropertySignature + name: Identifier, + type: string | number + }[] + } +} +``` + +## Type Alias: An object type with type literals and identifiers + +```ts +type D = { + a: string; + b: number; + c: A; + d: B; + e: C; +}; +``` + +AST: + +```ts +// TypeAlias +{ + kind: TypeAlias, + name: Identifier, + type: { + kind: TypeLiteral, + members: { + kind: PropertySignature + name: Identifier, + type: string | number | TypeReference + }[] + } +} + +// TypeReference +{ + kind: TypeReference + typename: Identifier +} +``` + +## Type Alias: An object type with type literals, identifiers, and other object types + +```ts +type D = { + a: string; + b: number; + c: A; + d: B; + e: C; + f: { + foo: string; + bar: number; + }; +}; +``` + +AST: + +```ts +// TypeAlias +{ + kind: TypeAlias, + name: Identifier, + type: { + kind: TypeLiteral, + members: { + kind: PropertySignature + name: Identifier, + type: string | number | TypeReference | TypeLiteral + }[] + } +} + +// TypeReference +{ + kind: TypeReference + typename: Identifier +} + +// TypeLiteral +{ + kind: TypeLiteral, + members: { + kind: PropertySignature + name: Identifier, + type: string | number | TypeReference | TypeLiteral + }[] +} +``` + +## Object Literals + +```ts +type A = string; +type B = number; +type C = { + a: string; + b: number; +}; + +type D = { + a: string; + b: number; + c: A; + d: B; + e: C; + f: { + foo: string; + bar: number; + }; +}; + +var a: A = 'string'; +var b: B = 11; +var c: C = { + a: a, + b: b, +}; + +var d: D = { + a: a, + b: b, + c: a, + d: b, + e: c, + f: { + foo: 'string', + bar: 10, + }, +}; +``` + +It produces this AST + +```ts +{ + "statements": [ + { + "kind": "TypeAlias", + "name": { + "kind": "Identifier", + "text": "A" + }, + "typename": { + "kind": "Identifier", + "text": "string" + } + }, + { + "kind": "TypeAlias", + "name": { + "kind": "Identifier", + "text": "B" + }, + "typename": { + "kind": "Identifier", + "text": "number" + } + }, + { + "kind": "TypeAlias", + "name": { + "kind": "Identifier", + "text": "C" + }, + "typename": { + "kind": "TypeLiteral", + "members": { + "0": { + "name": { + "kind": "Identifier", + "text": "a" + }, + "typename": { + "kind": "Identifier", + "text": "string" + } + }, + "1": { + "name": { + "kind": "Identifier", + "text": "b" + }, + "typename": { + "kind": "Identifier", + "text": "number" + } + } + } + } + }, + { + "kind": "TypeAlias", + "name": { + "kind": "Identifier", + "text": "D" + }, + "typename": { + "kind": "TypeLiteral", + "members": { + "0": { + "name": { + "kind": "Identifier", + "text": "a" + }, + "typename": { + "kind": "Identifier", + "text": "string" + } + }, + "1": { + "name": { + "kind": "Identifier", + "text": "b" + }, + "typename": { + "kind": "Identifier", + "text": "number" + } + }, + "2": { + "name": { + "kind": "Identifier", + "text": "c" + }, + "typename": { + "kind": "Identifier", + "text": "A" + } + }, + "3": { + "name": { + "kind": "Identifier", + "text": "d" + }, + "typename": { + "kind": "Identifier", + "text": "B" + } + }, + "4": { + "name": { + "kind": "Identifier", + "text": "e" + }, + "typename": { + "kind": "Identifier", + "text": "C" + } + }, + "5": { + "name": { + "kind": "Identifier", + "text": "f" + }, + "typename": { + "kind": "TypeLiteral", + "members": { + "0": { + "name": { + "kind": "Identifier", + "text": "foo" + }, + "typename": { + "kind": "Identifier", + "text": "string" + } + }, + "1": { + "name": { + "kind": "Identifier", + "text": "bar" + }, + "typename": { + "kind": "Identifier", + "text": "number" + } + } + } + } + } + } + } + }, + { + "kind": "VariableStatement", + "declarationList": { + "kind": "VariableDeclarationList", + "declarations": { + "0": { + "kind": "VariableDeclaration", + "name": { + "kind": "Identifier", + "text": "a" + }, + "typename": { + "kind": "Identifier", + "text": "A" + }, + "init": { + "kind": "StringLiteral", + "value": "string", + "isSingleQuote": true + } + } + }, + "flags": 0 + } + }, + { + "kind": "VariableStatement", + "declarationList": { + "kind": "VariableDeclarationList", + "declarations": { + "0": { + "kind": "VariableDeclaration", + "name": { + "kind": "Identifier", + "text": "b" + }, + "typename": { + "kind": "Identifier", + "text": "B" + }, + "init": { + "kind": "NumericLiteral", + "value": 11 + } + } + }, + "flags": 0 + } + }, + { + "kind": "VariableStatement", + "declarationList": { + "kind": "VariableDeclarationList", + "declarations": { + "0": { + "kind": "VariableDeclaration", + "name": { + "kind": "Identifier", + "text": "c" + }, + "typename": { + "kind": "Identifier", + "text": "C" + }, + "init": { + "kind": "ObjectLiteralExpression", + "properties": { + "0": { + "name": { + "kind": "Identifier", + "text": "a" + }, + "init": { + "kind": "Identifier", + "text": "a" + } + }, + "1": { + "name": { + "kind": "Identifier", + "text": "b" + }, + "init": { + "kind": "Identifier", + "text": "b" + } + } + } + } + } + }, + "flags": 0 + } + }, + { + "kind": "VariableStatement", + "declarationList": { + "kind": "VariableDeclarationList", + "declarations": { + "0": { + "kind": "VariableDeclaration", + "name": { + "kind": "Identifier", + "text": "d" + }, + "typename": { + "kind": "Identifier", + "text": "D" + }, + "init": { + "kind": "ObjectLiteralExpression", + "properties": { + "0": { + "name": { + "kind": "Identifier", + "text": "a" + }, + "init": { + "kind": "Identifier", + "text": "a" + } + }, + "1": { + "name": { + "kind": "Identifier", + "text": "b" + }, + "init": { + "kind": "Identifier", + "text": "b" + } + }, + "2": { + "name": { + "kind": "Identifier", + "text": "c" + }, + "init": { + "kind": "Identifier", + "text": "a" + } + }, + "3": { + "name": { + "kind": "Identifier", + "text": "d" + }, + "init": { + "kind": "Identifier", + "text": "b" + } + }, + "4": { + "name": { + "kind": "Identifier", + "text": "e" + }, + "init": { + "kind": "Identifier", + "text": "c" + } + }, + "5": { + "name": { + "kind": "Identifier", + "text": "f" + }, + "init": { + "kind": "ObjectLiteralExpression", + "properties": { + "0": { + "name": { + "kind": "Identifier", + "text": "foo" + }, + "init": { + "kind": "StringLiteral", + "value": "string", + "isSingleQuote": true + } + }, + "1": { + "name": { + "kind": "Identifier", + "text": "bar" + }, + "init": { + "kind": "NumericLiteral", + "value": 10 + } + } + } + } + } + } + } + } + }, + "flags": 0 + } + } + ] +} +``` + +## Type Checking + +Two way type checking: + +1. going from the value and checking the type definition + easier to find if the object literal has attributes that are not in the type definition +2. going from the type definition and checking the value properties + easier to find what's missing in the object literal to match the type definition + +Check phases + +1. Any property type mismatch should generate this error. If they don't share the same type. e.g. `number` and `object` + +- [x] Generate error + +``` +Type {X} is not assignable to {Y} +``` + +2. Go through the list of properties. For each one, check if it's defined in the type definition. If it's not, it should generate the type error. This error only appears if rule number one passes the checker. + +- [x] Generate error +- [ ] Generate error only if there're no rule number one errors + +``` +Object literal may only specify known properties, and 'g' does not exist in type 'D'. +``` + +3. Every property was right but it's missing some from the type definition + +``` +Type '{ a: string; b: number; }' is missing the following properties from type 'D': c, d, e, f +``` + +### Object type structure + +```ts +type ObjecType = { + flags: ObjectFlag; + members: Record; +}; +``` + +A module can have many different object types: + +```ts +type ObjectTypes = { + [key: string]: ObjectType; +}; +``` + +So we can use it like: + +```ts +const objectsTypes: ObjectTypes = { + A: { + flags: ObjectFlag, + members: { + a: number, + }, + }, +}; + +objectTypes.get('A').members.get('a'); +``` diff --git a/package.json b/package.json index 1a30754..6ef0302 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "test:file": "tsc && node built/testFile.js", "test:parser": "ts-node ./src/testParser", "test:binder": "ts-node ./src/testBinder", + "test:checker": "ts-node ./src/testChecker", "accept": "mv baselines/local/* baselines/reference/", "mtsc": "node built/index.js" }, diff --git a/src/check.ts b/src/check.ts index afb14e8..3b37ba7 100644 --- a/src/check.ts +++ b/src/check.ts @@ -9,21 +9,36 @@ import { VariableDeclaration, SymbolFlags, Symbol, + TypeLiteral, + TypeFlags, + Member, + PropertySignature, + TypeTable, + PropertyAssignment, } from './types'; import { error } from './error'; import { resolve } from './bind'; -const stringType: Type = { id: 'string' }; -const numberType: Type = { id: 'number' }; -const errorType: Type = { id: 'error' }; -const empty: Type = { id: 'empty' }; -const anyType: Type = { id: 'any' }; +const stringType: Type = { id: 'string', flags: TypeFlags.Any }; +const numberType: Type = { id: 'number', flags: TypeFlags.NumericLiteral }; +const errorType: Type = { id: 'error', flags: TypeFlags.Any }; +const empty: Type = { id: 'empty', flags: TypeFlags.Any }; +const anyType: Type = { id: 'any', flags: TypeFlags.Any }; + +function createObjectType(members: TypeTable): Type { + return { + id: 'object', + flags: TypeFlags.Object, + members, + }; +} function typeToString(type: Type) { return type.id; } export function check(module: Module) { + const objectTypes = new Map(); return module.statements.map(checkStatement); function checkStatement(statement: Statement): Type { @@ -31,7 +46,7 @@ export function check(module: Module) { case Node.ExpressionStatement: return checkExpression(statement.expr); case Node.TypeAlias: - return checkType(statement.typename); + return checkTypeIdentifierOrObjectType(statement); case Node.VariableStatement: statement.declarationList.declarations.forEach( checkVariableDeclaration, @@ -79,6 +94,8 @@ export function check(module: Module) { )}' to variable of type '${typeToString(t)}'.`, ); return t; + case Node.ObjectLiteralExpression: + return createObjectType(checkPropertyTypes(expression.properties)); } } @@ -92,15 +109,16 @@ export function check(module: Module) { function checkVariableDeclaration(declaration: VariableDeclaration) { const initType = checkExpression(declaration.init); - const symbol = resolve( + const varSymbol = resolve( module.locals, declaration.name.text, SymbolFlags.FunctionScopedVariable, ); - if (symbol && declaration !== symbol.valueDeclaration) { + // handle subsequent variable declarations types — generate an error if it has type mismatches + if (varSymbol && declaration !== varSymbol.valueDeclaration) { const valueDeclarationType = checkVariableDeclarationType( - symbol.valueDeclaration!, + varSymbol.valueDeclaration!, ); const type = declaration.typename @@ -119,16 +137,62 @@ export function check(module: Module) { } const type = checkType(declaration.typename); - if (type !== initType && type !== errorType) + + handleUnassignableTypes(declaration, initType, type); + + if (initType.id === 'object') { + // Handle property type mismatch and only known property errors + handlePropertyTypeMismatch(declaration, initType, type); + // Handle missing properties error + + return type; + } + + if (type !== initType && type !== errorType) { error( declaration.init.pos, `Cannot assign initialiser of type '${typeToString( initType, )}' to variable with declared type '${typeToString(type)}'.`, ); + } + return type; } + function handlePropertyTypeMismatch( + declaration: VariableDeclaration, + initType: Type, + type: Type, + ) { + let hasUnassignablePropertyTypes = false; + let undefinedPropertyName; + + for (const [propertyName, propertyType] of initType.members as TypeTable) { + const typePropertyType = type.members?.get(propertyName); + + if (typePropertyType) { + hasUnassignablePropertyTypes ||= handleUnassignablePropertyTypes( + declaration, + propertyType, + typePropertyType, + propertyName, + ); + } else { + undefinedPropertyName ||= propertyName; + } + } + + if (!hasUnassignablePropertyTypes && undefinedPropertyName) { + error( + declaration.init.pos, + `Object literal may only specify known properties, and '${undefinedPropertyName}' does not exist in type '${declaration.typename?.text}'.`, + ); + } + + return hasUnassignablePropertyTypes; + } + function checkVariableDeclarationType(declaration: VariableDeclaration) { return declaration.typename ? checkType(declaration.typename) @@ -144,12 +208,10 @@ export function check(module: Module) { default: const symbol = resolve(module.locals, name.text, SymbolFlags.Type); if (symbol) { - return checkType( - ( - symbol.declarations.find( - (d) => d.kind === Node.TypeAlias, - ) as TypeAlias - ).typename, + return checkTypeIdentifierOrObjectType( + symbol.declarations.find( + (d) => d.kind === Node.TypeAlias, + ) as TypeAlias, ); } error(name.pos, 'Could not resolve type ' + name.text); @@ -157,6 +219,93 @@ export function check(module: Module) { } } + function checkObjecType(statement: TypeAlias | PropertySignature) { + objectTypes.set( + statement.name.text, + createObjectType( + checkMemberTypes((statement.typename as TypeLiteral).members), + ), + ); + + return objectTypes.get(statement.name.text) as Type; + } + + function checkMemberTypes(members: Member[]) { + const membersTable = new Map(); + members.forEach((member) => + membersTable.set( + member.name.text, + checkTypeIdentifierOrObjectType(member), + ), + ); + return membersTable; + } + + function checkPropertyTypes(properties: PropertyAssignment[]) { + const membersTable = new Map(); + properties.forEach((property) => + membersTable.set( + 'text' in property.name + ? property.name.text + : property.name.value.toString(), + checkTypeIdentifierOrObjectType(property), + ), + ); + return membersTable; + } + + function checkTypeIdentifierOrObjectType( + statement: TypeAlias | PropertySignature | PropertyAssignment, + ) { + return 'typename' in statement + ? statement.typename.kind === Node.TypeLiteral + ? checkObjecType(statement) + : checkType(statement.typename) + : checkExpression(statement.init); + } + + function handleUnassignableTypes( + declaration: VariableDeclaration, + initType: Type, + type: Type, + ) { + if (initType.id !== type.id) { + error( + declaration.init.pos, + `Type '${typeToString( + initType, + )}' is not assignable to type '${typeToString(type)}'.`, + ); + } + } + + function handleUnassignablePropertyTypes( + declaration: VariableDeclaration, + initType: Type, + type: Type, + propertyName: string, + ): boolean { + if (initType.id !== type.id) { + error( + declaration.init.pos, + `Type '${typeToString( + initType, + )}' is not assignable to type '${typeToString( + type, + )}'. The expected type comes from property '${propertyName}' which is declared here on type '${ + declaration.typename?.text + }'`, + ); + return true; + } + + if (initType.id === type.id && initType.id === 'object') { + return handlePropertyTypeMismatch(declaration, initType, type); + } + + return false; + } + function handleSubsequentVariableDeclarationsTypes( declaration: VariableDeclaration, valueDeclarationType: Type, diff --git a/src/lex.ts b/src/lex.ts index b091099..000905f 100644 --- a/src/lex.ts +++ b/src/lex.ts @@ -59,6 +59,12 @@ export function lex(s: string): Lexer { case ',': token = Token.Comma; break; + case '{': + token = Token.OpenBrace; + break; + case '}': + token = Token.CloseBrace; + break; default: token = Token.Unknown; break; diff --git a/src/parse.ts b/src/parse.ts index e951cc3..ffee4b3 100644 --- a/src/parse.ts +++ b/src/parse.ts @@ -9,6 +9,11 @@ import { VariableDeclaration, NodeFlags, VariableStatement, + TypeLiteral, + Member, + ObjectLiteralExpression, + PropertyAssignment, + IdentifierOrLiteral, } from './types'; import { error } from './error'; @@ -18,7 +23,7 @@ export function parse(lexer: Lexer): Module { function parseModule(): Module { return { - statements: parseStatements( + statements: parseList( parseStatement, () => tryParseToken(Token.Semicolon), () => lexer.token() !== Token.EOF, @@ -36,7 +41,7 @@ export function parse(lexer: Lexer): Module { return e; } - function parseIdentifierOrLiteral(): Expression { + function parseIdentifierOrLiteral(): IdentifierOrLiteral { const pos = lexer.pos(); if (tryParseToken(Token.Identifier)) { return { kind: Node.Identifier, text: lexer.text(), pos }; @@ -49,6 +54,8 @@ export function parse(lexer: Lexer): Module { pos, isSingleQuote: lexer.isSingleQuote(), }; + } else if (tryParseToken(Token.OpenBrace)) { + return parseObjectLiteral(); } error( pos, @@ -67,6 +74,59 @@ export function parse(lexer: Lexer): Module { return { kind: Node.Identifier, text: '(missing)', pos: e.pos }; } + function parseProperty(): PropertyAssignment { + const pos = lexer.pos(); + const name = parseIdentifier(); + parseExpected(Token.Colon); + const init = parseIdentifierOrLiteral(); + + return { + name, + init, + pos, + }; + } + + function parseObjectLiteral(): ObjectLiteralExpression { + return { + kind: Node.ObjectLiteralExpression, + properties: parseList( + parseProperty, + () => tryParseToken(Token.Comma), + () => !tryParseToken(Token.CloseBrace), + ), + pos: lexer.pos(), + }; + } + + function parseMember(): Member { + const pos = lexer.pos(); + const name = parseIdentifier(); + parseExpected(Token.Colon); + const typename = tryParseToken(Token.OpenBrace) + ? parseTypeLiteral() + : parseIdentifier(); + + return { + kind: Node.PropertySignature, + name, + typename, + pos, + }; + } + + function parseTypeLiteral(): TypeLiteral { + return { + kind: Node.TypeLiteral, + members: parseList( + parseMember, + () => tryParseToken(Token.Semicolon), + () => !tryParseToken(Token.CloseBrace), + ), + pos: lexer.pos(), + }; + } + function parseStatement(): Statement { const pos = lexer.pos(); @@ -77,7 +137,9 @@ export function parse(lexer: Lexer): Module { } else if (tryParseToken(Token.Type)) { const name = parseIdentifier(); parseExpected(Token.Equals); - const typename = parseIdentifier(); + const typename = tryParseToken(Token.OpenBrace) + ? parseTypeLiteral() + : parseIdentifier(); return { kind: Node.TypeAlias, name, typename, pos }; } else if (tryParseToken(Token.Semicolon)) { return { kind: Node.EmptyStatement }; @@ -138,7 +200,7 @@ export function parse(lexer: Lexer): Module { } } - function parseStatements( + function parseList( element: () => T, terminator: () => boolean, peek: () => boolean, diff --git a/src/testChecker.ts b/src/testChecker.ts new file mode 100644 index 0000000..5096454 --- /dev/null +++ b/src/testChecker.ts @@ -0,0 +1,23 @@ +import * as fs from 'fs'; +import { parse } from './parse'; +import { lex } from './lex'; +import { bind } from './bind'; +import { check } from './check'; +import { errors } from './error'; + +const args = process.argv.slice(2); +const file = args[0]; + +if (!file) { + console.log('Missing test file'); + process.exit(); +} + +const tree = parse(lex(fs.readFileSync('tests/' + file, 'utf8'))); + +bind(tree); +check(tree); + +for (let [key, value] of errors.entries()) { + console.log(key, value); +} diff --git a/src/types.ts b/src/types.ts index 45a3b14..f246939 100644 --- a/src/types.ts +++ b/src/types.ts @@ -13,6 +13,8 @@ export enum Token { Comma = 'Comma', Whitespace = 'Whitespace', String = 'String', + OpenBrace = 'OpenBrace', + CloseBrace = 'CloseBrace', Unknown = 'Unknown', BOF = 'BOF', EOF = 'EOF', @@ -34,11 +36,14 @@ export enum Node { Var, Let, TypeAlias, + TypeLiteral, StringLiteral, EmptyStatement, VariableStatement, VariableDeclarationList, VariableDeclaration, + PropertySignature, + ObjectLiteralExpression, } export type Error = { @@ -54,7 +59,14 @@ export type Expression = | Identifier | NumericLiteral | Assignment - | StringLiteral; + | StringLiteral + | ObjectLiteralExpression; + +export type IdentifierOrLiteral = + | Identifier + | StringLiteral + | NumericLiteral + | ObjectLiteralExpression; export type Identifier = Location & { kind: Node.Identifier; @@ -78,6 +90,16 @@ export type Assignment = Location & { value: Expression; }; +export type PropertyAssignment = Location & { + name: Identifier | StringLiteral | NumericLiteral; + init: IdentifierOrLiteral; +}; + +export type ObjectLiteralExpression = Location & { + kind: Node.ObjectLiteralExpression; + properties: PropertyAssignment[]; +}; + export type Statement = | ExpressionStatement | TypeAlias @@ -110,7 +132,21 @@ export type VariableDeclaration = Location & { export type TypeAlias = Location & { kind: Node.TypeAlias; name: Identifier; - typename: Identifier; + typename: Identifier | TypeLiteral; +}; + +export type PropertySignature = Location & { + kind: Node.PropertySignature; + name: Identifier; + typename: Identifier | TypeLiteral; +}; + +// Added it to member as it's possible to have other types of members. e.g. method signature +export type Member = PropertySignature; + +export type TypeLiteral = Location & { + kind: Node.TypeLiteral; + members: Member[]; }; export type EmptyStatement = { @@ -132,7 +168,13 @@ export type Module = { statements: Statement[]; }; -export type Type = { id: string }; +export type TypeTable = Map; + +export type Type = { + id: string; + flags: TypeFlags; + members?: TypeTable; +}; export enum CharCodes { b = 98, @@ -161,3 +203,10 @@ export const enum NodeFlags { None = 0, Let = 1 << 0, } + +export const enum TypeFlags { + Any = 1 << 0, + StringLiteral = 1 << 1, + NumericLiteral = 1 << 2, + Object = 1 << 3, +} diff --git a/tests/objectLiteral.ts b/tests/objectLiteral.ts new file mode 100644 index 0000000..a414e1c --- /dev/null +++ b/tests/objectLiteral.ts @@ -0,0 +1,37 @@ +type A = string; +type B = number; +type C = { + a: string; + b: number; +}; + +type D = { + a: string; + b: number; + c: A; + d: B; + e: C; + f: { + foo: string; + bar: number; + }; +}; + +var a: A = 'string'; +var b: B = 10; +var c: C = { + a: a, + b: b +}; + +var d: D = { + a: a, + b: b, + c: a, + d: b, + e: c, + f: { + foo: 'string', + bar: 10 + } +}; \ No newline at end of file diff --git a/tests/objectTypeAlias.ts b/tests/objectTypeAlias.ts new file mode 100644 index 0000000..ac3de44 --- /dev/null +++ b/tests/objectTypeAlias.ts @@ -0,0 +1,18 @@ +type A = string; +type B = number; +type C = { + a: string; + b: number; +}; + +type D = { + a: string; + b: number; + c: A; + d: B; + e: C; + f: { + foo: string; + bar: number; + }; +}; \ No newline at end of file diff --git a/tests/unassignableObjectLiteral.ts b/tests/unassignableObjectLiteral.ts new file mode 100644 index 0000000..c67090d --- /dev/null +++ b/tests/unassignableObjectLiteral.ts @@ -0,0 +1,59 @@ +type A = string; +type B = number; +type C = { + a: string; + b: number; +}; + +type D = { + a: string; + b: number; + c: A; + d: B; + e: C; + f: { + foo: string; + bar: number; + }; +}; + +var a: D = 'string'; +var b: D = 1; +var c: D = { + a: 1 +} + +var d: D = { + f: { + foo: 123 + } +} + +type E = { + a: { + foo: { + bar: number + } + } +} + +var e: E = { + a: { + foo: { + bar: '123' + } + } +} + +type F = { + a: number +} + +var f: F = { + b: 123 +} + +var g: F = { + a: '123', + b: 123 +}