blob: 71df4e14da828de95b1449c21d0a9c096206a3e4 [file] [log] [blame]
// Copyright (c) 2013-2020 Sandstorm Development Group, Inc. and contributors
// Licensed under the MIT License:
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#include "generics.h"
#include "parser.h" // for expressionString()
namespace capnp {
namespace compiler {
BrandedDecl::BrandedDecl(BrandedDecl& other)
: body(other.body),
source(other.source) {
if (body.is<Resolver::ResolvedDecl>()) {
brand = kj::addRef(*other.brand);
}
}
BrandedDecl& BrandedDecl::operator=(BrandedDecl& other) {
body = other.body;
source = other.source;
if (body.is<Resolver::ResolvedDecl>()) {
brand = kj::addRef(*other.brand);
}
return *this;
}
kj::Maybe<BrandedDecl> BrandedDecl::applyParams(
kj::Array<BrandedDecl> params, Expression::Reader subSource) {
if (body.is<Resolver::ResolvedParameter>()) {
return nullptr;
} else {
return brand->setParams(kj::mv(params), body.get<Resolver::ResolvedDecl>().kind, subSource)
.map([&](kj::Own<BrandScope>&& scope) {
BrandedDecl result = *this;
result.brand = kj::mv(scope);
result.source = subSource;
return result;
});
}
}
kj::Maybe<BrandedDecl> BrandedDecl::getMember(
kj::StringPtr memberName, Expression::Reader subSource) {
if (body.is<Resolver::ResolvedParameter>()) {
return nullptr;
} else KJ_IF_MAYBE(r, body.get<Resolver::ResolvedDecl>().resolver->resolveMember(memberName)) {
return brand->interpretResolve(*body.get<Resolver::ResolvedDecl>().resolver, *r, subSource);
} else {
return nullptr;
}
}
kj::Maybe<Declaration::Which> BrandedDecl::getKind() {
if (body.is<Resolver::ResolvedParameter>()) {
return nullptr;
} else {
return body.get<Resolver::ResolvedDecl>().kind;
}
}
kj::Maybe<BrandedDecl&> BrandedDecl::getListParam() {
KJ_REQUIRE(body.is<Resolver::ResolvedDecl>());
auto& decl = body.get<Resolver::ResolvedDecl>();
KJ_REQUIRE(decl.kind == Declaration::BUILTIN_LIST);
auto params = KJ_ASSERT_NONNULL(brand->getParams(decl.id));
if (params.size() != 1) {
return nullptr;
} else {
return params[0];
}
}
Resolver::ResolvedParameter BrandedDecl::asVariable() {
KJ_REQUIRE(body.is<Resolver::ResolvedParameter>());
return body.get<Resolver::ResolvedParameter>();
}
bool BrandedDecl::compileAsType(
ErrorReporter& errorReporter, schema::Type::Builder target) {
KJ_IF_MAYBE(kind, getKind()) {
switch (*kind) {
case Declaration::ENUM: {
auto enum_ = target.initEnum();
enum_.setTypeId(getIdAndFillBrand([&]() { return enum_.initBrand(); }));
return true;
}
case Declaration::STRUCT: {
auto struct_ = target.initStruct();
struct_.setTypeId(getIdAndFillBrand([&]() { return struct_.initBrand(); }));
return true;
}
case Declaration::INTERFACE: {
auto interface = target.initInterface();
interface.setTypeId(getIdAndFillBrand([&]() { return interface.initBrand(); }));
return true;
}
case Declaration::BUILTIN_LIST: {
auto elementType = target.initList().initElementType();
KJ_IF_MAYBE(param, getListParam()) {
if (!param->compileAsType(errorReporter, elementType)) {
return false;
}
} else {
addError(errorReporter, "'List' requires exactly one parameter.");
return false;
}
if (elementType.isAnyPointer()) {
auto unconstrained = elementType.getAnyPointer().getUnconstrained();
if (unconstrained.isAnyKind()) {
addError(errorReporter, "'List(AnyPointer)' is not supported.");
// Seeing List(AnyPointer) later can mess things up, so change the type to Void.
elementType.setVoid();
return false;
} else if (unconstrained.isStruct()) {
addError(errorReporter, "'List(AnyStruct)' is not supported.");
// Seeing List(AnyStruct) later can mess things up, so change the type to Void.
elementType.setVoid();
return false;
}
}
return true;
}
case Declaration::BUILTIN_VOID: target.setVoid(); return true;
case Declaration::BUILTIN_BOOL: target.setBool(); return true;
case Declaration::BUILTIN_INT8: target.setInt8(); return true;
case Declaration::BUILTIN_INT16: target.setInt16(); return true;
case Declaration::BUILTIN_INT32: target.setInt32(); return true;
case Declaration::BUILTIN_INT64: target.setInt64(); return true;
case Declaration::BUILTIN_U_INT8: target.setUint8(); return true;
case Declaration::BUILTIN_U_INT16: target.setUint16(); return true;
case Declaration::BUILTIN_U_INT32: target.setUint32(); return true;
case Declaration::BUILTIN_U_INT64: target.setUint64(); return true;
case Declaration::BUILTIN_FLOAT32: target.setFloat32(); return true;
case Declaration::BUILTIN_FLOAT64: target.setFloat64(); return true;
case Declaration::BUILTIN_TEXT: target.setText(); return true;
case Declaration::BUILTIN_DATA: target.setData(); return true;
case Declaration::BUILTIN_OBJECT:
addError(errorReporter,
"As of Cap'n Proto 0.4, 'Object' has been renamed to 'AnyPointer'. Sorry for the "
"inconvenience, and thanks for being an early adopter. :)");
KJ_FALLTHROUGH;
case Declaration::BUILTIN_ANY_POINTER:
target.initAnyPointer().initUnconstrained().setAnyKind();
return true;
case Declaration::BUILTIN_ANY_STRUCT:
target.initAnyPointer().initUnconstrained().setStruct();
return true;
case Declaration::BUILTIN_ANY_LIST:
target.initAnyPointer().initUnconstrained().setList();
return true;
case Declaration::BUILTIN_CAPABILITY:
target.initAnyPointer().initUnconstrained().setCapability();
return true;
case Declaration::FILE:
case Declaration::USING:
case Declaration::CONST:
case Declaration::ENUMERANT:
case Declaration::FIELD:
case Declaration::UNION:
case Declaration::GROUP:
case Declaration::METHOD:
case Declaration::ANNOTATION:
case Declaration::NAKED_ID:
case Declaration::NAKED_ANNOTATION:
addError(errorReporter, kj::str("'", toString(), "' is not a type."));
return false;
}
KJ_UNREACHABLE;
} else {
// Oh, this is a type variable.
auto var = asVariable();
if (var.id == 0) {
// This is actually a method implicit parameter.
auto builder = target.initAnyPointer().initImplicitMethodParameter();
builder.setParameterIndex(var.index);
return true;
} else {
auto builder = target.initAnyPointer().initParameter();
builder.setScopeId(var.id);
builder.setParameterIndex(var.index);
return true;
}
}
}
Resolver::ResolveResult BrandedDecl::asResolveResult(
uint64_t scopeId, schema::Brand::Builder brandBuilder) {
auto result = body;
if (result.is<Resolver::ResolvedDecl>()) {
// May need to compile our context as the "brand".
result.get<Resolver::ResolvedDecl>().scopeId = scopeId;
getIdAndFillBrand([&]() {
result.get<Resolver::ResolvedDecl>().brand = brandBuilder.asReader();
return brandBuilder;
});
}
return result;
}
kj::String BrandedDecl::toString() {
return expressionString(source);
}
kj::String BrandedDecl::toDebugString() {
if (body.is<Resolver::ResolvedParameter>()) {
auto variable = body.get<Resolver::ResolvedParameter>();
return kj::str("variable(", variable.id, ", ", variable.index, ")");
} else {
auto decl = body.get<Resolver::ResolvedDecl>();
return kj::str("decl(", decl.id, ", ", (uint)decl.kind, "')");
}
}
BrandScope::BrandScope(ErrorReporter& errorReporter, uint64_t startingScopeId,
uint startingScopeParamCount, Resolver& startingScope)
: errorReporter(errorReporter), parent(nullptr), leafId(startingScopeId),
leafParamCount(startingScopeParamCount), inherited(true) {
// Create all lexical parent scopes, all with no brand bindings.
KJ_IF_MAYBE(p, startingScope.getParent()) {
parent = kj::refcounted<BrandScope>(
errorReporter, p->id, p->genericParamCount, *p->resolver);
}
}
bool BrandScope::isGeneric() {
if (leafParamCount > 0) return true;
KJ_IF_MAYBE(p, parent) {
return p->get()->isGeneric();
} else {
return false;
}
}
kj::Own<BrandScope> BrandScope::push(uint64_t typeId, uint paramCount) {
return kj::refcounted<BrandScope>(kj::addRef(*this), typeId, paramCount);
}
kj::Maybe<kj::Own<BrandScope>> BrandScope::setParams(
kj::Array<BrandedDecl> params, Declaration::Which genericType, Expression::Reader source) {
if (this->params.size() != 0) {
errorReporter.addErrorOn(source, "Double-application of generic parameters.");
return nullptr;
} else if (params.size() > leafParamCount) {
if (leafParamCount == 0) {
errorReporter.addErrorOn(source, "Declaration does not accept generic parameters.");
} else {
errorReporter.addErrorOn(source, "Too many generic parameters.");
}
return nullptr;
} else if (params.size() < leafParamCount) {
errorReporter.addErrorOn(source, "Not enough generic parameters.");
return nullptr;
} else {
if (genericType != Declaration::BUILTIN_LIST) {
for (auto& param: params) {
KJ_IF_MAYBE(kind, param.getKind()) {
switch (*kind) {
case Declaration::BUILTIN_LIST:
case Declaration::BUILTIN_TEXT:
case Declaration::BUILTIN_DATA:
case Declaration::BUILTIN_ANY_POINTER:
case Declaration::STRUCT:
case Declaration::INTERFACE:
break;
default:
param.addError(errorReporter,
"Sorry, only pointer types can be used as generic parameters.");
break;
}
}
}
}
return kj::refcounted<BrandScope>(*this, kj::mv(params));
}
}
kj::Own<BrandScope> BrandScope::pop(uint64_t newLeafId) {
if (leafId == newLeafId) {
return kj::addRef(*this);
}
KJ_IF_MAYBE(p, parent) {
return (*p)->pop(newLeafId);
} else {
// Looks like we're moving into a whole top-level scope.
return kj::refcounted<BrandScope>(errorReporter, newLeafId);
}
}
kj::Maybe<BrandedDecl> BrandScope::lookupParameter(
Resolver& resolver, uint64_t scopeId, uint index) {
// Returns null if the param should be inherited from the client scope.
if (scopeId == leafId) {
if (index < params.size()) {
return params[index];
} else if (inherited) {
return nullptr;
} else {
// Unbound and not inherited, so return AnyPointer.
auto decl = resolver.resolveBuiltin(Declaration::BUILTIN_ANY_POINTER);
return BrandedDecl(decl,
evaluateBrand(resolver, decl, List<schema::Brand::Scope>::Reader()),
Expression::Reader());
}
} else KJ_IF_MAYBE(p, parent) {
return p->get()->lookupParameter(resolver, scopeId, index);
} else {
KJ_FAIL_REQUIRE("scope is not a parent");
}
}
kj::Maybe<kj::ArrayPtr<BrandedDecl>> BrandScope::getParams(uint64_t scopeId) {
// Returns null if params at the requested scope should be inherited from the client scope.
if (scopeId == leafId) {
if (inherited) {
return nullptr;
} else {
return params.asPtr();
}
} else KJ_IF_MAYBE(p, parent) {
return p->get()->getParams(scopeId);
} else {
KJ_FAIL_REQUIRE("scope is not a parent");
}
}
BrandedDecl BrandScope::interpretResolve(
Resolver& resolver, Resolver::ResolveResult& result, Expression::Reader source) {
if (result.is<Resolver::ResolvedDecl>()) {
auto& decl = result.get<Resolver::ResolvedDecl>();
auto scope = pop(decl.scopeId);
KJ_IF_MAYBE(brand, decl.brand) {
scope = scope->evaluateBrand(resolver, decl, brand->getScopes());
} else {
scope = scope->push(decl.id, decl.genericParamCount);
}
return BrandedDecl(decl, kj::mv(scope), source);
} else {
auto& param = result.get<Resolver::ResolvedParameter>();
KJ_IF_MAYBE(p, lookupParameter(resolver, param.id, param.index)) {
return *p;
} else {
return BrandedDecl(param, source);
}
}
}
kj::Own<BrandScope> BrandScope::evaluateBrand(
Resolver& resolver, Resolver::ResolvedDecl decl,
List<schema::Brand::Scope>::Reader brand, uint index) {
auto result = kj::refcounted<BrandScope>(errorReporter, decl.id);
result->leafParamCount = decl.genericParamCount;
// Fill in `params`.
if (index < brand.size()) {
auto nextScope = brand[index];
if (decl.id == nextScope.getScopeId()) {
// Initialize our parameters.
switch (nextScope.which()) {
case schema::Brand::Scope::BIND: {
auto bindings = nextScope.getBind();
auto params = kj::heapArrayBuilder<BrandedDecl>(bindings.size());
for (auto binding: bindings) {
switch (binding.which()) {
case schema::Brand::Binding::UNBOUND: {
// Build an AnyPointer-equivalent.
auto anyPointerDecl = resolver.resolveBuiltin(Declaration::BUILTIN_ANY_POINTER);
params.add(BrandedDecl(anyPointerDecl,
kj::refcounted<BrandScope>(errorReporter, anyPointerDecl.scopeId),
Expression::Reader()));
break;
}
case schema::Brand::Binding::TYPE:
// Reverse this schema::Type back into a BrandedDecl.
params.add(decompileType(resolver, binding.getType()));
break;
}
}
result->params = params.finish();
break;
}
case schema::Brand::Scope::INHERIT:
KJ_IF_MAYBE(p, getParams(decl.id)) {
result->params = kj::heapArray(*p);
} else {
result->inherited = true;
}
break;
}
// Parent should start one level deeper in the list.
++index;
}
}
// Fill in `parent`.
KJ_IF_MAYBE(parent, decl.resolver->getParent()) {
result->parent = evaluateBrand(resolver, *parent, brand, index);
}
return result;
}
BrandedDecl BrandScope::decompileType(
Resolver& resolver, schema::Type::Reader type) {
auto builtin = [&](Declaration::Which which) -> BrandedDecl {
auto decl = resolver.resolveBuiltin(which);
return BrandedDecl(decl,
evaluateBrand(resolver, decl, List<schema::Brand::Scope>::Reader()),
Expression::Reader());
};
switch (type.which()) {
case schema::Type::VOID: return builtin(Declaration::BUILTIN_VOID);
case schema::Type::BOOL: return builtin(Declaration::BUILTIN_BOOL);
case schema::Type::INT8: return builtin(Declaration::BUILTIN_INT8);
case schema::Type::INT16: return builtin(Declaration::BUILTIN_INT16);
case schema::Type::INT32: return builtin(Declaration::BUILTIN_INT32);
case schema::Type::INT64: return builtin(Declaration::BUILTIN_INT64);
case schema::Type::UINT8: return builtin(Declaration::BUILTIN_U_INT8);
case schema::Type::UINT16: return builtin(Declaration::BUILTIN_U_INT16);
case schema::Type::UINT32: return builtin(Declaration::BUILTIN_U_INT32);
case schema::Type::UINT64: return builtin(Declaration::BUILTIN_U_INT64);
case schema::Type::FLOAT32: return builtin(Declaration::BUILTIN_FLOAT32);
case schema::Type::FLOAT64: return builtin(Declaration::BUILTIN_FLOAT64);
case schema::Type::TEXT: return builtin(Declaration::BUILTIN_TEXT);
case schema::Type::DATA: return builtin(Declaration::BUILTIN_DATA);
case schema::Type::ENUM: {
auto enumType = type.getEnum();
Resolver::ResolvedDecl decl = resolver.resolveId(enumType.getTypeId());
return BrandedDecl(decl,
evaluateBrand(resolver, decl, enumType.getBrand().getScopes()),
Expression::Reader());
}
case schema::Type::INTERFACE: {
auto interfaceType = type.getInterface();
Resolver::ResolvedDecl decl = resolver.resolveId(interfaceType.getTypeId());
return BrandedDecl(decl,
evaluateBrand(resolver, decl, interfaceType.getBrand().getScopes()),
Expression::Reader());
}
case schema::Type::STRUCT: {
auto structType = type.getStruct();
Resolver::ResolvedDecl decl = resolver.resolveId(structType.getTypeId());
return BrandedDecl(decl,
evaluateBrand(resolver, decl, structType.getBrand().getScopes()),
Expression::Reader());
}
case schema::Type::LIST: {
auto elementType = decompileType(resolver, type.getList().getElementType());
return KJ_ASSERT_NONNULL(builtin(Declaration::BUILTIN_LIST)
.applyParams(kj::heapArray(&elementType, 1), Expression::Reader()));
}
case schema::Type::ANY_POINTER: {
auto anyPointer = type.getAnyPointer();
switch (anyPointer.which()) {
case schema::Type::AnyPointer::UNCONSTRAINED:
return builtin(Declaration::BUILTIN_ANY_POINTER);
case schema::Type::AnyPointer::PARAMETER: {
auto param = anyPointer.getParameter();
auto id = param.getScopeId();
uint index = param.getParameterIndex();
KJ_IF_MAYBE(binding, lookupParameter(resolver, id, index)) {
return *binding;
} else {
return BrandedDecl(Resolver::ResolvedParameter {id, index}, Expression::Reader());
}
}
case schema::Type::AnyPointer::IMPLICIT_METHOD_PARAMETER:
KJ_FAIL_ASSERT("Alias pointed to implicit method type parameter?");
}
KJ_UNREACHABLE;
}
}
KJ_UNREACHABLE;
}
kj::Maybe<BrandedDecl> BrandScope::compileDeclExpression(
Expression::Reader source, Resolver& resolver,
ImplicitParams implicitMethodParams) {
switch (source.which()) {
case Expression::UNKNOWN:
// Error reported earlier.
return nullptr;
case Expression::POSITIVE_INT:
case Expression::NEGATIVE_INT:
case Expression::FLOAT:
case Expression::STRING:
case Expression::BINARY:
case Expression::LIST:
case Expression::TUPLE:
case Expression::EMBED:
errorReporter.addErrorOn(source, "Expected name.");
return nullptr;
case Expression::RELATIVE_NAME: {
auto name = source.getRelativeName();
auto nameValue = name.getValue();
// Check implicit method params first.
for (auto i: kj::indices(implicitMethodParams.params)) {
if (implicitMethodParams.params[i].getName() == nameValue) {
if (implicitMethodParams.scopeId == 0) {
return BrandedDecl::implicitMethodParam(i);
} else {
return BrandedDecl(Resolver::ResolvedParameter {
implicitMethodParams.scopeId, static_cast<uint16_t>(i) },
Expression::Reader());
}
}
}
KJ_IF_MAYBE(r, resolver.resolve(nameValue)) {
return interpretResolve(resolver, *r, source);
} else {
errorReporter.addErrorOn(name, kj::str("Not defined: ", nameValue));
return nullptr;
}
}
case Expression::ABSOLUTE_NAME: {
auto name = source.getAbsoluteName();
KJ_IF_MAYBE(r, resolver.getTopScope().resolver->resolveMember(name.getValue())) {
return interpretResolve(resolver, *r, source);
} else {
errorReporter.addErrorOn(name, kj::str("Not defined: ", name.getValue()));
return nullptr;
}
}
case Expression::IMPORT: {
auto filename = source.getImport();
KJ_IF_MAYBE(decl, resolver.resolveImport(filename.getValue())) {
// Import is always a root scope, so create a fresh BrandScope.
return BrandedDecl(*decl, kj::refcounted<BrandScope>(
errorReporter, decl->id, decl->genericParamCount, *decl->resolver), source);
} else {
errorReporter.addErrorOn(filename, kj::str("Import failed: ", filename.getValue()));
return nullptr;
}
}
case Expression::APPLICATION: {
auto app = source.getApplication();
KJ_IF_MAYBE(decl, compileDeclExpression(app.getFunction(), resolver, implicitMethodParams)) {
// Compile all params.
auto params = app.getParams();
auto compiledParams = kj::heapArrayBuilder<BrandedDecl>(params.size());
bool paramFailed = false;
for (auto param: params) {
if (param.isNamed()) {
errorReporter.addErrorOn(param.getNamed(), "Named parameter not allowed here.");
}
KJ_IF_MAYBE(d, compileDeclExpression(param.getValue(), resolver, implicitMethodParams)) {
compiledParams.add(kj::mv(*d));
} else {
// Param failed to compile. Error was already reported.
paramFailed = true;
}
};
if (paramFailed) {
return kj::mv(*decl);
}
// Add the parameters to the brand.
KJ_IF_MAYBE(applied, decl->applyParams(compiledParams.finish(), source)) {
return kj::mv(*applied);
} else {
// Error already reported. Ignore parameters.
return kj::mv(*decl);
}
} else {
// error already reported
return nullptr;
}
}
case Expression::MEMBER: {
auto member = source.getMember();
KJ_IF_MAYBE(decl, compileDeclExpression(member.getParent(), resolver, implicitMethodParams)) {
auto name = member.getName();
KJ_IF_MAYBE(memberDecl, decl->getMember(name.getValue(), source)) {
return kj::mv(*memberDecl);
} else {
errorReporter.addErrorOn(name, kj::str(
"'", expressionString(member.getParent()),
"' has no member named '", name.getValue(), "'"));
return nullptr;
}
} else {
// error already reported
return nullptr;
}
}
}
KJ_UNREACHABLE;
}
} // namespace compiler
} // namespace capnp