diff --git a/lib/builtin.js b/lib/builtin.js index 309fe85..2ee0e1c 100644 --- a/lib/builtin.js +++ b/lib/builtin.js @@ -6,6 +6,43 @@ const Parser = require('./parser'); const fs = require('fs'); const path = require('path'); + +function _function(name, returnType, params) { + return { + 'type': 'function', + 'isStatic': false, + 'isAsync': false, + 'hasThrow': false, + 'functionName': { + 'tag': 2, + 'lexeme': name, + }, + 'params': { + 'type': 'params', + 'params': params + }, + 'returnType': { + 'tag': 8, + 'lexeme': returnType, + }, + 'functionBody': null, + }; +} + +function _param(name, type) { + return { + 'type': 'param', + 'paramName': { + 'tag': 2, + 'lexeme': name + }, + 'paramType': { + 'tag': 8, + 'lexeme': type, + }, + }; +} + function _module(name) { const filePath = path.join(__dirname, '../builtin', `${name}.dara`); const source = fs.readFileSync(filePath, 'utf-8'); @@ -127,4 +164,24 @@ builtin.set('$Map', _module('map')); builtin.set('$Number', _module('number')); +[ + 'string', 'number', 'integer', + 'int8', 'int16', 'int32', 'int64', + 'long', 'ulong', 'uint8', 'uint16', + 'uint32', 'uint64', 'float', 'double', + 'boolean', 'bytes', 'any','object', + 'writable', 'readable' +].map(type => { + const name = `$${type}`; + builtin.set(name, _function(name, type, [_param('data', 'any')])); +}); + +builtin.set('$isNull', _function('$isNull', 'boolean', [_param('data', 'any')])); + +builtin.set('$sleep', _function('$sleep', 'void', [_param('timeLong', 'integer')])); + +builtin.set('$default', _function('$default', 'any', [_param('variable', 'any'), _param('defaultVal', 'any')])); + +builtin.set('$equal', _function('$equal', 'boolean', [_param('data', 'any'), _param('eqlData', 'any')])); + module.exports = builtin; diff --git a/lib/parser.js b/lib/parser.js index b0d3815..02943b4 100644 --- a/lib/parser.js +++ b/lib/parser.js @@ -736,13 +736,9 @@ class Parser extends BaseParser { this.move(); if (this.look.tag === '[') { node.fieldItemType = this.fieldValue(); - } else if (this.look.tag === Tag.TYPE) { + } else if (this.look.tag === Tag.TYPE || this.look.tag === Tag.ID) { const type = this.baseType(); node.fieldItemType = type; - } else if (this.look.tag === Tag.ID) { - const id = this.look; - node.fieldItemType = id; - this.move(); } else if (this.look.tag === '{') { node.fieldItemType = this.modelBody(); } else { diff --git a/lib/semantic.js b/lib/semantic.js index efa7ec8..de75efe 100644 --- a/lib/semantic.js +++ b/lib/semantic.js @@ -90,11 +90,12 @@ function _basic(name) { }; } -function _model(name, moduleName) { +function _model(name, moduleName, extendOn) { return { type: 'model', name: name, - moduleName + moduleName, + extendOn }; } @@ -193,6 +194,22 @@ function isSameType(expect, actual) { return false; } + +function isExtendOn(expect, actual){ + let isExtendOn = false; + if(!actual.extendOn) { + return isExtendOn; + } + actual.extendOn.forEach(base => { + if(isExtendOn) { + return; + } + + isExtendOn = isSameType(expect, base); + }); + return isExtendOn; +} + function isAssignable(expected, actual, expr) { if (isSameType(expected, actual)) { @@ -203,6 +220,17 @@ function isAssignable(expected, actual, expr) { return true; } + if(expected.type === 'model' && actual.type === 'model') { + // $Model vs model + if(expected.name === '$Model') { + return true; + } + + if (isExtendOn(expected, actual)) { + return true; + } + } + // actual is null if (actual.type === 'basic' && actual.name === 'null') { return true; @@ -287,15 +315,24 @@ function eql(expects, actuals) { if (isSameNumber(expect, actual)) { continue; } + // actual is null if (actual.type === 'basic' && actual.name === 'null') { continue; } - // $Model vs model - if (expect.type === 'model' && expect.name === '$Model' && actual.type === 'model') { - continue; + + + if(expect.type === 'model' && actual.type === 'model') { + // $Model vs model + if(expect.name === '$Model') { + continue; + } + + if (isExtendOn(expect, actual)) { + continue; + } } if (expect.type === 'map' && expect.keyType.name === 'string') { @@ -441,6 +478,8 @@ class TypeChecker { this.dependencies = new Map(); // 内部依赖 this.innerDep = new Map(); + // model依赖关系 + this.modelDep = new Map(); //执行编译的root if (!root) { this.root = path.dirname(this.filename); @@ -475,10 +514,18 @@ class TypeChecker { const find = model.modelBody.nodes.find((item) => { return item.fieldName.lexeme === propName; }); - if(find || !model.extendOn) { - return find; + if(find) { + return { + modelField: find, + modelName: model.modelName.lexeme, + }; + } + + if(!model.extendOn) { + return; } let extendModel; + if (model.extendOn.type === 'moduleModel') { const [ main, ...path ] = model.extendOn.path; const checker = this.dependencies.get(main.lexeme); @@ -486,11 +533,17 @@ class TypeChecker { return item.lexeme; }).join('.'); extendModel = checker.models.get(typeName); + } else if (model.extendOn.type === 'subModel') { + let modelName = model.extendOn.path.map((tag) => { + return tag.lexeme; + }).join('.'); + extendModel = this.models.get(modelName); } else if (model.extendOn.idType === 'builtin_model') { extendModel = builtin.get(model.extendOn.lexeme); } else { extendModel = this.models.get(model.extendOn.lexeme); } + return this.findProperty(extendModel, propName); } @@ -510,8 +563,10 @@ class TypeChecker { // 允许先使用,后定义,所以先全部设置到 types 中,再进行检查 models.forEach((node) => { + const name = node.type === 'model' ? node.modelName : node.exceptionName; if(node.extendOn) { this.checkType(node.extendOn); + this.modelDep.set(name.lexeme, node.extendOn); } if(node.type === 'model') { this.visitModel(node); @@ -750,6 +805,52 @@ class TypeChecker { return this; } + getExtendOn(ast, moduleName) { + let extendOn = []; + let extendModel, name; + if (ast.type === 'moduleModel') { + const [ main, ...path ] = ast.path; + moduleName = main.lexeme; + const checker = this.dependencies.get(moduleName); + name = path.map((item) => { + return item.lexeme; + }).join('.'); + extendModel = checker.models.get(name); + } else if (ast.type === 'subModel') { + name = ast.path.map((tag) => { + return tag.lexeme; + }).join('.'); + extendModel = this.models.get(name); + }else if (ast.idType === 'builtin_model') { + name = ast.lexeme; + extendModel = builtin.get(name); + } else { + name = ast.lexeme; + extendModel = this.models.get(name); + } + + if(!extendModel) { + return extendOn; + } + extendOn.push(_model(name, moduleName)); + + if(extendModel.extendOn) { + const checker = moduleName ? this.dependencies.get(moduleName) : this; + const ast = checker.modelDep.get(extendModel.modelName.lexeme); + extendOn = extendOn.concat(checker.getExtendOn(ast, moduleName)); + } + return extendOn; + } + + getModel(name, moduleName) { + if(!this.modelDep.has(name)) { + return _model(name, moduleName); + } + + const ast = this.modelDep.get(name); + return _model(name, moduleName, this.getExtendOn(ast, moduleName)); + } + resolveConflictModels(usedExternModel) { var conflicts = new Map(); var names = new Map(); @@ -1228,7 +1329,8 @@ class TypeChecker { } else if (type.tag === Tag.ID && type.lexeme.startsWith('$')) { return _model(type.lexeme); } else if (type.tag === Tag.ID && type.idType === 'model') { - return _model(type.lexeme, moduleName); + const checker = moduleName ? this.dependencies.get(moduleName) : this; + return checker.getModel(type.lexeme, moduleName); } else if (type.tag === Tag.ID && this.dependencies.has(type.lexeme)) { return { type: 'module_instance', @@ -1237,13 +1339,14 @@ class TypeChecker { } else if (type.tag === Tag.ID && type.idType === 'typedef') { return _typedef(type.lexeme, moduleName); } else if (type.tag === Tag.ID) { - return _model(type.lexeme); + return this.getModel(type.lexeme); } else if ( type.type === 'moduleModel' || type.type === 'subModel' || type.type === 'subModel_or_moduleModel') { if (moduleName && type.type === 'subModel') { - return _model(type.path.map((item) => { + const checker = this.dependencies.get(moduleName); + return checker.getModel(type.path.map((item) => { return item.lexeme; }).join('.'), moduleName); } @@ -1255,7 +1358,7 @@ class TypeChecker { return item.lexeme; }).join('.'); if (checker.models.has(typeName)) { - return _model(rest.map((item) => { + return checker.getModel(rest.map((item) => { return item.lexeme; }).join('.'), mainId.lexeme); } else if (checker.enums.has(typeName)) { @@ -1271,7 +1374,7 @@ class TypeChecker { } if (idType === 'model') { - return _model(type.path.map((item) => { + return this.getModel(type.path.map((item) => { return item.lexeme; }).join('.')); } @@ -1357,8 +1460,6 @@ class TypeChecker { const ast = builtin.get(id); if(ast.type === 'model') { return 'builtin_model'; - } else if(ast.type === '$type') { - return '$type'; } return 'builtin_module'; } else if (this.enums.has(id)) { @@ -1492,9 +1593,21 @@ class TypeChecker { }; } + // for filed: [ A.B ] + if (field.fieldItemType.type === 'moduleModel') { + const [mainId, ...rest] = field.fieldItemType.path; + let filedName = rest.map((tag) => { + return tag.lexeme; + }).join('.'); + return { + type: 'array', + itemType: this.getModel(filedName, mainId.lexeme) + }; + } + return { type: 'array', - itemType: _model(field.fieldItemType.lexeme, moduleName) + itemType: this.getModel(field.fieldItemType.lexeme, moduleName) }; } @@ -1514,6 +1627,17 @@ class TypeChecker { valueType: this.getType(find.fieldValue.valueType, moduleName) }; } + if (find.fieldValue.valueType.type === 'moduleModel') { + const [mainId, ...rest] = find.fieldValue.valueType.path; + let filedName = rest.map((tag) => { + return tag.lexeme; + }).join('.'); + return { + type: 'map', + keyType: _basic(find.fieldValue.keyType.lexeme), + valueType: this.getModel(filedName, mainId.lexeme) + }; + } return { type: 'map', keyType: _basic(find.fieldValue.keyType.lexeme), @@ -1525,7 +1649,7 @@ class TypeChecker { if (find.fieldValue.valueType.idType === 'model') { return { type: 'entry', - valueType: _model(find.fieldValue.valueType.lexeme, moduleName) + valueType: this.getModel(find.fieldValue.valueType.lexeme, moduleName) }; } if (find.fieldValue.valueType.type === 'array') { @@ -1541,7 +1665,7 @@ class TypeChecker { } if (find.fieldValue.type === 'modelBody') { - return _model([modelName, find.fieldName.lexeme].join('.'), moduleName); + return this.getModel([modelName, find.fieldName.lexeme].join('.'), moduleName); } if (find.fieldValue.fieldType === 'array') { @@ -1551,7 +1675,7 @@ class TypeChecker { if (find.fieldValue.fieldType.tag === Tag.ID) { const id = find.fieldValue.fieldType.lexeme; if (this.models.has(id)) { - return _model(id); + return this.getModel(id, moduleName); } if (this.dependencies.has(id)) { @@ -1562,14 +1686,14 @@ class TypeChecker { } if (this.enums.has(id)) { - return _enum(id); + return _enum(id, moduleName); } if (this.typedefs.has(id)) { - return _typedef(id); + return _typedef(id, moduleName); } - return _model(find.fieldValue.fieldType.lexeme, moduleName); + return this.getModel(find.fieldValue.fieldType.lexeme, moduleName); } if (find.fieldValue.fieldType.type === 'moduleModel') { @@ -1577,7 +1701,7 @@ class TypeChecker { let filedName = rest.map((tag) => { return tag.lexeme; }).join('.'); - return _model(filedName, mainId.lexeme); + return this.getModel(filedName, mainId.lexeme); } if (find.fieldValue.fieldType.type === 'moduleEnum') { @@ -1600,7 +1724,7 @@ class TypeChecker { let modelName = find.fieldValue.fieldType.path.map((tag) => { return tag.lexeme; }).join('.'); - return _model(modelName); + return this.getModel(modelName); } return _basic(find.fieldValue.fieldType); @@ -1636,6 +1760,13 @@ class TypeChecker { } getInstanceMethod(name, checkerName) { + if(builtin.has(name)) { + const builtinMethod = builtin.get(name); + return { + method: builtinMethod, + }; + } + let current = this; if (current.methods.has(name)) { // check B @@ -1703,7 +1834,7 @@ class TypeChecker { if (!find) { this.error(`The model ${currentPath.join('.')} is undefined`, prop); } - current = find; + current = find.modelField; } ast.realType = { type: 'class', @@ -1920,19 +2051,28 @@ class TypeChecker { for (let i = 0; i < ast.object.fields.length; i++) { const field = ast.object.fields[i]; const name = field.fieldName.lexeme; - const modelField = this.findProperty(model, name); - if (!modelField) { + let find; + if (ast.aliasId.isModel) { + find = this.findProperty(model, name); + } else { + const checker = this.dependencies.get(aliasId); + find = checker.findProperty(model, name); + } + if (!find) { this.error(`the property "${name}" is undefined in model "${aliasId}.${modelName}"`, field.fieldName); } - + modelName = find.modelName; + const modelField = find.modelField; const type = this.getExprType(field.expr, env); let expected; if (ast.aliasId.isModel) { expected = this.getFieldType(modelField, modelName); } else { - expected = this.getFieldType(modelField, modelName, aliasId); + const checker = this.dependencies.get(aliasId); + expected = checker.getFieldType(modelField, modelName, aliasId); } + if (!eql([expected], [type])) { this.error(`the field type are mismatched. expected ` + `${display(expected)}, but ${display(type)}`, field.fieldName); @@ -1943,6 +2083,7 @@ class TypeChecker { } visitConstructModel(ast, env) { + const aliasId = ast.aliasId.lexeme; if (this.dependencies.has(aliasId)) { ast.aliasId.isModule = true; @@ -1959,6 +2100,14 @@ class TypeChecker { this.checkConstructModelFields(ast, model, `${modelId}`, env); return; } + + if (builtin.has(aliasId)) { + const model = builtin.get(aliasId); + assert.ok(model.type === 'model'); + ast.aliasId.isModel = true; + this.checkConstructModelFields(ast, model, aliasId, env); + return; + } if (this.models.has(aliasId)) { const model = this.models.get(aliasId); @@ -1984,7 +2133,7 @@ class TypeChecker { this.checkConstructModelFields(ast, subModel, modelName, env); return; } - + this.error(`expected "${aliasId}" is module or model`, ast.aliasId); } @@ -2121,6 +2270,7 @@ class TypeChecker { const arg = ast.args[i]; this.visitExpr(arg, env); const type = this.getExprType(arg, env); + actual.push(type); } @@ -2226,27 +2376,29 @@ class TypeChecker { if (isBasicType(type.valueType.name)) { return _basic(type.valueType.name); } - - return _model(type.valueType.name); + return this.getModel(type.valueType.name); } if (type.type === 'model') { - let model; + let model, modelName; if (type.moduleName) { const checker = this.dependencies.get(type.moduleName); model = checker.models.get(type.name); + modelName = `${type.moduleName}.${type.name}`; } else if (builtin.has(type.name)) { model = builtin.get(type.name); + modelName = type.name; } else { model = this.models.get(type.name); + modelName = type.name; } - const find = this.findProperty(model, propName); + const find = this.findProperty(model, propName, modelName); if (!find) { return; } - return this.getFieldType(find, type.name, type.moduleName); + return this.getFieldType(find.modelField, find.modelName, type.moduleName); } } @@ -2422,12 +2574,14 @@ class TypeChecker { if (ast.type === 'construct_model') { if (ast.aliasId.isModel) { - return _model([ast.aliasId.lexeme, ...ast.propertyPath.map((item) => { + return this.getModel([ast.aliasId.lexeme, ...ast.propertyPath.map((item) => { return item.lexeme; })].join('.')); } - return _model(ast.propertyPath.map((item) => { + const moduleName = ast.aliasId.lexeme; + const checker = this.dependencies.get(moduleName); + return checker.getModel(ast.propertyPath.map((item) => { return item.lexeme; }).join('.'), ast.aliasId.lexeme); } @@ -2535,7 +2689,9 @@ class TypeChecker { if (t.idType === 'enum') { return _enum(t.lexeme, extern); } - return _model(t.lexeme, extern); + + const checker = this.dependencies.get(extern); + return checker.getModel(t.lexeme, extern); } if (this.dependencies.has(t.lexeme)) { @@ -2554,7 +2710,7 @@ class TypeChecker { return _typedef(t.lexeme); } - return _model(t.lexeme); + return this.getModel(t.lexeme); } if (t.type === 'array') { @@ -2618,15 +2774,17 @@ class TypeChecker { const typeName = rest.map((item) => { return item.lexeme; }).join('.'); - this.usedExternModel.get(mainId.lexeme).add(typeName); - return _model(typeName, mainId.lexeme); + const moduleName = mainId.lexeme; + this.usedExternModel.get(moduleName).add(typeName); + const checker = this.dependencies.get(moduleName); + return checker.getModel(typeName, moduleName); } if (t.type === 'subModel') { let modelName = t.path.map((tag) => { return tag.lexeme; }).join('.'); - return _model(modelName); + return this.getModel(modelName); } if (t.type === 'moduleEnum') { @@ -2809,7 +2967,7 @@ class TypeChecker { } } - env.local.set(id, expected || type); + env.local.set(id, expected || type); ast.expr.inferred = expected || type; } @@ -2852,6 +3010,8 @@ class TypeChecker { // TODO } else if (arrayField.type === 'map') { // TODO + } else if (arrayField.type === 'subModel_or_moduleModel') { + this.checkType(arrayField); } else if (arrayField.fieldType === 'array') { return this.arrayFieldFlat(arrayField.fieldItemType); } else { diff --git a/test/fixtures/builtin_function/Darafile b/test/fixtures/builtin_function/Darafile new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/test/fixtures/builtin_function/Darafile @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/test/fixtures/builtin_function/function.dara b/test/fixtures/builtin_function/function.dara new file mode 100644 index 0000000..5bf2333 --- /dev/null +++ b/test/fixtures/builtin_function/function.dara @@ -0,0 +1,41 @@ +static function returnAny(): any; + +static async function main(args: [string]): void { + var a: integer = $integer(args[0]) + 10; + var b: string = $string(a) + args[1] + $string(returnAny()); + var c: number = $number(b) + $number(a) + $number(returnAny()); + var d: int8 = $int8(b) + $int8(a) + $int8(returnAny()); + var e: int16 = $int16(b) + $int16(a) + $int16(returnAny()); + var f: int32 = $int32(b) + $int32(a) + $int32(returnAny()); + var g: int64 = $int64(b) + $int64(a) + $int64(returnAny()); + var h: long = $long(b) + $long(a) + $long(returnAny()); + var i: ulong = $ulong(b) + $ulong(a) + $ulong(returnAny()); + var j: uint8 = $uint8(b) + $uint8(a) + $uint8(returnAny()); + var k: uint16 = $uint16(b) + $uint16(a) + $uint16(returnAny()); + var l: uint32 = $uint32(b) + $uint32(a) + $uint32(returnAny()); + var m: uint64 = $uint64(b) + $uint64(a) + $uint64(returnAny()); + var n: float = $float(b) + $float(a) + $float(returnAny()); + var o: double = $double(b) + $double(a) + $double(returnAny()); + if($boolean(args[2])) { + var data = $bytes(returnAny()); + var length: integer = data.length(); + var test: any = $any(data); + var maps: map[string]string = { + key = 'value', + }; + var obj: object = $object(maps); + var ws = $writable(obj); + var rs = $readable(maps); + data = rs.read(30); + if(!$isNull(data)) { + ws.write(data); + } + } + + $sleep(a); + var defaultVal = $string($default(args[0], args[1])); + + if($equal(defaultVal, b)) { + return; + } +} \ No newline at end of file diff --git a/test/fixtures/builtin_model/Darafile b/test/fixtures/builtin_model/Darafile new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/test/fixtures/builtin_model/Darafile @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/test/fixtures/builtin_model/model.dara b/test/fixtures/builtin_model/model.dara new file mode 100644 index 0000000..7e87622 --- /dev/null +++ b/test/fixtures/builtin_model/model.dara @@ -0,0 +1,30 @@ +static async function main(args: [string]): void { + var a = new $Model; + var b = new $Request{ + protocol = "https", + port = 80, + method = "POST", + pathname = "/test", + query = { + key = "value" + }, + headers = { + key = "value" + }, + body = $File.createReadStream('/tmp/test'), + }; + var c = new $Response{ + statusCode = 200, + statusMessage = "Success", + headers = { + key = "value" + }, + body = $File.createReadStream('/tmp/test'), + }; + var d = new $Error{ + name = "Error", + message = "error message", + code = "Error", + stack = "__filename:5:16", + }; +} \ No newline at end of file diff --git a/test/fixtures/extend_model/main.dara b/test/fixtures/extend_model/main.dara index d16ff17..79a2bd7 100644 --- a/test/fixtures/extend_model/main.dara +++ b/test/fixtures/extend_model/main.dara @@ -1,40 +1,60 @@ -import OSS +import './util.dara' Util; +import OSS; -model mainFile extends OSS.File { - size: number, +model extendFile extends Util.mainFile { + path: string, + sub: OSS.File, + subFiles: [ OSS.SubFile.file ], + fileMap: map[string]OSS.SubFile.file, } -exception MainFileError extends OSS.File { - size: number, +model DeriveSubFile extends Util.ExtendSubFile { + size: string, } -exception ExtendFileError extends OSS.FileError { - size: number, -} -exception ExtendSubFileError extends OSS.SubFile.file { - size: number, -} +static function getFile(efile: OSS.File, file: OSS.File, err: OSS.SubFile.file): void; static function call(size: number): void { - var file = new mainFile{ + var file = new Util.mainFile{ name = 'name', size = 100, }; - if(size > file.size) { - throw new MainFileError{ + var maps = { + key = new Util.ExtendSubFileError{ name = 'name', size = 100, } + }; + var efile = new extendFile { + name = 'name', + size = 100, + path = '/test', + sub = file, + subFiles = [ + new DeriveSubFile { + name = 'name', + size = '100', + } + ], + fileMap = maps + }; + if(size > file.size) { + throw new Util.MainFileError{ + name = 'name', + size = 100, + }; } else if (size == file.size){ - throw new ExtendFileError{ + throw new Util.ExtendFileError{ code = 'name', size = 100, - } + }; } else { - throw new ExtendSubFileError{ + var err = new Util.ExtendSubFileError{ name = 'name', size = 100, - } + }; + getFile(efile, file, err); } + } diff --git a/test/fixtures/extend_model/util.dara b/test/fixtures/extend_model/util.dara new file mode 100644 index 0000000..4c7ac9f --- /dev/null +++ b/test/fixtures/extend_model/util.dara @@ -0,0 +1,21 @@ +import OSS + +model mainFile extends OSS.File { + size: number, +} + +exception MainFileError extends OSS.File { + size: number, +} + +exception ExtendFileError extends OSS.FileError { + size: number, +} + +exception ExtendSubFileError extends OSS.SubFile.file { + size: number, +} + +model ExtendSubFile extends OSS.SubFile.file { + size: number, +} \ No newline at end of file diff --git a/test/semantic.test.js b/test/semantic.test.js index 571977f..3a3376b 100644 --- a/test/semantic.test.js +++ b/test/semantic.test.js @@ -1475,6 +1475,7 @@ describe('semantic', function () { { 'fields': [], 'expectedType': { + 'extendOn': undefined, 'moduleName': undefined, 'name': 'M', 'type': 'model' @@ -1733,6 +1734,7 @@ describe('semantic', function () { 'tag': 2, 'index': 36, 'inferred': { + 'extendOn': undefined, 'moduleName': undefined, 'name': '$Error', 'type': 'model', @@ -1954,6 +1956,7 @@ describe('semantic', function () { 'left': { 'id': { 'inferred': { + 'extendOn': undefined, 'moduleName': undefined, 'name': '$Request', 'type': 'model' @@ -2043,6 +2046,7 @@ describe('semantic', function () { 'type': 'variable', 'index': 37, 'inferred': { + 'extendOn': undefined, 'moduleName': undefined, 'name': '$Request', 'type': 'model' @@ -2955,6 +2959,7 @@ describe('semantic', function () { 'type': 'basic' }, 'inferred': { + 'extendOn': undefined, 'moduleName': undefined, 'name': 'M', 'type': 'model' @@ -3017,6 +3022,7 @@ describe('semantic', function () { } }, 'inferred': { + 'extendOn': undefined, 'moduleName': undefined, 'name': 'M', 'type': 'model' @@ -3068,6 +3074,7 @@ describe('semantic', function () { 'type': 'basic' }, 'expectedType': { + 'extendOn': undefined, 'moduleName': undefined, 'name': 'M', 'type': 'model' @@ -3120,11 +3127,13 @@ describe('semantic', function () { 'tag': 2 }, 'inferred': { + 'extendOn': undefined, 'moduleName': undefined, 'name': 'M', 'type': 'model' }, 'expectedType': { + 'extendOn': undefined, 'moduleName': undefined, 'name': '$Model', 'type': 'model' @@ -3304,6 +3313,7 @@ describe('semantic', function () { expect(expectedType).to.be.eql({ 'type': 'array', 'itemType': { + 'extendOn': undefined, 'moduleName': undefined, 'type': 'model', 'name': 'N' @@ -3323,6 +3333,7 @@ describe('semantic', function () { }`, '__filename'); expectedType = ast.moduleBody.nodes[2].functionBody.stmts.stmts[0].object.fields[0].expectedType; expect(expectedType).to.be.eql({ + 'extendOn': undefined, 'moduleName': undefined, 'type': 'model', 'name': 'N' @@ -3362,6 +3373,7 @@ describe('semantic', function () { }`, '__filename'); expectedType = ast.moduleBody.nodes[1].functionBody.stmts.stmts[0].object.fields[0].expectedType; expect(expectedType).to.be.eql({ + 'extendOn': undefined, 'moduleName': undefined, 'type': 'model', 'name': 'M.n' @@ -3597,6 +3609,7 @@ describe('semantic', function () { const [stmt] = func1.functionBody.stmts.stmts; expect(stmt.propertyPathTypes).to.eql([ { + 'extendOn': undefined, 'moduleName': undefined, 'name': 'M.N', 'type': 'model' @@ -3623,6 +3636,7 @@ describe('semantic', function () { const [stmt1] = func2.functionBody.stmts.stmts; expect(stmt1.propertyPathTypes).to.eql([ { + 'extendOn': undefined, 'moduleName': undefined, 'name': 'N1', 'type': 'model' @@ -4942,6 +4956,7 @@ describe('semantic', function () { 'index': 38, 'type': 'variable', 'inferred': { + 'extendOn': undefined, 'type': 'model', 'name': 'M', 'moduleName': undefined @@ -5620,6 +5635,7 @@ describe('semantic', function () { 'index': 35, 'type': 'variable', 'inferred': { + 'extendOn': undefined, 'moduleName': undefined, 'type': 'model', 'name': 'M' @@ -6190,6 +6206,7 @@ describe('semantic', function () { }); expect(instanceCallArgs).to.be.eql({ + 'extendOn': undefined, 'type': 'model', 'name': 'Options', 'moduleName': 'OSS' @@ -6244,6 +6261,7 @@ describe('semantic', function () { 'index': 34, 'type': 'variable', 'inferred': { + 'extendOn': undefined, 'moduleName': undefined, 'type': 'model', 'name': 'M' @@ -6288,6 +6306,7 @@ describe('semantic', function () { 'name': 'string' }, 'valueType': { + 'extendOn': undefined, 'moduleName': undefined, 'type': 'model', 'name': 'N' @@ -6301,6 +6320,7 @@ describe('semantic', function () { 'name': 'string' }, 'valueType': { + 'extendOn': undefined, 'moduleName': undefined, 'type': 'model', 'name': 'N' @@ -7011,4 +7031,109 @@ describe('semantic', function () { readAndParse('fixtures/builtin_module/logger.dara'); }).to.not.throwException(); }); + + it('use builtin functions shoule be ok', function(){ + expect(function () { + readAndParse('fixtures/builtin_function/function.dara'); + }).to.not.throwException(); + }); + + it('use builtin models shoule be ok', function(){ + expect(function () { + readAndParse('fixtures/builtin_model/model.dara'); + }).to.not.throwException(); + }); + + it('use extend model assign shoule be ok', function(){ + expect(function () { + parse(` + model base {}; + + model sub extends base {}; + + model sub2 extends sub {}; + + model sub3 extends sub2 {}; + + model sub4 extends sub3 {}; + + model TestError extends $Error { + message: string, + }; + + static function callOSS(): void { + var a: base = new sub4; + + var b: base = new sub3; + + var c: base = new sub2; + + var d: base = new sub; + + var e: sub = new sub2; + + var f: sub2 = new sub3; + + var g: sub3 = new sub4; + + var h: $Model = new base; + + var j: $Error = new TestError{ + message = "true", + }; + + return; + }`, '__filename'); + }).to.not.throwException(); + + + expect(function () { + parse(` + model m { + sub: { + name: string, + sub: { + name: string, + }, + base: $Model, + } + }; + + model sub extends m.sub { + + }; + + + model sub2 extends m.sub.sub {}; + + static function callOSS(): void { + var b = new sub2 { + name = "name", + }; + var a: m.sub = new sub { + name = "name", + sub = b, + base = b, + }; + return; + }`, '__filename'); + }).to.not.throwException(); + + expect(function () { + parse(` + model base {}; + + model sub extends base {}; + + model other {}; + + static function callOSS(): void { + var b: base = new other; + return; + }`, '__filename'); + }).to.throwException(function(e) { + expect(e).to.be.a(SyntaxError); + expect(e.message).to.be('declared variable with mismatched type, expected: base, actual: other'); + }); + }); }); \ No newline at end of file