@neo-one/smart-contract-compiler
Version:
NEO•ONE TypeScript smart contract compiler.
376 lines (374 loc) • 17 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Builtins = void 0;
const tslib_1 = require("tslib");
const ts_utils_1 = require("@neo-one/ts-utils");
const utils_1 = require("@neo-one/utils");
const lodash_1 = tslib_1.__importDefault(require("lodash"));
const typescript_1 = tslib_1.__importDefault(require("typescript"));
const utils_2 = require("../../utils");
const types_1 = require("./types");
const getMember = (sym, name) => ts_utils_1.tsUtils.symbol.getMemberOrThrow(sym, name);
const getExportOrMember = (sym, name) => {
const member = ts_utils_1.tsUtils.symbol.getMember(sym, name);
return member === undefined ? ts_utils_1.tsUtils.symbol.getExportOrThrow(sym, name) : member;
};
const findNonNull = (value) => value.find((val) => val !== undefined);
const throwIfNull = (value) => {
if (value == undefined) {
throw new Error('Something went wrong.');
}
return value;
};
class Builtins {
constructor(context) {
this.context = context;
this.builtinMembers = new Map();
this.allBuiltinMembers = new Map();
this.builtinInterfaces = new Map();
this.builtinValues = new Map();
this.builtinOverrides = new Map();
this.memoized = utils_2.createMemoized();
}
isBuiltinSymbol(symbol) {
return symbol !== undefined && (this.builtinValues.has(symbol) || this.builtinInterfaces.has(symbol));
}
isBuiltinIdentifier(value) {
return (this.getAnyInterfaceSymbolMaybe(value) !== undefined ||
this.getAnyTypeSymbolMaybe(value) !== undefined ||
this.getAnyValueSymbolMaybe(value) !== undefined);
}
isBuiltinFile(file) {
return this.getContract() === file;
}
getMember(value, prop) {
return this.memoized('get-member', `${utils_2.nodeKey(value)}:${utils_2.nodeKey(prop)}`, () => {
const propSymbol = this.context.analysis.getSymbol(prop);
if (propSymbol === undefined) {
return undefined;
}
const valueSymbol = this.context.analysis.getTypeSymbol(value);
if (valueSymbol === undefined) {
return this.allBuiltinMembers.get(propSymbol);
}
if (!ts_utils_1.tsUtils.guards.isSuperExpression(value)) {
const overridenMember = this.walkOverridesForMember(valueSymbol, propSymbol);
if (overridenMember !== undefined) {
return overridenMember;
}
}
const members = this.getAllMembers(valueSymbol);
return members.get(propSymbol);
});
}
getOnlyMember(value, name) {
return this.getOnlyMemberBase(value, name, (result) => result[1]);
}
getOnlyMemberSymbol(value, name) {
return throwIfNull(this.getOnlyMemberBase(value, name, (result) => result[0]));
}
getMembers(name, isMember, isEligible, symbolMembers = false) {
const filterPseudoSymbol = (symbol, key) => {
const symbolSymbol = this.getInterfaceSymbolBase('SymbolConstructor', this.getGlobals());
return ts_utils_1.tsUtils.symbol.getMember(symbolSymbol, key) !== symbol;
};
const isSymbolKey = (key) => key.startsWith('__@');
let testKey = (key) => !isSymbolKey(key);
let modifyKey = (key) => key;
if (symbolMembers) {
testKey = isSymbolKey;
modifyKey = (key) => key.slice(3);
}
const members = this.getAllMembers(this.getAnyInterfaceSymbol(name));
const mutableMembers = [];
members.forEach((builtin, memberSymbol) => {
const memberName = ts_utils_1.tsUtils.symbol.getName(memberSymbol);
if (isMember(builtin) &&
filterPseudoSymbol(memberSymbol, memberName) &&
testKey(memberName) &&
isEligible(builtin)) {
mutableMembers.push([modifyKey(memberName), builtin]);
}
});
return mutableMembers;
}
getInterface(value) {
const valueSymbol = this.context.analysis.getSymbol(value);
if (valueSymbol === undefined) {
return undefined;
}
return this.builtinInterfaces.get(valueSymbol);
}
getInterfaceSymbol(value) {
return this.getAnyInterfaceSymbol(value);
}
getValue(value) {
const valueSymbol = this.context.analysis.getSymbol(value);
if (valueSymbol === undefined) {
return undefined;
}
return this.builtinValues.get(valueSymbol);
}
getValueInterface(value) {
const builtinValue = this.getValue(value);
return builtinValue === undefined || !types_1.isBuiltinValueObject(builtinValue) ? undefined : builtinValue.type;
}
getValueSymbol(value) {
return this.getAnyValueSymbol(value);
}
getTypeSymbol(name) {
return this.getAnyTypeSymbol(name);
}
isInterface(node, testType, name) {
return this.memoized('is-interface', `${utils_2.typeKey(testType)}:${name}`, () => {
const symbol = this.context.analysis.getSymbolForType(node, testType);
if (symbol === undefined) {
return false;
}
const interfaceSymbol = this.getAnyInterfaceSymbol(name);
return symbol === interfaceSymbol;
});
}
isType(node, testType, name) {
return this.memoized('is-type', `${utils_2.typeKey(testType)}:${name}`, () => {
const symbol = this.context.analysis.getSymbolForType(node, testType);
if (symbol === undefined) {
return false;
}
if (name === 'Fixed') {
const fixedTagSymbol = this.getAnyInterfaceSymbol('FixedTag');
if (symbol === fixedTagSymbol) {
return true;
}
}
if (name === 'ForwardedValue') {
const forwardedValueTagSymbol = this.getAnyInterfaceSymbol('ForwardedValueTag');
if (symbol === forwardedValueTagSymbol) {
return true;
}
}
const typeSymbol = this.getAnyTypeSymbol(name);
return symbol === typeSymbol;
});
}
isValue(node, name) {
return this.memoized('is-value', `${utils_2.nodeKey(node)}:${name}`, () => {
const symbol = this.context.analysis.getSymbol(node);
if (symbol === undefined) {
return false;
}
const valueSymbol = this.getAnyValueSymbol(name);
return symbol === valueSymbol;
});
}
addMember(valueSymbol, memberSymbol, builtin) {
let members = this.builtinMembers.get(valueSymbol);
if (members === undefined) {
members = new Map();
this.builtinMembers.set(valueSymbol, members);
}
members.set(memberSymbol, builtin);
this.allBuiltinMembers.set(memberSymbol, builtin);
const memberSymbolName = ts_utils_1.tsUtils.symbol.getName(memberSymbol);
if (memberSymbolName.startsWith('__@')) {
const symbolSymbol = this.getInterfaceSymbolBase('SymbolConstructor', this.getGlobals());
const memberSymbolSymbol = getMember(symbolSymbol, memberSymbolName.slice(3));
members.set(memberSymbolSymbol, builtin);
this.allBuiltinMembers.set(memberSymbolSymbol, builtin);
}
}
addOverride(superSymbol, overrideSymbol) {
this.builtinOverrides.set(superSymbol, overrideSymbol);
}
addGlobalMember(value, member, builtin) {
this.addMemberBase(value, member, builtin, this.getGlobals());
}
addContractMember(value, member, builtin) {
this.addMemberBase(value, member, builtin, this.getContract());
}
addInterface(value, builtin) {
this.addInterfaceBase(value, builtin, this.getGlobals());
}
addContractInterface(value, builtin) {
this.addInterfaceBase(value, builtin, this.getContract());
}
addValue(value, builtin) {
this.addValueBase(value, builtin, this.getGlobals());
}
addTestValue(value, builtin) {
const file = this.getTestGlobals();
if (file === undefined) {
return;
}
this.addValueBase(value, builtin, file);
}
addContractValue(value, builtin) {
this.addValueBase(value, builtin, this.getContract());
}
walkOverridesForMember(valueSymbol, propSymbol) {
const overrideValueSymbol = this.builtinOverrides.get(valueSymbol);
if (overrideValueSymbol === undefined) {
return undefined;
}
let overridePropSymbol = this.builtinOverrides.get(propSymbol);
if (overridePropSymbol === undefined) {
overridePropSymbol = propSymbol;
}
const member = this.walkOverridesForMember(overrideValueSymbol, overridePropSymbol);
if (member !== undefined) {
return member;
}
const overridenMembers = this.getAllMembers(overrideValueSymbol);
return overridenMembers.get(overridePropSymbol);
}
getOnlyMemberBase(value, name, getValue) {
return this.memoized('only-member-base', `${value}$${name}`, () => {
const symbol = this.getAnyInterfaceOrValueSymbol(value);
const members = this.getAllMembers(symbol);
const result = [...members.entries()].find(([memberSymbol]) => ts_utils_1.tsUtils.symbol.getName(memberSymbol) === name);
return result === undefined ? undefined : getValue(result);
});
}
getAllMembers(symbol) {
return this.memoized('get-all-members', ts_utils_1.symbolKey(symbol), () => {
const interfaceMembers = this.builtinMembers.get(symbol);
const memberEntries = [...this.getInheritedSymbols(symbol)].reduce((acc, parentInterfaceSymbol) => {
const parentInterfaceMembers = this.builtinMembers.get(parentInterfaceSymbol);
if (parentInterfaceMembers === undefined) {
return acc;
}
return [...parentInterfaceMembers.entries()].concat(acc);
}, interfaceMembers === undefined ? [] : [...interfaceMembers.entries()]);
return new Map(memberEntries);
});
}
addMemberBase(value, member, builtin, file) {
let valueSymbol = this.getInterfaceSymbolMaybe(value, file);
let memberSymbol;
if (valueSymbol === undefined) {
valueSymbol = this.getValueSymbolBase(value, file);
memberSymbol = getExportOrMember(valueSymbol, member);
}
else {
memberSymbol = getMember(valueSymbol, member);
}
this.addMember(valueSymbol, memberSymbol, builtin);
}
getAnyInterfaceOrValueSymbol(value) {
const valueSymbol = this.getAnyInterfaceSymbolMaybe(value);
return valueSymbol === undefined ? this.getAnyValueSymbol(value) : valueSymbol;
}
addInterfaceBase(value, builtin, file) {
this.builtinInterfaces.set(this.getInterfaceSymbolBase(value, file), builtin);
}
addValueBase(value, builtin, file) {
this.builtinValues.set(this.getValueSymbolBase(value, file), builtin);
}
getAnyValueSymbol(name) {
return this.memoized('any-value-symbol', name, () => throwIfNull(this.getAnyValueSymbolMaybe(name)));
}
getAnyValueSymbolMaybe(name) {
return this.memoized('get-any-value-symbol-maybe', name, () => findNonNull(this.getFiles().map((file) => this.getValueSymbolMaybe(name, file))));
}
getValueSymbolBase(name, file) {
return throwIfNull(this.getValueSymbolMaybe(name, file));
}
getValueSymbolMaybe(name, file) {
let decl = ts_utils_1.tsUtils.statement.getVariableDeclaration(file, name);
if (decl === undefined) {
decl = ts_utils_1.tsUtils.statement.getFunction(file, name);
}
if (decl === undefined) {
decl = ts_utils_1.tsUtils.statement.getEnum(file, name);
}
if (decl === undefined) {
decl = ts_utils_1.tsUtils.statement.getClass(file, name);
}
if (decl === undefined) {
return undefined;
}
return ts_utils_1.tsUtils.node.getSymbol(this.context.typeChecker, decl);
}
getAnyInterfaceSymbol(name) {
return this.memoized('any-interface-symbol', name, () => throwIfNull(this.getAnyInterfaceSymbolMaybe(name)));
}
getAnyInterfaceSymbolMaybe(name) {
return this.memoized('get-any-interface-symbol-maybe', name, () => findNonNull(this.getFiles().map((file) => this.getInterfaceSymbolMaybe(name, file))));
}
getInterfaceSymbolBase(name, file) {
return throwIfNull(this.getInterfaceSymbolMaybe(name, file));
}
getInterfaceSymbolMaybe(name, file) {
return this.getInterfaceSymbols(file)[name];
}
getInterfaceSymbols(file) {
return this.memoized('interface-symbols', ts_utils_1.tsUtils.file.getFilePath(file), () => {
const interfaceDecls = ts_utils_1.tsUtils.statement.getInterfaces(file);
const decls = interfaceDecls.concat(ts_utils_1.tsUtils.statement.getEnums(file));
return lodash_1.default.fromPairs(decls.map((decl) => {
const type = ts_utils_1.tsUtils.type_.getType(this.context.typeChecker, decl);
const symbol = ts_utils_1.tsUtils.type_.getSymbol(type);
return [ts_utils_1.tsUtils.node.getName(decl), symbol];
}));
});
}
getInheritedSymbols(symbol, baseTypes = []) {
return this.memoized('get-inherited-symbols', ts_utils_1.symbolKey(symbol), () => {
const symbols = new Set();
for (const decl of ts_utils_1.tsUtils.symbol.getDeclarations(symbol)) {
if (typescript_1.default.isInterfaceDeclaration(decl) || typescript_1.default.isClassDeclaration(decl) || typescript_1.default.isClassExpression(decl)) {
let baseType = baseTypes[0];
let nextBaseTypes = baseTypes.slice(1);
if (baseTypes.length === 0) {
const currentBaseTypes = ts_utils_1.tsUtils.class_.getBaseTypesFlattened(this.context.typeChecker, decl);
baseType = currentBaseTypes[0];
nextBaseTypes = currentBaseTypes.slice(1);
}
if (baseType !== undefined) {
const baseSymbol = this.context.analysis.getSymbolForType(decl, baseType);
if (baseSymbol !== undefined) {
symbols.add(baseSymbol);
this.getInheritedSymbols(baseSymbol, nextBaseTypes).forEach((inheritedSymbol) => {
symbols.add(inheritedSymbol);
});
}
}
}
}
return symbols;
});
}
getAnyTypeSymbol(name) {
return this.memoized('get-any-type-symbol', name, () => throwIfNull(this.getAnyTypeSymbolMaybe(name)));
}
getAnyTypeSymbolMaybe(name) {
return this.memoized('get-any-type-symbol-maybe', name, () => findNonNull(this.getFiles().map((file) => this.getTypeSymbolMaybe(name, file))));
}
getTypeSymbolMaybe(name, file) {
return this.getTypeSymbols(file)[name];
}
getTypeSymbols(file) {
return this.memoized('type-symbols', ts_utils_1.tsUtils.file.getFilePath(file), () => {
const decls = ts_utils_1.tsUtils.statement.getTypeAliases(file);
return lodash_1.default.fromPairs(decls.map((decl) => {
const type = ts_utils_1.tsUtils.type_.getType(this.context.typeChecker, decl);
const symbol = ts_utils_1.tsUtils.type_.getAliasSymbol(type);
return [ts_utils_1.tsUtils.node.getName(decl), symbol];
}));
});
}
getFiles() {
return this.memoized('file-cache', 'files', () => [this.getGlobals(), this.getContract(), this.getTestGlobals()].filter(utils_1.utils.notNull));
}
getGlobals() {
return this.memoized('file-cache', 'globals', () => ts_utils_1.tsUtils.file.getSourceFileOrThrow(this.context.program, this.context.host.getSmartContractPath('global.d.ts')));
}
getContract() {
return this.memoized('file-cache', 'contract', () => ts_utils_1.tsUtils.file.getSourceFileOrThrow(this.context.program, this.context.host.getSmartContractPath('index.d.ts')));
}
getTestGlobals() {
return this.memoized('file-cache', 'test', () => ts_utils_1.tsUtils.file.getSourceFile(this.context.program, this.context.host.getSmartContractPath('harness.d.ts')));
}
}
exports.Builtins = Builtins;
//# sourceMappingURL=Builtins.js.map