@neo-one/smart-contract-compiler
Version:
NEO•ONE TypeScript smart contract compiler.
521 lines (519 loc) • 27.5 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.CallLikeHelper = void 0;
const tslib_1 = require("tslib");
const ts_utils_1 = require("@neo-one/ts-utils");
const lodash_1 = tslib_1.__importDefault(require("lodash"));
const typescript_1 = tslib_1.__importDefault(require("typescript"));
const DiagnosticCode_1 = require("../../../DiagnosticCode");
const DiagnosticMessage_1 = require("../../../DiagnosticMessage");
const builtins_1 = require("../../builtins");
const constants_1 = require("../../constants");
const Helper_1 = require("../Helper");
class CallLikeHelper extends Helper_1.Helper {
constructor() {
super(...arguments);
this.kind = typescript_1.default.SyntaxKind.CallExpression;
}
emit(sb, expression, optionsIn) {
const isOptionalChain = typescript_1.default.isOptionalChain(expression);
const expr = typescript_1.default.isCallExpression(expression)
? ts_utils_1.tsUtils.expression.getExpression(expression)
: ts_utils_1.tsUtils.template.getTag(expression);
const valueBuiltin = sb.context.builtins.getValue(expr);
if (valueBuiltin !== undefined && !ts_utils_1.tsUtils.guards.isSuperExpression(expr)) {
if (typescript_1.default.isCallExpression(expression) && builtins_1.isBuiltinCall(valueBuiltin)) {
valueBuiltin.emitCall(sb, expression, optionsIn);
return;
}
if (typescript_1.default.isTaggedTemplateExpression(expression) && builtins_1.isBuiltinTemplate(valueBuiltin)) {
valueBuiltin.emitCall(sb, expression, optionsIn);
return;
}
sb.context.reportError(expr, DiagnosticCode_1.DiagnosticCode.InvalidBuiltinCall, DiagnosticMessage_1.DiagnosticMessage.InvalidBuiltinCall);
}
const throwTypeError = (innerOptions) => {
sb.emitOp(expr, 'DROP');
sb.emitHelper(expr, innerOptions, sb.helpers.throwTypeError);
};
const processUndefined = (innerOptions) => {
sb.emitOp(expr, 'DROP');
sb.emitOp(expr, 'DROP');
sb.emitHelper(expr, innerOptions, sb.helpers.wrapUndefined);
};
const throwTypeErrorUnlessOptionalChain = (innerOptions) => {
isOptionalChain ? processUndefined(innerOptions) : throwTypeError(innerOptions);
};
const handleArguments = (innerOptions) => {
if (typescript_1.default.isCallExpression(expression)) {
sb.emitHelper(expression, innerOptions, sb.helpers.args);
}
else {
const template = ts_utils_1.tsUtils.template.getTemplate(expression);
if (typescript_1.default.isNoSubstitutionTemplateLiteral(template)) {
sb.emitPushInt(template, 0);
sb.emitOp(template, 'NEWARRAY');
sb.emitOp(template, 'DUP');
sb.visit(template, innerOptions);
sb.emitOp(template, 'APPEND');
sb.emitHelper(template, innerOptions, sb.helpers.wrapArray);
sb.emitPushInt(template, 1);
sb.emitOp(template, 'PACK');
}
else {
const head = ts_utils_1.tsUtils.template.getTemplateHead(template);
lodash_1.default.reverse([...ts_utils_1.tsUtils.template.getTemplateSpans(template)]).forEach((span) => {
sb.visit(ts_utils_1.tsUtils.expression.getExpression(span), innerOptions);
});
sb.emitPushInt(template, 0);
sb.emitOp(template, 'NEWARRAY');
sb.emitOp(template, 'DUP');
sb.emitPushString(head, ts_utils_1.tsUtils.literal.getLiteralValue(head));
sb.emitHelper(head, innerOptions, sb.helpers.wrapString);
sb.emitOp(head, 'APPEND');
ts_utils_1.tsUtils.template.getTemplateSpans(template).forEach((span) => {
const spanLiteral = ts_utils_1.tsUtils.template.getLiteral(span);
sb.emitOp(spanLiteral, 'DUP');
sb.emitPushString(spanLiteral, ts_utils_1.tsUtils.literal.getLiteralValue(spanLiteral));
sb.emitHelper(head, innerOptions, sb.helpers.wrapString);
sb.emitOp(expr, 'APPEND');
});
sb.emitHelper(template, innerOptions, sb.helpers.wrapArray);
sb.emitPushInt(template, ts_utils_1.tsUtils.template.getTemplateSpans(template).length + 1);
sb.emitOp(template, 'PACK');
}
}
};
const handlePropertyVisit = (lhs, innerOptions) => {
if (ts_utils_1.tsUtils.guards.isSuperExpression(lhs)) {
sb.scope.getThis(sb, lhs, innerOptions);
sb.visit(lhs, innerOptions);
}
else {
sb.visit(lhs, innerOptions);
sb.emitOp(lhs, 'DUP');
}
};
const isValidBuiltinCall = (builtinProp) => {
if (typescript_1.default.isCallExpression(expression)) {
return builtins_1.isBuiltinMemberCall(builtinProp) || builtins_1.isBuiltinInstanceMemberCall(builtinProp);
}
if (typescript_1.default.isTaggedTemplateExpression(expression)) {
return builtins_1.isBuiltinMemberTemplate(builtinProp) || builtins_1.isBuiltinInstanceMemberTemplate(builtinProp);
}
return false;
};
const handleBuiltinMemberCall = (builtinProp, memberLike, visited) => {
if (typescript_1.default.isCallExpression(expression)) {
if (builtins_1.isBuiltinMemberCall(builtinProp)) {
builtinProp.emitCall(sb, memberLike, expression, optionsIn);
return;
}
if (builtins_1.isBuiltinInstanceMemberCall(builtinProp)) {
builtinProp.emitCall(sb, memberLike, expression, optionsIn, visited);
return;
}
}
else if (typescript_1.default.isTaggedTemplateExpression(expression)) {
if (builtins_1.isBuiltinMemberTemplate(builtinProp)) {
builtinProp.emitCall(sb, memberLike, expression, optionsIn);
return;
}
if (builtins_1.isBuiltinInstanceMemberTemplate(builtinProp)) {
builtinProp.emitCall(sb, memberLike, expression, optionsIn, visited);
return;
}
}
};
const emitDropNTimes = (innerSb, node, n) => {
for (let i = 0; i < n; i += 1) {
innerSb.emitOp(node, 'DROP');
}
};
const callIfNotNullOrUndefined = ({ innerOptions, argsNum, callback, }) => {
sb.emitHelper(expr, optionsIn, sb.helpers.if({
condition: () => {
sb.emitOp(expr, 'DUP');
sb.emitHelper(expr, optionsIn, sb.helpers.isNull);
},
whenTrue: () => {
emitDropNTimes(sb, expr, argsNum);
sb.emitHelper(expr, innerOptions, sb.helpers.wrapUndefined);
},
whenFalse: () => {
sb.emitHelper(expr, optionsIn, sb.helpers.if({
condition: () => {
sb.emitOp(expr, 'DUP');
sb.emitHelper(expr, optionsIn, sb.helpers.isUndefined);
},
whenTrue: () => {
emitDropNTimes(sb, expr, argsNum);
sb.emitHelper(expr, innerOptions, sb.helpers.wrapUndefined);
},
whenFalse: () => {
callback();
},
}));
},
}));
};
const superExpression = typescript_1.default.isCallExpression(expression) ? ts_utils_1.tsUtils.expression.getExpression(expression) : undefined;
if (typescript_1.default.isCallExpression(expression) &&
superExpression !== undefined &&
ts_utils_1.tsUtils.guards.isSuperExpression(superExpression)) {
if (optionsIn.handleSuperConstruct !== undefined) {
optionsIn.handleSuperConstruct(expression, superExpression, optionsIn);
return;
}
const superClass = optionsIn.superClass;
if (superClass === undefined) {
throw new Error('Something went wrong, expected super class to be defined.');
}
const options = sb.pushValueOptions(sb.noSetValueOptions(optionsIn));
handleArguments(options);
sb.scope.getThis(sb, expression, options);
sb.scope.get(sb, expression, options, superClass);
sb.emitHelper(expression, sb.noPushValueOptions(options), sb.helpers.invokeConstruct());
}
else if (typescript_1.default.isPropertyAccessExpression(expr)) {
const value = ts_utils_1.tsUtils.expression.getExpression(expr);
const valueType = sb.context.analysis.getType(value);
const name = ts_utils_1.tsUtils.node.getNameNode(expr);
const nameValue = ts_utils_1.tsUtils.node.getName(expr);
const builtinProp = sb.context.builtins.getMember(value, name);
if (builtinProp !== undefined && isValidBuiltinCall(builtinProp)) {
handleBuiltinMemberCall(builtinProp, expr, false);
return;
}
const createProcessBuiltin = (valueName) => {
const member = sb.context.builtins.getOnlyMember(valueName, nameValue);
if (member === undefined) {
return throwTypeError;
}
return () => {
sb.emitOp(expression, 'DROP');
handleBuiltinMemberCall(member, expr, true);
};
};
const processObject = (innerOptions) => {
handleArguments(innerOptions);
sb.emitOp(expr, 'ROT');
sb.emitOp(expr, 'ROT');
sb.emitPushString(name, nameValue);
sb.emitHelper(expr, innerOptions, sb.helpers.getPropertyObjectProperty);
isOptionalChain
? callIfNotNullOrUndefined({
innerOptions,
argsNum: 3,
callback: () => sb.emitHelper(expr, optionsIn, sb.helpers.invokeCall({ bindThis: true })),
})
: sb.emitHelper(expr, optionsIn, sb.helpers.invokeCall({ bindThis: true }));
};
const options = sb.pushValueOptions(sb.noSetValueOptions(optionsIn));
handlePropertyVisit(value, options);
sb.emitHelper(value, options, sb.helpers.forBuiltinType({
type: valueType,
array: createProcessBuiltin('Array'),
arrayStorage: createProcessBuiltin('ArrayStorage'),
boolean: createProcessBuiltin('Boolean'),
buffer: createProcessBuiltin('Buffer'),
null: throwTypeError,
number: createProcessBuiltin('Number'),
object: processObject,
string: createProcessBuiltin('String'),
symbol: createProcessBuiltin('Symbol'),
undefined: throwTypeError,
map: createProcessBuiltin('Map'),
mapStorage: createProcessBuiltin('MapStorage'),
set: createProcessBuiltin('Set'),
setStorage: createProcessBuiltin('SetStorage'),
error: createProcessBuiltin('Error'),
forwardValue: createProcessBuiltin('ForwardValue'),
iteratorResult: createProcessBuiltin('IteratorResult'),
iterable: createProcessBuiltin('Iterable'),
iterableIterator: createProcessBuiltin('IterableIterator'),
transaction: createProcessBuiltin('TransactionBase'),
output: createProcessBuiltin('Output'),
attribute: createProcessBuiltin('AttributeBase'),
input: createProcessBuiltin('Input'),
account: createProcessBuiltin('Account'),
asset: createProcessBuiltin('Asset'),
contract: createProcessBuiltin('Contract'),
header: createProcessBuiltin('Header'),
block: createProcessBuiltin('Block'),
}));
}
else if (typescript_1.default.isElementAccessExpression(expr)) {
const value = ts_utils_1.tsUtils.expression.getExpression(expr);
const valueType = sb.context.analysis.getType(value);
const prop = ts_utils_1.tsUtils.expression.getArgumentExpressionOrThrow(expr);
const propType = sb.context.analysis.getType(prop);
const builtinProp = sb.context.builtins.getMember(value, prop);
if (builtinProp !== undefined && isValidBuiltinCall(builtinProp)) {
handleBuiltinMemberCall(builtinProp, expr, false);
return;
}
const getCallCases = (instanceName, useSymbol = false) => sb.context.builtins
.getMembers(instanceName, (call) => builtins_1.isBuiltinInstanceMemberCall(call) || builtins_1.isBuiltinInstanceMemberTemplate(call), (call) => (typescript_1.default.isCallExpression(expression) &&
builtins_1.isBuiltinInstanceMemberCall(call) &&
call.canCall(sb, expr, expression, optionsIn)) ||
(typescript_1.default.isTaggedTemplateExpression(expression) &&
builtins_1.isBuiltinInstanceMemberTemplate(call) &&
call.canCall(sb, expr, expression, optionsIn)), useSymbol)
.map(([propName, builtin]) => ({
condition: () => {
sb.emitOp(prop, 'DUP');
sb.emitPushString(prop, propName);
sb.emitOp(prop, 'EQUAL');
},
whenTrue: () => {
sb.emitOp(expr, 'DROP');
sb.emitOp(expr, 'DROP');
handleBuiltinMemberCall(builtin, expr, true);
},
}));
const throwInnerTypeError = (innerOptions) => {
sb.emitOp(expr, 'DROP');
sb.emitOp(expr, 'DROP');
throwTypeError(innerOptions);
};
const createHandleProp = (handleString, handleNumber, handleSymbol) => (innerOptions) => {
sb.visit(prop, innerOptions);
sb.emitHelper(prop, innerOptions, sb.helpers.forBuiltinType({
type: propType,
array: throwInnerTypeError,
arrayStorage: throwInnerTypeError,
boolean: throwInnerTypeError,
buffer: throwInnerTypeError,
null: throwInnerTypeError,
number: handleNumber,
object: throwInnerTypeError,
string: handleString,
symbol: handleSymbol,
undefined: throwInnerTypeError,
map: throwInnerTypeError,
mapStorage: throwInnerTypeError,
set: throwInnerTypeError,
setStorage: throwInnerTypeError,
error: throwInnerTypeError,
forwardValue: throwInnerTypeError,
iteratorResult: throwInnerTypeError,
iterable: throwInnerTypeError,
iterableIterator: throwInnerTypeError,
transaction: throwInnerTypeError,
output: throwInnerTypeError,
attribute: throwInnerTypeError,
input: throwInnerTypeError,
account: throwInnerTypeError,
asset: throwInnerTypeError,
contract: throwInnerTypeError,
header: throwInnerTypeError,
block: throwInnerTypeError,
}));
};
const createProcessBuiltin = (instanceName) => {
const handleStringBase = (innerInnerOptions) => {
sb.emitHelper(expr, innerInnerOptions, sb.helpers.case(getCallCases(instanceName, false), () => {
throwInnerTypeError(innerInnerOptions);
}));
};
const handleString = (innerInnerOptions) => {
sb.emitHelper(prop, innerInnerOptions, sb.helpers.unwrapString);
handleStringBase(innerInnerOptions);
};
const handleNumber = (innerInnerOptions) => {
sb.emitHelper(prop, innerInnerOptions, sb.helpers.toString({ type: propType, knownType: constants_1.Types.Number }));
handleStringBase(innerInnerOptions);
};
const handleSymbol = (innerInnerOptions) => {
sb.emitHelper(prop, innerInnerOptions, sb.helpers.unwrapSymbol);
sb.emitHelper(expr, innerInnerOptions, sb.helpers.case(getCallCases(instanceName, true), () => {
throwInnerTypeError(innerInnerOptions);
}));
};
return createHandleProp(handleString, handleNumber, handleSymbol);
};
const createProcessArray = () => {
const handleNumberBase = (innerInnerOptions) => {
sb.emitOp(expr, 'NIP');
handleArguments(innerInnerOptions);
sb.emitOp(expr, 'ROT');
sb.emitOp(expr, 'ROT');
sb.emitOp(expr, 'OVER');
sb.emitOp(expr, 'SWAP');
sb.emitHelper(expr, innerInnerOptions, sb.helpers.getArrayIndex);
sb.emitHelper(expr, optionsIn, sb.helpers.invokeCall({ bindThis: true }));
};
const handleString = (innerInnerOptions) => {
sb.emitHelper(prop, innerInnerOptions, sb.helpers.unwrapString);
sb.emitHelper(expr, innerInnerOptions, sb.helpers.case(getCallCases('Array', false), () => {
sb.emitHelper(prop, innerInnerOptions, sb.helpers.wrapString);
sb.emitHelper(prop, innerInnerOptions, sb.helpers.toNumber({ type: propType, knownType: constants_1.Types.String }));
handleNumberBase(innerInnerOptions);
}));
};
const handleNumber = (innerInnerOptions) => {
sb.emitHelper(prop, innerInnerOptions, sb.helpers.unwrapNumber);
handleNumberBase(innerInnerOptions);
};
const handleSymbol = (innerInnerOptions) => {
sb.emitHelper(prop, innerInnerOptions, sb.helpers.unwrapSymbol);
sb.emitHelper(expr, innerInnerOptions, sb.helpers.case(getCallCases('Array', true), () => {
throwInnerTypeError(innerInnerOptions);
}));
};
return createHandleProp(handleString, handleNumber, handleSymbol);
};
const processObject = (innerOptions) => {
const handleStringBase = (innerInnerOptions) => {
sb.emitHelper(expr, innerInnerOptions, sb.helpers.getPropertyObjectProperty);
isOptionalChain
?
callIfNotNullOrUndefined({
innerOptions,
argsNum: 3,
callback: () => sb.emitHelper(expr, optionsIn, sb.helpers.invokeCall({ bindThis: true })),
})
: sb.emitHelper(expr, optionsIn, sb.helpers.invokeCall({ bindThis: true }));
};
const handleNumber = (innerInnerOptions) => {
sb.emitHelper(prop, innerInnerOptions, sb.helpers.toString({ type: propType, knownType: constants_1.Types.Number }));
handleStringBase(innerInnerOptions);
};
const handleString = (innerInnerOptions) => {
sb.emitHelper(prop, innerInnerOptions, sb.helpers.unwrapString);
handleStringBase(innerInnerOptions);
};
const handleSymbol = (innerInnerOptions) => {
sb.emitHelper(prop, innerInnerOptions, sb.helpers.unwrapSymbol);
sb.emitHelper(expr, innerInnerOptions, sb.helpers.getSymbolObjectProperty);
isOptionalChain
?
callIfNotNullOrUndefined({
innerOptions,
argsNum: 3,
callback: () => sb.emitHelper(expr, optionsIn, sb.helpers.invokeCall({ bindThis: true })),
})
: sb.emitHelper(expr, optionsIn, sb.helpers.invokeCall({ bindThis: true }));
};
handleArguments(innerOptions);
sb.emitOp(expression, 'ROT');
sb.emitOp(expression, 'ROT');
sb.visit(prop, innerOptions);
sb.emitHelper(prop, innerOptions, sb.helpers.forBuiltinType({
type: propType,
array: throwInnerTypeError,
arrayStorage: throwInnerTypeError,
boolean: throwInnerTypeError,
buffer: throwInnerTypeError,
null: throwInnerTypeError,
number: handleNumber,
object: throwInnerTypeError,
string: handleString,
symbol: handleSymbol,
undefined: throwInnerTypeError,
map: throwInnerTypeError,
mapStorage: throwInnerTypeError,
set: throwInnerTypeError,
setStorage: throwInnerTypeError,
error: throwInnerTypeError,
forwardValue: throwInnerTypeError,
iteratorResult: throwInnerTypeError,
iterable: throwInnerTypeError,
iterableIterator: throwInnerTypeError,
transaction: throwInnerTypeError,
output: throwInnerTypeError,
attribute: throwInnerTypeError,
input: throwInnerTypeError,
account: throwInnerTypeError,
asset: throwInnerTypeError,
contract: throwInnerTypeError,
header: throwInnerTypeError,
block: throwInnerTypeError,
}));
};
const options = sb.pushValueOptions(sb.noSetValueOptions(optionsIn));
handlePropertyVisit(value, options);
sb.emitHelper(value, options, sb.helpers.forBuiltinType({
type: valueType,
array: createProcessArray(),
arrayStorage: createProcessBuiltin('ArrayStorage'),
boolean: createProcessBuiltin('Boolean'),
buffer: createProcessBuiltin('Buffer'),
null: throwTypeError,
number: createProcessBuiltin('Number'),
object: processObject,
string: createProcessBuiltin('String'),
symbol: createProcessBuiltin('Symbol'),
undefined: throwTypeError,
map: createProcessBuiltin('Map'),
mapStorage: createProcessBuiltin('MapStorage'),
set: createProcessBuiltin('Set'),
setStorage: createProcessBuiltin('SetStorage'),
error: createProcessBuiltin('Error'),
forwardValue: createProcessBuiltin('ForwardValue'),
iteratorResult: createProcessBuiltin('IteratorResult'),
iterable: createProcessBuiltin('Iterable'),
iterableIterator: createProcessBuiltin('IterableIterator'),
transaction: createProcessBuiltin('TransactionBase'),
output: createProcessBuiltin('Output'),
attribute: createProcessBuiltin('AttributeBase'),
input: createProcessBuiltin('Input'),
account: createProcessBuiltin('Account'),
asset: createProcessBuiltin('Asset'),
contract: createProcessBuiltin('Contract'),
header: createProcessBuiltin('Header'),
block: createProcessBuiltin('Block'),
}));
}
else if (typescript_1.default.isCallExpression(expression)) {
const value = ts_utils_1.tsUtils.expression.getExpression(expression);
const valueType = sb.context.analysis.getType(value);
const processCall = () => {
sb.emitHelper(expr, optionsIn, sb.helpers.invokeCall({ bindThis: false }));
};
const options = sb.pushValueOptions(sb.noSetValueOptions(optionsIn));
handleArguments(options);
sb.visit(expr, options);
sb.emitHelper(value, options, sb.helpers.forBuiltinType({
type: valueType,
array: throwTypeError,
arrayStorage: throwTypeError,
boolean: throwTypeError,
buffer: throwTypeError,
null: throwTypeErrorUnlessOptionalChain,
number: throwTypeError,
object: processCall,
string: throwTypeError,
symbol: throwTypeError,
undefined: throwTypeErrorUnlessOptionalChain,
map: throwTypeError,
mapStorage: throwTypeError,
set: throwTypeError,
setStorage: throwTypeError,
error: throwTypeError,
forwardValue: throwTypeError,
iteratorResult: throwTypeError,
iterable: throwTypeError,
iterableIterator: throwTypeError,
transaction: throwTypeError,
output: throwTypeError,
attribute: throwTypeError,
input: throwTypeError,
account: throwTypeError,
asset: throwTypeError,
contract: throwTypeError,
header: throwTypeError,
block: throwTypeError,
}));
}
else {
const options = sb.pushValueOptions(sb.noSetValueOptions(optionsIn));
handleArguments(options);
sb.visit(expr, options);
sb.emitHelper(expr, optionsIn, sb.helpers.invokeCall({ bindThis: false }));
}
}
}
exports.CallLikeHelper = CallLikeHelper;
//# sourceMappingURL=CallLikeHelper.js.map