From 44eb9edd301ea1e452c4249e7a4e74e74beb63e7 Mon Sep 17 00:00:00 2001 From: Kris Avi Date: Mon, 18 Nov 2019 14:42:57 +0100 Subject: [PATCH 1/2] Type hinting --- server/src/models/token.ts | 7 + server/src/models/tokentypes.ts | 1 + server/src/parser/models/declare.ts | 18 ++- server/src/parser/parser.ts | 28 +++- server/src/parser/types.ts | 10 +- server/src/scanner/scanner.ts | 84 ++++++++++- server/src/scanner/types.ts | 8 +- server/src/services/analysisService.ts | 6 +- server/src/typeChecker/ksTypes/typeHint.ts | 160 +++++++++++++++++++++ server/src/typeChecker/typeChecker.ts | 108 ++++++++++++-- 10 files changed, 401 insertions(+), 29 deletions(-) create mode 100644 server/src/typeChecker/ksTypes/typeHint.ts diff --git a/server/src/models/token.ts b/server/src/models/token.ts index 2b9c1188..337b8818 100644 --- a/server/src/models/token.ts +++ b/server/src/models/token.ts @@ -14,6 +14,8 @@ export class Token implements TokenBase { public readonly end: Position; public readonly uri: string; + public ref: Maybe; + /** * What symbol tracker is this token tied too */ @@ -32,6 +34,7 @@ export class Token implements TokenBase { this.start = start; this.end = end; this.uri = uri; + this.ref = undefined; this.tracker = undefined; } @@ -57,6 +60,10 @@ export class Token implements TokenBase { }; } + public get getRef(): Token | undefined { + return this.ref; + } + /** * Convert the token to a human readable string */ diff --git a/server/src/models/tokentypes.ts b/server/src/models/tokentypes.ts index bfc9e4d8..eacc447c 100644 --- a/server/src/models/tokentypes.ts +++ b/server/src/models/tokentypes.ts @@ -2,6 +2,7 @@ export enum TokenType { // whitespace whiteSpace, commentLine, region, endRegion, + type, returns, plus, minus, multi, div, power, not, and, or, true, false, diff --git a/server/src/parser/models/declare.ts b/server/src/parser/models/declare.ts index 83b0181a..624d58a9 100644 --- a/server/src/parser/models/declare.ts +++ b/server/src/parser/models/declare.ts @@ -1,5 +1,5 @@ import { Stmt, Block } from './stmt'; -import { IExpr, IStmtVisitor, ScopeKind, NodeDataBuilder } from '../types'; +import { ITypeHint, IExpr, IStmtVisitor, ScopeKind, NodeDataBuilder } from '../types'; import { TokenType } from '../../models/tokentypes'; import { empty, unWrap } from '../../utilities/typeGuards'; import { Range, Position } from 'vscode-languageserver'; @@ -103,6 +103,7 @@ export class Var extends Decl { public readonly toIs: Token; public readonly value: IExpr; public readonly scope: Scope; + public readonly typeHint?: ITypeHint; constructor(builder: NodeDataBuilder) { super(); @@ -110,6 +111,7 @@ export class Var extends Decl { this.toIs = unWrap(builder.toIs); this.value = unWrap(builder.value); this.scope = unWrap(builder.scope); + this.typeHint = builder.typeHint; } public toLines(): string[] { @@ -148,6 +150,7 @@ export class Lock extends Decl { public readonly to: Token; public readonly value: IExpr; public readonly scope?: Scope; + public readonly typeHint?: ITypeHint; constructor(builder: NodeDataBuilder) { super(); @@ -156,6 +159,7 @@ export class Lock extends Decl { this.to = unWrap(builder.to); this.value = unWrap(builder.value); this.scope = builder.scope; + this.typeHint = builder.typeHint; } public toLines(): string[] { @@ -205,6 +209,7 @@ export class Func extends Decl { public readonly identifier: Token; public readonly block: Block; public readonly scope?: Scope; + public readonly typeHint?: ITypeHint; constructor(builder: NodeDataBuilder) { super(); @@ -212,6 +217,7 @@ export class Func extends Decl { this.identifier = unWrap(builder.identifier); this.block = unWrap(builder.block); this.scope = builder.scope; + this.typeHint = builder.typeHint; } public toLines(): string[] { @@ -254,6 +260,7 @@ export class Param extends Decl { public readonly requiredParameters: Parameter[]; public readonly optionalParameters: DefaultParam[]; public readonly scope?: Scope; + public readonly typeHint?: ITypeHint; constructor(builder: NodeDataBuilder) { super(); @@ -261,6 +268,7 @@ export class Param extends Decl { this.requiredParameters = unWrap(builder.requiredParameters); this.optionalParameters = unWrap(builder.optionalParameters); this.scope = builder.scope; + this.typeHint = builder.typeHint; } public toLines(): string[] { @@ -330,7 +338,10 @@ export class Param extends Decl { } export class Parameter extends NodeBase { - constructor(public readonly identifier: Token) { + constructor( + public readonly identifier: Token, + public readonly typeHint?: Maybe, + ) { super(); } @@ -360,8 +371,9 @@ export class DefaultParam extends Parameter { identifier: Token, public readonly toIs: Token, public readonly value: IExpr, + typeHint?: Maybe, ) { - super(identifier); + super(identifier, typeHint); } public toLines(): string[] { diff --git a/server/src/parser/parser.ts b/server/src/parser/parser.ts index 5539c256..d391341b 100644 --- a/server/src/parser/parser.ts +++ b/server/src/parser/parser.ts @@ -2,6 +2,7 @@ import { TokenType, isValidIdentifier } from '../models/tokentypes'; import { IParseError, IExpr, + ITypeHint, IStmt, INodeResult, RunStmtType, @@ -42,6 +43,7 @@ export class Parser { private tokens: Token[]; private current: number; private runStmts: RunStmtType[]; + private types: Token[]; private readonly andBind = this.and.bind(this); private readonly eqaulityBind = this.equality.bind(this); @@ -57,6 +59,7 @@ export class Parser { tokens: Token[], logger: ILogger = mockLogger, tracer: ITracer = mockTracer, + types: Token[] = [], ) { this.uri = uri; this.tokens = tokens.concat(this.eof(tokens)); @@ -64,6 +67,7 @@ export class Parser { this.runStmts = []; this.logger = logger; this.tracer = tracer; + this.types = types; } // parse tokens @@ -75,6 +79,9 @@ export class Parser { this.logger.info( `Parsing started for ${file} with ${this.tokens.length} tokens.`, ); + this.logger.info( + `Parsing started for ${file} with ${this.types.length} tokens.`, + ); const statements: Stmt.Stmt[] = []; const parseDiagnostics: Diagnostic[] = []; @@ -242,9 +249,10 @@ export class Parser { } // parse function declaration - private declareFunction(scope?: Decl.Scope): INodeResult { + private declareFunction(scope?: Decl.Scope, typeHint?: Maybe): INodeResult { const builder: NodeDataBuilder = { scope, + typeHint, functionToken: undefined, identifier: undefined, block: undefined, @@ -261,6 +269,8 @@ export class Parser { if (this.matchToken(TokenType.curlyOpen)) { const block = this.block(); builder.block = block.value; + + builder.typeHint = this.types.find(x => x.ref === builder.identifier)?.literal; this.matchToken(TokenType.period); return nodeResult(new Decl.Func(builder), block.errors); @@ -308,7 +318,8 @@ export class Parser { Decl.Parameter, ); - parameters.push(new Decl.Parameter(identifer)); + const typeHint = this.types.find(x => x.ref === identifer); + parameters.push(new Decl.Parameter(identifer, typeHint?.literal)); } while (this.matchToken(TokenType.comma)); return parameters; @@ -333,8 +344,9 @@ export class Parser { [TokenType.to, TokenType.is], ); const valueResult = this.expression(); + const typeHint = this.types.find(x => x.ref === identifer); defaultParameters.push( - new Decl.DefaultParam(identifer, toIs, valueResult.value), + new Decl.DefaultParam(identifer, toIs, valueResult.value, typeHint?.literal), ); errors.push(valueResult.errors); } while (this.matchToken(TokenType.comma)); @@ -343,13 +355,14 @@ export class Parser { } // parse lock statement - private declareLock(scope?: Decl.Scope): INodeResult { + private declareLock(scope?: Decl.Scope, typeHint?: Maybe): INodeResult { const builder: NodeDataBuilder = { scope, lock: undefined, identifier: undefined, value: undefined, to: undefined, + typeHint, }; builder.lock = this.previous(); @@ -366,18 +379,21 @@ export class Parser { ); const valueResult = this.expression(); builder.value = valueResult.value; + + builder.typeHint = this.types.find(x => x.ref === builder.identifier)?.literal; this.terminal(Decl.Lock, builder); return nodeResult(new Decl.Lock(builder), valueResult.errors); } // parse a variable declaration, scoping occurs elsewhere - private declareVariable(scope: Decl.Scope): INodeResult { + private declareVariable(scope: Decl.Scope, typeHint?: Maybe): INodeResult { const builder: NodeDataBuilder = { scope, identifier: undefined, value: undefined, toIs: undefined, + typeHint, }; builder.identifier = this.consumeIdentifierThrow( @@ -395,6 +411,8 @@ export class Parser { const valueResult = this.expression(); builder.value = valueResult.value; + builder.typeHint = this.types.find(x => x.ref === builder.identifier)?.literal; + this.terminal(Decl.Var, builder); return nodeResult(new Decl.Var(builder), valueResult.errors); } diff --git a/server/src/parser/types.ts b/server/src/parser/types.ts index 6b917107..ffca2274 100644 --- a/server/src/parser/types.ts +++ b/server/src/parser/types.ts @@ -18,11 +18,16 @@ export interface IDeclScope extends RangeSequence { toString(): string; } +export interface ITypeHint extends RangeSequence { + typeHint: string; + toString(): string; +} + export type NodeDataBuilder = Partial>>; export type NodeData = Properties< T, - Token | NodeBase | NodeBase[] | undefined + Token | NodeBase | NodeBase[] | ITypeHint | undefined >; export type SuffixTermTrailer = @@ -191,7 +196,8 @@ export type TreeNode = | IExpr | ISuffixTerm | IParameter - | IDeclScope; + | IDeclScope + | ITypeHint; export interface IExprVisitable { accept any>( diff --git a/server/src/scanner/scanner.ts b/server/src/scanner/scanner.ts index 122d7b27..5c2823b9 100644 --- a/server/src/scanner/scanner.ts +++ b/server/src/scanner/scanner.ts @@ -4,6 +4,7 @@ import { ScanResult, ScanKind, TokenResult, + TypeResult, WhitespaceResult, RegionResult, DiagnosticResult, @@ -65,6 +66,11 @@ export class Scanner { */ private readonly tokenResult: TokenResult; + /** + * results for types + */ + private readonly typeResult: TypeResult; + /** * results for whitespace */ @@ -114,6 +120,17 @@ export class Scanner { kind: ScanKind.Token, }; + this.typeResult = { + result: new Token( + TokenType.type, + 'placeholder', + undefined, + { line: 0, character: 0 }, + { line: 0, character: 0 }, + 'placeholder', + ), + kind: ScanKind.Type, + }; this.regionResult = { result: new Token( TokenType.region, @@ -152,6 +169,7 @@ export class Scanner { const tokens: Token[] = []; const scanDiagnostics: Diagnostic[] = []; const regions: Token[] = []; + const types: Token[] = []; const splits = this.uri.split('/'); const file = splits[splits.length - 1]; @@ -177,8 +195,14 @@ export class Scanner { case ScanKind.Region: regions.push(result.result); break; - case ScanKind.Whitespace: + case ScanKind.Type: + // Were not able to reference earlier to lookback + // Gets first identifier on the line where type is defined + result.result.ref = tokens.filter(token => token.type === TokenType.identifier && token.start.line == result.result.start.line)[0]; + types.push(result.result); break; + case ScanKind.Whitespace: + break; } } @@ -189,7 +213,7 @@ export class Scanner { this.logger.warn(`Scanning encounter ${scanDiagnostics.length} errors`); } - return { tokens, scanDiagnostics, regions }; + return { tokens, scanDiagnostics, regions, types}; } catch (err) { this.logger.error('Error occurred in scanner'); logException(this.logger, this.tracer, err, LogLevel.error); @@ -198,6 +222,7 @@ export class Scanner { tokens: [], scanDiagnostics: [], regions: [], + types: [], }; } } @@ -285,7 +310,7 @@ export class Scanner { /** * Extract a comment or a region */ - private comment(): WhitespaceResult | RegionResult { + private comment(): WhitespaceResult | RegionResult | TypeResult { this.advanceWhitespace(); if (this.peek() !== '#') { this.advanceEndOfLine(); @@ -293,13 +318,30 @@ export class Scanner { } this.increment(); - const start = this.current; + let start = this.current; while (this.isAlpha(this.peek())) this.increment(); - const text = this.source.substr(start, this.current - start).toLowerCase(); const region = regions.get(text); - + const ttype = types.get(text); + // TODO: accept #returns: only for function defition and #type: for parameter/lock/variable + if (!empty(ttype)) { + start = this.current; + this.advanceWhitespace(); + while (this.peek() !== ':') { + this.increment(); + } + this.increment(); + this.advanceWhitespace(); + start = this.current; + while (this.isAlphaNumeric(this.peek())) this.increment(); + if (ttype == types.get('returns')) { + this.advanceEndOfLine(); + } + const value = this.source + .substr(start, this.current - start).trim().replace(new RegExp(" ", 'g'), ""); + return this.generateType(ttype.type, value); + } this.advanceEndOfLine(); return empty(region) ? this.whiteSpaceResult @@ -517,6 +559,31 @@ export class Scanner { return this.regionResult; } + + /** + * generate token from provided token type and optional literal + * @param type token type + * @param literal optional literal + * @param toLower should the lexeme be lowered + */ + private generateType(type: TokenType, literal?: any, ref?: any): TypeResult { + const text = this.source.substr(this.start, this.current - this.start); + + const token = new Token( + type, + text, + literal, + this.startPosition.toImmutable(), + this.currentPosition.toImmutable(), + this.uri, + ); + + token.ref = ref; + + this.typeResult.result = token; + return this.typeResult; + } + /** * generate error * @param message error message @@ -802,3 +869,8 @@ const regions: ITokenMap = new Map([ ['region', { type: TokenType.region }], ['endregion', { type: TokenType.endRegion }], ]); + +const types: ITokenMap = new Map([ + ['type', {type: TokenType.type }], + ['returns', {type: TokenType.returns }], +]); diff --git a/server/src/scanner/types.ts b/server/src/scanner/types.ts index 29cae743..a3ce4365 100644 --- a/server/src/scanner/types.ts +++ b/server/src/scanner/types.ts @@ -2,12 +2,13 @@ import { TokenType } from '../models/tokentypes'; import { Diagnostic } from 'vscode-languageserver'; import { Token } from '../models/token'; -export type ITokenMap = Map; +export type ITokenMap = Map; export interface Tokenized { tokens: Token[]; scanDiagnostics: Diagnostic[]; regions: Token[]; + types: Token[]; } export const enum ScanKind { @@ -15,6 +16,7 @@ export const enum ScanKind { Token, Diagnostic, Region, + Type, } export type Result = { @@ -26,9 +28,11 @@ export type TokenResult = Result; export type WhitespaceResult = Result; export type DiagnosticResult = Result; export type RegionResult = Result; +export type TypeResult = Result; export type ScanResult = | TokenResult | WhitespaceResult | DiagnosticResult - | RegionResult; + | RegionResult + | TypeResult; diff --git a/server/src/services/analysisService.ts b/server/src/services/analysisService.ts index 49b9a540..e36bcb2f 100644 --- a/server/src/services/analysisService.ts +++ b/server/src/services/analysisService.ts @@ -450,7 +450,7 @@ export class AnalysisService extends EventEmitter { performance.mark('scanner-start'); const scanner = new Scanner(text, uri, this.logger, this.tracer); - const { tokens, scanDiagnostics, regions } = scanner.scanTokens(); + const { tokens, scanDiagnostics, regions, types } = scanner.scanTokens(); performance.mark('scanner-end'); // if scanner found errors report those immediately @@ -461,7 +461,7 @@ export class AnalysisService extends EventEmitter { } performance.mark('parser-start'); - const parser = new Parser(uri, tokens, this.logger, this.tracer); + const parser = new Parser(uri, tokens, this.logger, this.tracer, types); const { script, parseDiagnostics } = parser.parse(); performance.mark('parser-end'); @@ -606,6 +606,8 @@ export class AnalysisService extends EventEmitter { performance.mark('type-checking-start'); const typeDiagnostics = typeChecker + // TODO: Kris: Get functions on first pass for set statements using functions + // .check(symbolTable) .check() .map(error => addDiagnosticsUri(error, uri)); diff --git a/server/src/typeChecker/ksTypes/typeHint.ts b/server/src/typeChecker/ksTypes/typeHint.ts new file mode 100644 index 00000000..c6d96a36 --- /dev/null +++ b/server/src/typeChecker/ksTypes/typeHint.ts @@ -0,0 +1,160 @@ + +/* import { queueType } from './collections/queue'; +import { listType } from './collections/list'; +import { stackType } from './collections/stack'; +import { uniqueSetType } from './collections/uniqueset'; */ +import { structureType } from './primitives/structure'; +import { nodeType } from './node'; +import { constantType } from './constant'; +import { pathType } from './io/path'; +import { volumeType } from './io/volume'; +import { vectorType } from './collections/vector'; +import { rgbaType } from './rgba'; +import { directionType } from './collections/direction'; +import { kacAlarmType } from './kacAlarmWrapper'; +import { geoCoordinatesType } from './geoCoordinates'; +import { bodyAtmosphereType } from './bodyatmosphere'; +import { noteType } from './note'; +import { voiceType } from './voice'; +import { hsvaType } from './hsva'; +import { vectorRendererType } from './vectorRenderer'; +import { guiWidgetType } from './gui/guiWidget'; +import { orbitableType } from './orbital/orbitable'; +import { timeSpanType } from './timespan'; +import { highlightType } from './highlight'; +import { orbitInfoType } from './orbitInfo'; +import { careerType } from './career'; +import { waypointType } from './waypoint'; +import { resourceTransferType } from './resourceTransfer'; +import { lexiconType } from './collections/lexicon'; +import { rangeType } from './collections/range'; +import { volumeFileType } from './io/volumneFile'; +import { pidLoopType } from './pidLoop'; +import { volumeItemType } from './io/volumeItem'; +import { volumeDirectoryType } from './io/volumeDirectory'; +import { delegateType } from './primitives/delegate'; +import { kUniverseType } from './kUniverse'; +import { homeConnectionType } from './communication/homeConnection'; +import { controlConnectionType } from './communication/controlConnection'; +import { vesselAltType } from './vessel/vesselAlt'; +import { vesselEtaType } from './vessel/vesselEta'; +import { stageType } from './vessel/stage'; +import { steeringManagerType } from './steeringManager'; +import { terminalStructType } from './terminalStruct'; +import { noneType } from './primitives/none'; +import { userListType } from './collections/userList'; +import { + scalarType, + doubleType, + integerType, +} from './primitives/scalar'; +import { stringType } from './primitives/string'; +import { booleanType } from './primitives/boolean'; +import { coreType } from './core'; +import { versionInfoType } from './versionInfo'; +import { configType } from './config'; +import { builtInDelegateType } from './primitives/builtInDelegate'; +import { addonListType } from './addon/addonList'; +import { vesselSensorsType } from './vessel/vesselSensors'; +import { serializableType } from './primitives/serializeableStructure'; +import { bodyTargetType } from './orbital/bodyTarget'; +import { vesselTargetType } from './orbital/vesselTarget'; +import { boundsType } from './parts/bounds'; +import { IType } from '../types'; +import { Type } from '../models/types/type'; +import { partType } from './parts/part'; +import { kosProcessorFieldsType } from './kosProcessorFields'; +import { orbitableVelocityType } from './orbitalVelocity'; +import { aggregateResourceType } from './parts/aggregateResource'; +import { elementType } from './parts/element'; +import { engineType } from './parts/engine'; +import { dockingPortType } from './parts/dockingPort'; +import { scienceExperimentType } from './parts/scienceExperimentModule'; +import { scienceDataType } from './scienceData'; +import { listType } from './collections/list'; +import { partModuleType } from './parts/partModule'; + +type ITypeMap = Map; +export const TypeHint: ITypeMap = new Map([ +// ['', structureType], + ['aggregateResource', aggregateResourceType], + ['element', elementType], + ['kosProcessorFields', kosProcessorFieldsType], + ['vesselSensors', vesselSensorsType], + ['dockingPort', dockingPortType], + ['engine', engineType], + ['path', pathType], + ['structure', structureType], + ['node', nodeType], + ['none', noneType], + ['boolean', booleanType], + ['string', stringType], + ['scalar', scalarType], + ['integer', integerType], + ['double', doubleType], + ['delegate', delegateType], + ['bodyTarget', bodyTargetType], + ['volume', volumeType], + ['volumeItem', volumeItemType], + ['part', partType], + ['alt', vesselAltType], + ['addons', addonListType], + ['rgba', rgbaType], + ['config', configType], + ['constant', constantType], + ['control', controlConnectionType], + ['core', coreType], + ['orbitInfo', orbitInfoType], + ['eta', vesselEtaType], + ['facing', directionType], + ['direction', directionType], + ['geoposition', geoCoordinatesType], + ['homeconnection', homeConnectionType], + ['kuniverse', kUniverseType], + ['vesselTarget', vesselTargetType], + ['vector', vectorType], + ['stage', stageType], + ['steeringmanager', steeringManagerType], + ['terminal', terminalStructType], + ['time', timeSpanType], + ['velocity', orbitableType], + ['orbitable', orbitableType], + ['version', versionInfoType], + ['bounds', boundsType], + ['orbitableVelocity', orbitableVelocityType], + ['serializable', serializableType], + ['builtInDelegate', builtInDelegateType], + ['userList', userListType], + ['pidLoop', pidLoopType], + ['volumeDirectory', volumeDirectoryType], + ['resourceTransfer', resourceTransferType], + ['lexicon', lexiconType], + ['volumeFile', volumeFileType], + ['range', rangeType], + ['kacAlarm', kacAlarmType], + ['bodyAtmosphere', bodyAtmosphereType], + ['waypoint', waypointType], + ['career', careerType], + ['highlight', highlightType], + ['guiWidget', guiWidgetType], + ['vectorRenderer', vectorRendererType], + ['scienceData', scienceDataType], + ['partModule', partModuleType], + ['vesselList', listType.apply(vesselTargetType)], + ['partModuleList', listType.apply(partModuleType)], + ['shipParts', listType.apply(partType)], + ['partList', listType.apply(partType)], + ['scienceExperimentList', listType.apply(scienceExperimentType)], + ['scienceExperiment', scienceExperimentType], + ['voice', voiceType], + ['note', noteType], + ['hsva', hsvaType], + ['NULL', noneType], + ]); + +/* const SpecialHint: [string[], IType][] = [ + ['list', listType], + ['queue', queueType], + ['stack', stackType], + ['uniqueSet', uniqueSetType], + ]; */ \ No newline at end of file diff --git a/server/src/typeChecker/typeChecker.ts b/server/src/typeChecker/typeChecker.ts index 13453729..24d26483 100644 --- a/server/src/typeChecker/typeChecker.ts +++ b/server/src/typeChecker/typeChecker.ts @@ -64,6 +64,10 @@ import { Operator } from './models/types/operator'; import { VariadicType } from './models/types/variadicType'; import { Type } from './models/types/type'; import { CallSignature } from './models/types/callSignature'; +import { TypeHint } from './ksTypes/typeHint'; + +// TODO: KRIS +//import { SymbolTable } from '../analysis/models/symbolTable'; type Diagnostics = Diagnostic[]; @@ -91,6 +95,9 @@ export class TypeChecker */ private readonly script: Script; + // TODO: Kris + //private symbolTable?: SymbolTable; + /** * The cached bound check statement */ @@ -111,6 +118,13 @@ export class TypeChecker */ public check(): Diagnostics { // resolve the sequence of statements + + // TODO: Kris + // public check(symbolTable?: SymbolTable): Diagnostics { + // if (symbolTable) { + // this.symbolTable = symbolTable; + // //console.log(this.symbolTable.rootScope.environment); + // } try { const splits = this.script.uri.split('/'); const file = splits[splits.length - 1]; @@ -140,6 +154,7 @@ export class TypeChecker * @param stmts statements to check */ private checkStmts(stmts: IStmt[]): Diagnostics { + //buildSymbolTable(stmts, this.checkStmtBind); return accumulateErrors(stmts, this.checkStmtBind); } @@ -182,7 +197,11 @@ export class TypeChecker const { tracker } = decl.identifier; if (this.isBasicTracker(tracker)) { - tracker.declareType(result.type.assignmentType()); + if (decl.typeHint && TypeHint.get(decl.typeHint.toString())) { + tracker.declareType(TypeHint.get(decl.typeHint.toString()) || result.type.assignmentType()); + } else { + tracker.declareType(result.type.assignmentType()); + } } return result.errors; @@ -197,7 +216,11 @@ export class TypeChecker const { tracker } = decl.identifier; if (this.isBasicTracker(tracker)) { - tracker.declareType(result.type.assignmentType()); + if (decl.typeHint && TypeHint.get(decl.typeHint.toString())) { + tracker.declareType(TypeHint.get(decl.typeHint.toString()) || result.type.assignmentType()); + } else { + tracker.declareType(result.type.assignmentType()); + } } return result.errors; @@ -211,20 +234,63 @@ export class TypeChecker const errors = this.checkStmt(decl.block); const { tracker } = decl.identifier; + let requiredParameters: Decl.Parameter[] = []; + let optionalParameters: Decl.DefaultParam[] = []; + for (const param of decl.block.stmts.filter(x => x instanceof Decl.Param)) { + const temp = param; + + temp.optionalParameters.forEach(element => { + optionalParameters.push(element); + }); + temp.requiredParameters.forEach(element => { + requiredParameters.push(element); + }); + } + + + // debug code: +/* for (const required of requiredParameters) { + const { tracker } = required.identifier; + + if (this.isBasicTracker(tracker)) { + if (required.typeHint && TypeHint.get(required.typeHint)) { + tracker.declareType(TypeHint.get(required.typeHint) || structureType); + } else { + tracker.declareType(structureType); + } + } + } + for (const optional of optionalParameters) { + const valueResult = this.checkExpr(optional.value); + const { tracker } = optional.identifier; + + if (this.isBasicTracker(tracker)) { + if (optional.typeHint && TypeHint.get(optional.typeHint)) { + tracker.declareType(TypeHint.get(optional.typeHint) || valueResult.type); + } else { + tracker.declareType(valueResult.type); + } + } + } */ + if (!empty(tracker) && tracker.kind === TrackerKind.basic) { const { symbol } = tracker.declared; if (symbol.tag === KsSymbolKind.function) { const paramsTypes: IType[] = []; - + //console.log(symbol.name, decl, requiredParameters, optionalParameters); // TODO eventually we should tag ks parameter to the function type for (let i = 0; i < symbol.requiredParameters; i += 1) { - paramsTypes.push(structureType); + paramsTypes.push(requiredParameters[i].identifier.tracker?.declared.type || structureType); } for (let i = 0; i < symbol.optionalParameters; i += 1) { - paramsTypes.push(createUnion(true, structureType, noneType)); + paramsTypes.push(createUnion(true, optionalParameters[i].identifier.tracker?.declared.type.assignmentType() || structureType, noneType)); } - const returnType = symbol.returnValue ? structureType : noneType; + let returnType: IType = symbol.returnValue ? structureType : noneType; + + if (decl.typeHint && TypeHint.get(decl.typeHint.toString())) { + returnType = TypeHint.get(decl.typeHint.toString()) || returnType; + } const funcType = createFunctionType( tracker.declared.symbol.name.lookup, @@ -236,7 +302,7 @@ export class TypeChecker } } - return errors; + return errors; } /** @@ -251,7 +317,11 @@ export class TypeChecker const { tracker } = required.identifier; if (this.isBasicTracker(tracker)) { - tracker.declareType(structureType); + if (required.typeHint && TypeHint.get(required.typeHint)) { + tracker.declareType(TypeHint.get(required.typeHint) || structureType); + } else { + tracker.declareType(structureType); + } } } @@ -261,7 +331,11 @@ export class TypeChecker const { tracker } = optional.identifier; if (this.isBasicTracker(tracker)) { - tracker.declareType(valueResult.type); + if (optional.typeHint && TypeHint.get(optional.typeHint)) { + tracker.declareType(TypeHint.get(optional.typeHint) || valueResult.type); + } else { + tracker.declareType(valueResult.type); + } } errors = errors.concat(valueResult.errors); @@ -1299,6 +1373,12 @@ export class TypeChecker tracker: Maybe, builder: SuffixTypeBuilder, ): Diagnostics { + // TODO: Kris + // const funcFromSymbol = this.symbolTable?.scopedSymbols(tracker?.declared.range.end || call.end).filter(x => x.name === tracker?.declared.symbol.name)[0]; + // if (funcFromSymbol) { + // console.log(funcFromSymbol.name.tracker?.declared.type); + // console.log(funcFromSymbol.name.tracker?.declared.type.callSignature()) + // } const type = builder.current(); const errors: Diagnostics = []; @@ -1678,6 +1758,7 @@ export class TypeChecker // if we're a trailer check for suffixes if (builder.isTrailer()) { const type = builder.current(); + //console.log(type); const suffix = type .assignmentType() .suffixes() @@ -2028,3 +2109,12 @@ const accumulateErrors = ( return errors; }; +// Code I used for 2 pass system without reporting errors on first try +// const buildSymbolTable = ( +// items: T[], +// checker: (item: T) => Diagnostics, +// ): void => { +// for (const item of items) { +// checker(item); +// } +// }; \ No newline at end of file From 7d2c689936c8cb5d3b3c65bfc9a61ea134a0360c Mon Sep 17 00:00:00 2001 From: Kris Avi Date: Mon, 18 Nov 2019 15:45:16 +0100 Subject: [PATCH 2/2] Fix the test of automatic function signature --- server/test/typeChecking.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/test/typeChecking.spec.ts b/server/test/typeChecking.spec.ts index a94562ac..5d07fb3e 100644 --- a/server/test/typeChecking.spec.ts +++ b/server/test/typeChecking.spec.ts @@ -736,7 +736,7 @@ describe('Statements', () => { const [first, second] = params; expect(first).toBe(structureType); - expect(second).toBe(createUnion(true, structureType, noneType)); + expect(second).toBe(createUnion(true, integerType, noneType)); expect(returns).toBe(structureType); });