implements PR025
This commit is contained in:
parent
0770bb869a
commit
52e8746bcc
@ -1,6 +1,7 @@
|
|||||||
package p.studio.compiler.pbs;
|
package p.studio.compiler.pbs;
|
||||||
|
|
||||||
import p.studio.compiler.models.IRFunction;
|
import p.studio.compiler.models.IRFunction;
|
||||||
|
import p.studio.compiler.models.SourceKind;
|
||||||
import p.studio.compiler.models.IRBackendFile;
|
import p.studio.compiler.models.IRBackendFile;
|
||||||
import p.studio.compiler.pbs.ast.PbsAst;
|
import p.studio.compiler.pbs.ast.PbsAst;
|
||||||
import p.studio.compiler.pbs.lexer.PbsLexer;
|
import p.studio.compiler.pbs.lexer.PbsLexer;
|
||||||
@ -21,10 +22,21 @@ public final class PbsFrontendCompiler {
|
|||||||
final FileId fileId,
|
final FileId fileId,
|
||||||
final String source,
|
final String source,
|
||||||
final DiagnosticSink diagnostics) {
|
final DiagnosticSink diagnostics) {
|
||||||
|
return compileFile(fileId, source, diagnostics, SourceKind.PROJECT);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IRBackendFile compileFile(
|
||||||
|
final FileId fileId,
|
||||||
|
final String source,
|
||||||
|
final DiagnosticSink diagnostics,
|
||||||
|
final SourceKind sourceKind) {
|
||||||
final var admissionBaseline = diagnostics.errorCount();
|
final var admissionBaseline = diagnostics.errorCount();
|
||||||
final var tokens = PbsLexer.lex(source, fileId, diagnostics);
|
final var tokens = PbsLexer.lex(source, fileId, diagnostics);
|
||||||
final var ast = PbsParser.parse(tokens, fileId, diagnostics);
|
final var parseMode = sourceKind == SourceKind.SDK_INTERFACE
|
||||||
final var irBackendFile = compileParsedFile(fileId, ast, diagnostics);
|
? PbsParser.ParseMode.INTERFACE_MODULE
|
||||||
|
: PbsParser.ParseMode.ORDINARY;
|
||||||
|
final var ast = PbsParser.parse(tokens, fileId, diagnostics, parseMode);
|
||||||
|
final var irBackendFile = compileParsedFile(fileId, ast, diagnostics, sourceKind);
|
||||||
if (diagnostics.errorCount() > admissionBaseline) {
|
if (diagnostics.errorCount() > admissionBaseline) {
|
||||||
return IRBackendFile.empty(fileId);
|
return IRBackendFile.empty(fileId);
|
||||||
}
|
}
|
||||||
@ -35,8 +47,16 @@ public final class PbsFrontendCompiler {
|
|||||||
final FileId fileId,
|
final FileId fileId,
|
||||||
final PbsAst.File ast,
|
final PbsAst.File ast,
|
||||||
final DiagnosticSink diagnostics) {
|
final DiagnosticSink diagnostics) {
|
||||||
|
return compileParsedFile(fileId, ast, diagnostics, SourceKind.PROJECT);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IRBackendFile compileParsedFile(
|
||||||
|
final FileId fileId,
|
||||||
|
final PbsAst.File ast,
|
||||||
|
final DiagnosticSink diagnostics,
|
||||||
|
final SourceKind sourceKind) {
|
||||||
final var semanticsErrorBaseline = diagnostics.errorCount();
|
final var semanticsErrorBaseline = diagnostics.errorCount();
|
||||||
declarationSemanticsValidator.validate(ast, diagnostics);
|
declarationSemanticsValidator.validate(ast, sourceKind, diagnostics);
|
||||||
flowSemanticsValidator.validate(ast, diagnostics);
|
flowSemanticsValidator.validate(ast, diagnostics);
|
||||||
if (diagnostics.errorCount() > semanticsErrorBaseline) {
|
if (diagnostics.errorCount() > semanticsErrorBaseline) {
|
||||||
return IRBackendFile.empty(fileId);
|
return IRBackendFile.empty(fileId);
|
||||||
|
|||||||
@ -245,11 +245,12 @@ public final class PbsModuleVisibilityValidator {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(pbs-interface-modules): include top-level host declarations when interface-module mode is added.
|
|
||||||
if (topDecl instanceof PbsAst.StructDecl structDecl) {
|
if (topDecl instanceof PbsAst.StructDecl structDecl) {
|
||||||
registerNonFunctionDeclaration(declarations, NonFunctionKind.STRUCT, structDecl.name(), structDecl.span(), nameTable);
|
registerNonFunctionDeclaration(declarations, NonFunctionKind.STRUCT, structDecl.name(), structDecl.span(), nameTable);
|
||||||
} else if (topDecl instanceof PbsAst.ContractDecl contractDecl) {
|
} else if (topDecl instanceof PbsAst.ContractDecl contractDecl) {
|
||||||
registerNonFunctionDeclaration(declarations, NonFunctionKind.CONTRACT, contractDecl.name(), contractDecl.span(), nameTable);
|
registerNonFunctionDeclaration(declarations, NonFunctionKind.CONTRACT, contractDecl.name(), contractDecl.span(), nameTable);
|
||||||
|
} else if (topDecl instanceof PbsAst.HostDecl hostDecl) {
|
||||||
|
registerNonFunctionDeclaration(declarations, NonFunctionKind.HOST, hostDecl.name(), hostDecl.span(), nameTable);
|
||||||
} else if (topDecl instanceof PbsAst.ErrorDecl errorDecl) {
|
} else if (topDecl instanceof PbsAst.ErrorDecl errorDecl) {
|
||||||
registerNonFunctionDeclaration(declarations, NonFunctionKind.ERROR, errorDecl.name(), errorDecl.span(), nameTable);
|
registerNonFunctionDeclaration(declarations, NonFunctionKind.ERROR, errorDecl.name(), errorDecl.span(), nameTable);
|
||||||
} else if (topDecl instanceof PbsAst.EnumDecl enumDecl) {
|
} else if (topDecl instanceof PbsAst.EnumDecl enumDecl) {
|
||||||
|
|||||||
@ -27,6 +27,14 @@ record PbsCallableScope(
|
|||||||
return new PbsCallableScope("contract signature scope", "contract", ownerNameId);
|
return new PbsCallableScope("contract signature scope", "contract", ownerNameId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static PbsCallableScope hostMethods(final NameId ownerNameId) {
|
||||||
|
return new PbsCallableScope("host method scope", "host", ownerNameId);
|
||||||
|
}
|
||||||
|
|
||||||
|
static PbsCallableScope builtinMethods(final NameId ownerNameId) {
|
||||||
|
return new PbsCallableScope("builtin method scope", "builtin", ownerNameId);
|
||||||
|
}
|
||||||
|
|
||||||
static PbsCallableScope implementsMethods(final NameId ownerNameId) {
|
static PbsCallableScope implementsMethods(final NameId ownerNameId) {
|
||||||
return new PbsCallableScope("implements method scope", "implements", ownerNameId);
|
return new PbsCallableScope("implements method scope", "implements", ownerNameId);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -133,6 +133,12 @@ final class PbsDeclarationRuleValidator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void validateConstDeclaration(final PbsAst.ConstDecl constDecl) {
|
void validateConstDeclaration(final PbsAst.ConstDecl constDecl) {
|
||||||
|
validateConstDeclaration(constDecl, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void validateConstDeclaration(
|
||||||
|
final PbsAst.ConstDecl constDecl,
|
||||||
|
final boolean requireInitializer) {
|
||||||
if (constDecl.explicitType() == null) {
|
if (constDecl.explicitType() == null) {
|
||||||
p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics,
|
p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics,
|
||||||
PbsSemanticsErrors.E_SEM_MISSING_CONST_TYPE_ANNOTATION.name(),
|
PbsSemanticsErrors.E_SEM_MISSING_CONST_TYPE_ANNOTATION.name(),
|
||||||
@ -146,7 +152,7 @@ final class PbsDeclarationRuleValidator {
|
|||||||
diagnostics);
|
diagnostics);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (constDecl.initializer() == null) {
|
if (requireInitializer && constDecl.initializer() == null) {
|
||||||
p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics,
|
p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics,
|
||||||
PbsSemanticsErrors.E_SEM_MISSING_CONST_INITIALIZER.name(),
|
PbsSemanticsErrors.E_SEM_MISSING_CONST_INITIALIZER.name(),
|
||||||
"Non-builtin const declaration '%s' must include an initializer".formatted(constDecl.name()),
|
"Non-builtin const declaration '%s' must include an initializer".formatted(constDecl.name()),
|
||||||
|
|||||||
@ -1,21 +1,56 @@
|
|||||||
package p.studio.compiler.pbs.semantics;
|
package p.studio.compiler.pbs.semantics;
|
||||||
|
|
||||||
|
import p.studio.compiler.models.SourceKind;
|
||||||
import p.studio.compiler.pbs.ast.PbsAst;
|
import p.studio.compiler.pbs.ast.PbsAst;
|
||||||
import p.studio.compiler.source.Span;
|
import p.studio.compiler.source.Span;
|
||||||
import p.studio.compiler.source.diagnostics.DiagnosticSink;
|
import p.studio.compiler.source.diagnostics.DiagnosticSink;
|
||||||
import p.studio.compiler.source.tables.NameTable;
|
import p.studio.compiler.source.tables.NameTable;
|
||||||
import p.studio.utilities.structures.ReadOnlyList;
|
import p.studio.utilities.structures.ReadOnlyList;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
public final class PbsDeclarationSemanticsValidator {
|
public final class PbsDeclarationSemanticsValidator {
|
||||||
|
private static final String ATTR_HOST = "Host";
|
||||||
|
private static final String ATTR_BUILTIN_TYPE = "BuiltinType";
|
||||||
|
private static final String ATTR_BUILTIN_CONST = "BuiltinConst";
|
||||||
|
private static final String ATTR_INTRINSIC_CALL = "IntrinsicCall";
|
||||||
|
private static final String ATTR_SLOT = "Slot";
|
||||||
|
|
||||||
|
private static final Set<String> RESERVED_ATTRIBUTES = Set.of(
|
||||||
|
ATTR_HOST,
|
||||||
|
ATTR_BUILTIN_TYPE,
|
||||||
|
ATTR_BUILTIN_CONST,
|
||||||
|
ATTR_INTRINSIC_CALL,
|
||||||
|
ATTR_SLOT);
|
||||||
|
|
||||||
private final NameTable nameTable = new NameTable();
|
private final NameTable nameTable = new NameTable();
|
||||||
private final PbsConstSemanticsValidator constSemanticsValidator = new PbsConstSemanticsValidator();
|
private final PbsConstSemanticsValidator constSemanticsValidator = new PbsConstSemanticsValidator();
|
||||||
|
|
||||||
public void validate(final PbsAst.File ast, final DiagnosticSink diagnostics) {
|
public void validate(final PbsAst.File ast, final DiagnosticSink diagnostics) {
|
||||||
|
validate(ast, SourceKind.PROJECT, diagnostics);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void validate(
|
||||||
|
final PbsAst.File ast,
|
||||||
|
final SourceKind sourceKind,
|
||||||
|
final DiagnosticSink diagnostics) {
|
||||||
final var binder = new PbsNamespaceBinder(nameTable, diagnostics);
|
final var binder = new PbsNamespaceBinder(nameTable, diagnostics);
|
||||||
final var rules = new PbsDeclarationRuleValidator(nameTable, diagnostics);
|
final var rules = new PbsDeclarationRuleValidator(nameTable, diagnostics);
|
||||||
|
final var interfaceModule = sourceKind == SourceKind.SDK_INTERFACE;
|
||||||
|
|
||||||
for (final var topDecl : ast.topDecls()) {
|
for (final var topDecl : ast.topDecls()) {
|
||||||
if (topDecl instanceof PbsAst.FunctionDecl functionDecl) {
|
if (topDecl instanceof PbsAst.FunctionDecl functionDecl) {
|
||||||
|
if (interfaceModule) {
|
||||||
|
reportInterfaceNonDeclarativeDecl(
|
||||||
|
functionDecl.span(),
|
||||||
|
"Top-level functions are not allowed in interface modules",
|
||||||
|
diagnostics);
|
||||||
|
}
|
||||||
validateCallable(
|
validateCallable(
|
||||||
PbsCallableScope.topLevelFunctions(),
|
PbsCallableScope.topLevelFunctions(),
|
||||||
functionDecl.name(),
|
functionDecl.name(),
|
||||||
@ -32,6 +67,12 @@ public final class PbsDeclarationSemanticsValidator {
|
|||||||
|
|
||||||
if (topDecl instanceof PbsAst.StructDecl structDecl) {
|
if (topDecl instanceof PbsAst.StructDecl structDecl) {
|
||||||
binder.registerType(structDecl.name(), structDecl.span(), "struct");
|
binder.registerType(structDecl.name(), structDecl.span(), "struct");
|
||||||
|
if (interfaceModule && (structDecl.hasBody() || !structDecl.methods().isEmpty() || !structDecl.ctors().isEmpty())) {
|
||||||
|
reportInterfaceNonDeclarativeDecl(
|
||||||
|
structDecl.span(),
|
||||||
|
"Interface modules must not declare executable struct bodies",
|
||||||
|
diagnostics);
|
||||||
|
}
|
||||||
validateStructDeclaration(structDecl, binder, rules, diagnostics);
|
validateStructDeclaration(structDecl, binder, rules, diagnostics);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -39,16 +80,39 @@ public final class PbsDeclarationSemanticsValidator {
|
|||||||
if (topDecl instanceof PbsAst.ServiceDecl serviceDecl) {
|
if (topDecl instanceof PbsAst.ServiceDecl serviceDecl) {
|
||||||
binder.registerType(serviceDecl.name(), serviceDecl.span(), "service");
|
binder.registerType(serviceDecl.name(), serviceDecl.span(), "service");
|
||||||
binder.registerValue(serviceDecl.name(), serviceDecl.span(), "service singleton");
|
binder.registerValue(serviceDecl.name(), serviceDecl.span(), "service singleton");
|
||||||
|
if (interfaceModule) {
|
||||||
|
reportInterfaceNonDeclarativeDecl(
|
||||||
|
serviceDecl.span(),
|
||||||
|
"Interface modules must not declare executable service bodies",
|
||||||
|
diagnostics);
|
||||||
|
}
|
||||||
validateServiceDeclaration(serviceDecl, binder, rules);
|
validateServiceDeclaration(serviceDecl, binder, rules);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (topDecl instanceof PbsAst.ContractDecl contractDecl) {
|
if (topDecl instanceof PbsAst.ContractDecl contractDecl) {
|
||||||
binder.registerType(contractDecl.name(), contractDecl.span(), "contract");
|
binder.registerType(contractDecl.name(), contractDecl.span(), "contract");
|
||||||
|
if (interfaceModule) {
|
||||||
|
for (final var signature : contractDecl.signatures()) {
|
||||||
|
validateReservedAttributeTarget(signature.attributes(), "contract signature", diagnostics);
|
||||||
|
}
|
||||||
|
}
|
||||||
validateContractDeclaration(contractDecl, binder, rules);
|
validateContractDeclaration(contractDecl, binder, rules);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (topDecl instanceof PbsAst.HostDecl hostDecl) {
|
||||||
|
binder.registerHostOwner(hostDecl.name(), hostDecl.span(), "host owner");
|
||||||
|
validateHostDeclaration(hostDecl, binder, rules, interfaceModule, diagnostics);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (topDecl instanceof PbsAst.BuiltinTypeDecl builtinTypeDecl) {
|
||||||
|
binder.registerType(builtinTypeDecl.name(), builtinTypeDecl.span(), "builtin type");
|
||||||
|
validateBuiltinTypeDeclaration(builtinTypeDecl, binder, rules, interfaceModule, diagnostics);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (topDecl instanceof PbsAst.ErrorDecl errorDecl) {
|
if (topDecl instanceof PbsAst.ErrorDecl errorDecl) {
|
||||||
binder.registerType(errorDecl.name(), errorDecl.span(), "error");
|
binder.registerType(errorDecl.name(), errorDecl.span(), "error");
|
||||||
rules.validateErrorDeclaration(errorDecl);
|
rules.validateErrorDeclaration(errorDecl);
|
||||||
@ -69,11 +133,18 @@ public final class PbsDeclarationSemanticsValidator {
|
|||||||
|
|
||||||
if (topDecl instanceof PbsAst.ConstDecl constDecl) {
|
if (topDecl instanceof PbsAst.ConstDecl constDecl) {
|
||||||
binder.registerValue(constDecl.name(), constDecl.span(), "const");
|
binder.registerValue(constDecl.name(), constDecl.span(), "const");
|
||||||
rules.validateConstDeclaration(constDecl);
|
final var allowMissingInitializer = validateConstDeclarationAttributes(constDecl, interfaceModule, diagnostics);
|
||||||
|
rules.validateConstDeclaration(constDecl, !allowMissingInitializer);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (topDecl instanceof PbsAst.ImplementsDecl implementsDecl) {
|
if (topDecl instanceof PbsAst.ImplementsDecl implementsDecl) {
|
||||||
|
if (interfaceModule) {
|
||||||
|
reportInterfaceNonDeclarativeDecl(
|
||||||
|
implementsDecl.span(),
|
||||||
|
"Interface modules must not declare executable 'implements' blocks",
|
||||||
|
diagnostics);
|
||||||
|
}
|
||||||
validateImplementsDeclaration(implementsDecl, binder, rules);
|
validateImplementsDeclaration(implementsDecl, binder, rules);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -164,6 +235,80 @@ public final class PbsDeclarationSemanticsValidator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void validateHostDeclaration(
|
||||||
|
final PbsAst.HostDecl hostDecl,
|
||||||
|
final PbsNamespaceBinder binder,
|
||||||
|
final PbsDeclarationRuleValidator rules,
|
||||||
|
final boolean interfaceModule,
|
||||||
|
final DiagnosticSink diagnostics) {
|
||||||
|
if (!interfaceModule) {
|
||||||
|
reportInterfaceNonDeclarativeDecl(
|
||||||
|
hostDecl.span(),
|
||||||
|
"'declare host' is valid only in interface-module sources",
|
||||||
|
diagnostics);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final var signatureScope = PbsCallableScope.hostMethods(nameTable.register(hostDecl.name()));
|
||||||
|
for (final var signature : hostDecl.signatures()) {
|
||||||
|
validateCallable(
|
||||||
|
signatureScope,
|
||||||
|
signature.name(),
|
||||||
|
signature.parameters(),
|
||||||
|
signature.returnKind(),
|
||||||
|
signature.returnType(),
|
||||||
|
signature.resultErrorType(),
|
||||||
|
signature.span(),
|
||||||
|
false,
|
||||||
|
binder,
|
||||||
|
rules);
|
||||||
|
validateHostSignatureAttributes(signature, diagnostics);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateBuiltinTypeDeclaration(
|
||||||
|
final PbsAst.BuiltinTypeDecl builtinTypeDecl,
|
||||||
|
final PbsNamespaceBinder binder,
|
||||||
|
final PbsDeclarationRuleValidator rules,
|
||||||
|
final boolean interfaceModule,
|
||||||
|
final DiagnosticSink diagnostics) {
|
||||||
|
if (!interfaceModule) {
|
||||||
|
reportInterfaceNonDeclarativeDecl(
|
||||||
|
builtinTypeDecl.span(),
|
||||||
|
"'declare builtin type' is valid only in interface-module sources",
|
||||||
|
diagnostics);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
validateBuiltinTypeAttribute(builtinTypeDecl, diagnostics);
|
||||||
|
|
||||||
|
final var fieldTypes = new ArrayList<PbsAst.TypeRef>(builtinTypeDecl.fields().size());
|
||||||
|
for (final var field : builtinTypeDecl.fields()) {
|
||||||
|
fieldTypes.add(field.typeRef());
|
||||||
|
validateBuiltinFieldAttributes(field, diagnostics);
|
||||||
|
}
|
||||||
|
rules.validateTypeSurfaces(
|
||||||
|
fieldTypes,
|
||||||
|
"builtin type '%s'".formatted(builtinTypeDecl.name()),
|
||||||
|
false);
|
||||||
|
|
||||||
|
final var methodScope = PbsCallableScope.builtinMethods(nameTable.register(builtinTypeDecl.name()));
|
||||||
|
for (final var signature : builtinTypeDecl.signatures()) {
|
||||||
|
validateCallable(
|
||||||
|
methodScope,
|
||||||
|
signature.name(),
|
||||||
|
signature.parameters(),
|
||||||
|
signature.returnKind(),
|
||||||
|
signature.returnType(),
|
||||||
|
signature.resultErrorType(),
|
||||||
|
signature.span(),
|
||||||
|
false,
|
||||||
|
binder,
|
||||||
|
rules);
|
||||||
|
validateIntrinsicCallAttributes(signature, diagnostics);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void validateImplementsDeclaration(
|
private void validateImplementsDeclaration(
|
||||||
final PbsAst.ImplementsDecl implementsDecl,
|
final PbsAst.ImplementsDecl implementsDecl,
|
||||||
final PbsNamespaceBinder binder,
|
final PbsNamespaceBinder binder,
|
||||||
@ -185,6 +330,404 @@ public final class PbsDeclarationSemanticsValidator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean validateConstDeclarationAttributes(
|
||||||
|
final PbsAst.ConstDecl constDecl,
|
||||||
|
final boolean interfaceModule,
|
||||||
|
final DiagnosticSink diagnostics) {
|
||||||
|
final var builtinConstAttributes = attributesNamed(constDecl.attributes(), ATTR_BUILTIN_CONST);
|
||||||
|
for (final var attribute : constDecl.attributes()) {
|
||||||
|
if (!isReservedAttribute(attribute.name())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (ATTR_BUILTIN_CONST.equals(attribute.name())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
reportInvalidReservedAttributeTarget(
|
||||||
|
attribute,
|
||||||
|
"Attribute '%s' is not valid on const declarations".formatted(attribute.name()),
|
||||||
|
diagnostics);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!interfaceModule && !builtinConstAttributes.isEmpty()) {
|
||||||
|
for (final var attribute : builtinConstAttributes) {
|
||||||
|
reportInvalidReservedAttributeTarget(
|
||||||
|
attribute,
|
||||||
|
"'BuiltinConst' is valid only in interface-module const declarations",
|
||||||
|
diagnostics);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (builtinConstAttributes.size() > 1) {
|
||||||
|
for (int i = 1; i < builtinConstAttributes.size(); i++) {
|
||||||
|
reportDuplicateReservedAttribute(builtinConstAttributes.get(i), ATTR_BUILTIN_CONST, diagnostics);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (builtinConstAttributes.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
validateBuiltinConstAttributeShape(builtinConstAttributes.getFirst(), diagnostics);
|
||||||
|
if (constDecl.initializer() != null) {
|
||||||
|
p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics,
|
||||||
|
PbsSemanticsErrors.E_SEM_MALFORMED_RESERVED_ATTRIBUTE.name(),
|
||||||
|
"Const declaration '%s' with BuiltinConst metadata must omit initializer".formatted(constDecl.name()),
|
||||||
|
constDecl.span());
|
||||||
|
}
|
||||||
|
return interfaceModule;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateHostSignatureAttributes(
|
||||||
|
final PbsAst.FunctionSignature signature,
|
||||||
|
final DiagnosticSink diagnostics) {
|
||||||
|
final var hostAttributes = attributesNamed(signature.attributes(), ATTR_HOST);
|
||||||
|
for (final var attribute : signature.attributes()) {
|
||||||
|
if (!isReservedAttribute(attribute.name())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (ATTR_HOST.equals(attribute.name())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
reportInvalidReservedAttributeTarget(
|
||||||
|
attribute,
|
||||||
|
"Attribute '%s' is not valid on host signatures".formatted(attribute.name()),
|
||||||
|
diagnostics);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hostAttributes.isEmpty()) {
|
||||||
|
p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics,
|
||||||
|
PbsSemanticsErrors.E_SEM_MISSING_REQUIRED_RESERVED_ATTRIBUTE.name(),
|
||||||
|
"Host signature '%s' must carry exactly one Host attribute".formatted(signature.name()),
|
||||||
|
signature.span());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hostAttributes.size() > 1) {
|
||||||
|
for (int i = 1; i < hostAttributes.size(); i++) {
|
||||||
|
reportDuplicateReservedAttribute(hostAttributes.get(i), ATTR_HOST, diagnostics);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
validateHostAttributeShape(hostAttributes.getFirst(), diagnostics);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateBuiltinTypeAttribute(
|
||||||
|
final PbsAst.BuiltinTypeDecl builtinTypeDecl,
|
||||||
|
final DiagnosticSink diagnostics) {
|
||||||
|
final var builtinTypeAttributes = attributesNamed(builtinTypeDecl.attributes(), ATTR_BUILTIN_TYPE);
|
||||||
|
for (final var attribute : builtinTypeDecl.attributes()) {
|
||||||
|
if (!isReservedAttribute(attribute.name())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (ATTR_BUILTIN_TYPE.equals(attribute.name())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
reportInvalidReservedAttributeTarget(
|
||||||
|
attribute,
|
||||||
|
"Attribute '%s' is not valid on builtin type declarations".formatted(attribute.name()),
|
||||||
|
diagnostics);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (builtinTypeAttributes.isEmpty()) {
|
||||||
|
p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics,
|
||||||
|
PbsSemanticsErrors.E_SEM_MISSING_REQUIRED_RESERVED_ATTRIBUTE.name(),
|
||||||
|
"Builtin type '%s' must carry exactly one BuiltinType attribute".formatted(builtinTypeDecl.name()),
|
||||||
|
builtinTypeDecl.span());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (builtinTypeAttributes.size() > 1) {
|
||||||
|
for (int i = 1; i < builtinTypeAttributes.size(); i++) {
|
||||||
|
reportDuplicateReservedAttribute(builtinTypeAttributes.get(i), ATTR_BUILTIN_TYPE, diagnostics);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
validateBuiltinTypeAttributeShape(builtinTypeAttributes.getFirst(), diagnostics);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateBuiltinFieldAttributes(
|
||||||
|
final PbsAst.BuiltinFieldDecl field,
|
||||||
|
final DiagnosticSink diagnostics) {
|
||||||
|
final var slotAttributes = attributesNamed(field.attributes(), ATTR_SLOT);
|
||||||
|
for (final var attribute : field.attributes()) {
|
||||||
|
if (!isReservedAttribute(attribute.name())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (ATTR_SLOT.equals(attribute.name())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
reportInvalidReservedAttributeTarget(
|
||||||
|
attribute,
|
||||||
|
"Attribute '%s' is not valid on builtin fields".formatted(attribute.name()),
|
||||||
|
diagnostics);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (slotAttributes.size() > 1) {
|
||||||
|
for (int i = 1; i < slotAttributes.size(); i++) {
|
||||||
|
reportDuplicateReservedAttribute(slotAttributes.get(i), ATTR_SLOT, diagnostics);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!slotAttributes.isEmpty()) {
|
||||||
|
validateSlotAttributeShape(slotAttributes.getFirst(), diagnostics);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateIntrinsicCallAttributes(
|
||||||
|
final PbsAst.FunctionSignature signature,
|
||||||
|
final DiagnosticSink diagnostics) {
|
||||||
|
final var intrinsicAttributes = attributesNamed(signature.attributes(), ATTR_INTRINSIC_CALL);
|
||||||
|
for (final var attribute : signature.attributes()) {
|
||||||
|
if (!isReservedAttribute(attribute.name())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (ATTR_INTRINSIC_CALL.equals(attribute.name())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
reportInvalidReservedAttributeTarget(
|
||||||
|
attribute,
|
||||||
|
"Attribute '%s' is not valid on builtin method signatures".formatted(attribute.name()),
|
||||||
|
diagnostics);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (intrinsicAttributes.size() > 1) {
|
||||||
|
for (int i = 1; i < intrinsicAttributes.size(); i++) {
|
||||||
|
reportDuplicateReservedAttribute(intrinsicAttributes.get(i), ATTR_INTRINSIC_CALL, diagnostics);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!intrinsicAttributes.isEmpty()) {
|
||||||
|
validateIntrinsicCallAttributeShape(intrinsicAttributes.getFirst(), diagnostics);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateHostAttributeShape(
|
||||||
|
final PbsAst.Attribute attribute,
|
||||||
|
final DiagnosticSink diagnostics) {
|
||||||
|
final var args = validateNamedArguments(attribute, Set.of("module", "name", "version"), Set.of(), diagnostics);
|
||||||
|
if (args == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
validateRequiredStringArgument(attribute, args, "module", false, diagnostics);
|
||||||
|
validateRequiredStringArgument(attribute, args, "name", false, diagnostics);
|
||||||
|
validateRequiredIntArgument(attribute, args, "version", true, diagnostics);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateBuiltinTypeAttributeShape(
|
||||||
|
final PbsAst.Attribute attribute,
|
||||||
|
final DiagnosticSink diagnostics) {
|
||||||
|
final var args = validateNamedArguments(attribute, Set.of("name", "version"), Set.of(), diagnostics);
|
||||||
|
if (args == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
validateRequiredStringArgument(attribute, args, "name", false, diagnostics);
|
||||||
|
validateRequiredIntArgument(attribute, args, "version", true, diagnostics);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateBuiltinConstAttributeShape(
|
||||||
|
final PbsAst.Attribute attribute,
|
||||||
|
final DiagnosticSink diagnostics) {
|
||||||
|
final var args = validateNamedArguments(attribute, Set.of("target", "name", "version"), Set.of(), diagnostics);
|
||||||
|
if (args == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
validateRequiredStringArgument(attribute, args, "target", false, diagnostics);
|
||||||
|
validateRequiredStringArgument(attribute, args, "name", false, diagnostics);
|
||||||
|
validateRequiredIntArgument(attribute, args, "version", true, diagnostics);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateIntrinsicCallAttributeShape(
|
||||||
|
final PbsAst.Attribute attribute,
|
||||||
|
final DiagnosticSink diagnostics) {
|
||||||
|
final var args = validateNamedArguments(attribute, Set.of("name"), Set.of("version"), diagnostics);
|
||||||
|
if (args == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
validateRequiredStringArgument(attribute, args, "name", false, diagnostics);
|
||||||
|
if (args.containsKey("version")) {
|
||||||
|
validateRequiredIntArgument(attribute, args, "version", true, diagnostics);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateSlotAttributeShape(
|
||||||
|
final PbsAst.Attribute attribute,
|
||||||
|
final DiagnosticSink diagnostics) {
|
||||||
|
final var args = validateNamedArguments(attribute, Set.of("index"), Set.of(), diagnostics);
|
||||||
|
if (args == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
validateRequiredIntArgument(attribute, args, "index", false, diagnostics);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, PbsAst.AttributeValue> validateNamedArguments(
|
||||||
|
final PbsAst.Attribute attribute,
|
||||||
|
final Set<String> requiredNames,
|
||||||
|
final Set<String> optionalNames,
|
||||||
|
final DiagnosticSink diagnostics) {
|
||||||
|
final var allowedNames = new HashSet<String>();
|
||||||
|
allowedNames.addAll(requiredNames);
|
||||||
|
allowedNames.addAll(optionalNames);
|
||||||
|
|
||||||
|
final var arguments = new HashMap<String, PbsAst.AttributeValue>();
|
||||||
|
var malformed = false;
|
||||||
|
for (final var argument : attribute.arguments()) {
|
||||||
|
if (!allowedNames.contains(argument.name())) {
|
||||||
|
malformed = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (arguments.putIfAbsent(argument.name(), argument.value()) != null) {
|
||||||
|
malformed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (final var requiredName : requiredNames) {
|
||||||
|
if (!arguments.containsKey(requiredName)) {
|
||||||
|
malformed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (malformed) {
|
||||||
|
p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics,
|
||||||
|
PbsSemanticsErrors.E_SEM_MALFORMED_RESERVED_ATTRIBUTE.name(),
|
||||||
|
"Malformed %s attribute argument set".formatted(attribute.name()),
|
||||||
|
attribute.span());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return arguments;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateRequiredStringArgument(
|
||||||
|
final PbsAst.Attribute attribute,
|
||||||
|
final Map<String, PbsAst.AttributeValue> arguments,
|
||||||
|
final String argumentName,
|
||||||
|
final boolean positiveOnly,
|
||||||
|
final DiagnosticSink diagnostics) {
|
||||||
|
final var value = arguments.get(argumentName);
|
||||||
|
if (!(value instanceof PbsAst.AttributeStringValue stringValue)) {
|
||||||
|
p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics,
|
||||||
|
PbsSemanticsErrors.E_SEM_MALFORMED_RESERVED_ATTRIBUTE.name(),
|
||||||
|
"Malformed %s.%s attribute argument".formatted(attribute.name(), argumentName),
|
||||||
|
attribute.span());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isStringLiteralEmpty(stringValue.value())) {
|
||||||
|
p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics,
|
||||||
|
PbsSemanticsErrors.E_SEM_MALFORMED_RESERVED_ATTRIBUTE.name(),
|
||||||
|
"Attribute %s.%s must be a non-empty string".formatted(attribute.name(), argumentName),
|
||||||
|
attribute.span());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (positiveOnly && stringValue.value().isBlank()) {
|
||||||
|
p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics,
|
||||||
|
PbsSemanticsErrors.E_SEM_MALFORMED_RESERVED_ATTRIBUTE.name(),
|
||||||
|
"Attribute %s.%s must be a positive string literal".formatted(attribute.name(), argumentName),
|
||||||
|
attribute.span());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateRequiredIntArgument(
|
||||||
|
final PbsAst.Attribute attribute,
|
||||||
|
final Map<String, PbsAst.AttributeValue> arguments,
|
||||||
|
final String argumentName,
|
||||||
|
final boolean positive,
|
||||||
|
final DiagnosticSink diagnostics) {
|
||||||
|
final var value = arguments.get(argumentName);
|
||||||
|
if (!(value instanceof PbsAst.AttributeIntValue intValue)) {
|
||||||
|
p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics,
|
||||||
|
PbsSemanticsErrors.E_SEM_MALFORMED_RESERVED_ATTRIBUTE.name(),
|
||||||
|
"Malformed %s.%s attribute argument".formatted(attribute.name(), argumentName),
|
||||||
|
attribute.span());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final var numericValue = intValue.value();
|
||||||
|
final boolean invalid = positive ? numericValue <= 0 : numericValue < 0;
|
||||||
|
if (invalid) {
|
||||||
|
p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics,
|
||||||
|
PbsSemanticsErrors.E_SEM_MALFORMED_RESERVED_ATTRIBUTE.name(),
|
||||||
|
"Attribute %s.%s has invalid numeric value".formatted(attribute.name(), argumentName),
|
||||||
|
attribute.span());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isStringLiteralEmpty(final String lexeme) {
|
||||||
|
if (lexeme == null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
final var trimmed = lexeme.trim();
|
||||||
|
if (trimmed.length() >= 2 && trimmed.startsWith("\"") && trimmed.endsWith("\"")) {
|
||||||
|
return trimmed.length() == 2;
|
||||||
|
}
|
||||||
|
return trimmed.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<PbsAst.Attribute> attributesNamed(
|
||||||
|
final ReadOnlyList<PbsAst.Attribute> attributes,
|
||||||
|
final String name) {
|
||||||
|
final var matches = new ArrayList<PbsAst.Attribute>();
|
||||||
|
for (final var attribute : attributes) {
|
||||||
|
if (name.equals(attribute.name())) {
|
||||||
|
matches.add(attribute);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return matches;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isReservedAttribute(final String name) {
|
||||||
|
return RESERVED_ATTRIBUTES.contains(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateReservedAttributeTarget(
|
||||||
|
final ReadOnlyList<PbsAst.Attribute> attributes,
|
||||||
|
final String targetSurface,
|
||||||
|
final DiagnosticSink diagnostics) {
|
||||||
|
for (final var attribute : attributes) {
|
||||||
|
if (!isReservedAttribute(attribute.name())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
reportInvalidReservedAttributeTarget(
|
||||||
|
attribute,
|
||||||
|
"Attribute '%s' is not valid on %s".formatted(attribute.name(), targetSurface),
|
||||||
|
diagnostics);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void reportInvalidReservedAttributeTarget(
|
||||||
|
final PbsAst.Attribute attribute,
|
||||||
|
final String message,
|
||||||
|
final DiagnosticSink diagnostics) {
|
||||||
|
p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics,
|
||||||
|
PbsSemanticsErrors.E_SEM_INVALID_RESERVED_ATTRIBUTE_TARGET.name(),
|
||||||
|
message,
|
||||||
|
attribute.span());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void reportDuplicateReservedAttribute(
|
||||||
|
final PbsAst.Attribute attribute,
|
||||||
|
final String attributeName,
|
||||||
|
final DiagnosticSink diagnostics) {
|
||||||
|
p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics,
|
||||||
|
PbsSemanticsErrors.E_SEM_DUPLICATE_RESERVED_ATTRIBUTE.name(),
|
||||||
|
"Duplicate %s attribute on the same declaration surface".formatted(attributeName),
|
||||||
|
attribute.span());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void reportInterfaceNonDeclarativeDecl(
|
||||||
|
final Span span,
|
||||||
|
final String message,
|
||||||
|
final DiagnosticSink diagnostics) {
|
||||||
|
p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics,
|
||||||
|
PbsSemanticsErrors.E_SEM_INTERFACE_NON_DECLARATIVE_DECLARATION.name(),
|
||||||
|
message,
|
||||||
|
span);
|
||||||
|
}
|
||||||
|
|
||||||
private void validateCallable(
|
private void validateCallable(
|
||||||
final PbsCallableScope scope,
|
final PbsCallableScope scope,
|
||||||
final String callableName,
|
final String callableName,
|
||||||
|
|||||||
@ -10,6 +10,11 @@ public enum PbsSemanticsErrors {
|
|||||||
E_SEM_DUPLICATE_ENUM_CASE_ID,
|
E_SEM_DUPLICATE_ENUM_CASE_ID,
|
||||||
E_SEM_INVALID_MIXED_OPTIONAL_RESULT_RETURN,
|
E_SEM_INVALID_MIXED_OPTIONAL_RESULT_RETURN,
|
||||||
E_SEM_INVALID_OPTIONAL_VOID_TYPE_SURFACE,
|
E_SEM_INVALID_OPTIONAL_VOID_TYPE_SURFACE,
|
||||||
|
E_SEM_INTERFACE_NON_DECLARATIVE_DECLARATION,
|
||||||
|
E_SEM_INVALID_RESERVED_ATTRIBUTE_TARGET,
|
||||||
|
E_SEM_MISSING_REQUIRED_RESERVED_ATTRIBUTE,
|
||||||
|
E_SEM_DUPLICATE_RESERVED_ATTRIBUTE,
|
||||||
|
E_SEM_MALFORMED_RESERVED_ATTRIBUTE,
|
||||||
E_SEM_MISSING_CONST_TYPE_ANNOTATION,
|
E_SEM_MISSING_CONST_TYPE_ANNOTATION,
|
||||||
E_SEM_MISSING_CONST_INITIALIZER,
|
E_SEM_MISSING_CONST_INITIALIZER,
|
||||||
E_SEM_CONST_NON_CONSTANT_INITIALIZER,
|
E_SEM_CONST_NON_CONSTANT_INITIALIZER,
|
||||||
|
|||||||
@ -134,7 +134,11 @@ public class PBSFrontendPhaseService implements FrontendPhaseService {
|
|||||||
if (failedModuleKeys.contains(parsedSource.moduleKey())) {
|
if (failedModuleKeys.contains(parsedSource.moduleKey())) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
final var irBackendFile = frontendCompiler.compileParsedFile(parsedSource.fileId(), parsedSource.ast(), diagnostics);
|
final var irBackendFile = frontendCompiler.compileParsedFile(
|
||||||
|
parsedSource.fileId(),
|
||||||
|
parsedSource.ast(),
|
||||||
|
diagnostics,
|
||||||
|
parsedSource.sourceKind());
|
||||||
if (parsedSource.sourceKind() == SourceKind.SDK_INTERFACE) {
|
if (parsedSource.sourceKind() == SourceKind.SDK_INTERFACE) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -215,6 +215,66 @@ class PbsModuleVisibilityTest {
|
|||||||
d.getCode().equals(PbsLinkErrors.E_LINK_UNRESOLVED_BARREL_ENTRY.name())));
|
d.getCode().equals(PbsLinkErrors.E_LINK_UNRESOLVED_BARREL_ENTRY.name())));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldResolveHostBarrelEntryWhenHostDeclarationExistsInInterfaceModule() {
|
||||||
|
final var diagnostics = DiagnosticSink.empty();
|
||||||
|
final var sourceFileId = new FileId(100);
|
||||||
|
final var barrelFileId = new FileId(101);
|
||||||
|
final var sourceAst = parseSource(
|
||||||
|
"""
|
||||||
|
declare host Gfx {
|
||||||
|
[Host(module = "gfx", name = "draw_pixel", version = 1)]
|
||||||
|
fn draw_pixel(x: int, y: int) -> void;
|
||||||
|
}
|
||||||
|
""",
|
||||||
|
sourceFileId,
|
||||||
|
diagnostics,
|
||||||
|
PbsParser.ParseMode.INTERFACE_MODULE);
|
||||||
|
final var barrelAst = parseBarrel(
|
||||||
|
"""
|
||||||
|
pub host Gfx;
|
||||||
|
""",
|
||||||
|
barrelFileId,
|
||||||
|
diagnostics);
|
||||||
|
final var module = new PbsModuleVisibilityValidator.ModuleUnit(
|
||||||
|
new PbsModuleVisibilityValidator.ModuleCoordinates("sdk", ReadOnlyList.wrap(List.of("gfx"))),
|
||||||
|
ReadOnlyList.wrap(List.of(new PbsModuleVisibilityValidator.SourceFile(sourceFileId, sourceAst))),
|
||||||
|
ReadOnlyList.wrap(List.of(new PbsModuleVisibilityValidator.BarrelFile(barrelFileId, barrelAst))));
|
||||||
|
|
||||||
|
new PbsModuleVisibilityValidator().validate(ReadOnlyList.wrap(List.of(module)), diagnostics);
|
||||||
|
|
||||||
|
assertTrue(diagnostics.stream().noneMatch(d ->
|
||||||
|
d.getCode().equals(PbsLinkErrors.E_LINK_UNRESOLVED_BARREL_ENTRY.name())));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldRejectHostBarrelEntryWhenHostDeclarationIsMissing() {
|
||||||
|
final var diagnostics = DiagnosticSink.empty();
|
||||||
|
final var sourceFileId = new FileId(110);
|
||||||
|
final var barrelFileId = new FileId(111);
|
||||||
|
final var sourceAst = parseSource(
|
||||||
|
"""
|
||||||
|
declare enum Color(Red);
|
||||||
|
""",
|
||||||
|
sourceFileId,
|
||||||
|
diagnostics);
|
||||||
|
final var barrelAst = parseBarrel(
|
||||||
|
"""
|
||||||
|
pub host Gfx;
|
||||||
|
""",
|
||||||
|
barrelFileId,
|
||||||
|
diagnostics);
|
||||||
|
final var module = new PbsModuleVisibilityValidator.ModuleUnit(
|
||||||
|
new PbsModuleVisibilityValidator.ModuleCoordinates("sdk", ReadOnlyList.wrap(List.of("gfx"))),
|
||||||
|
ReadOnlyList.wrap(List.of(new PbsModuleVisibilityValidator.SourceFile(sourceFileId, sourceAst))),
|
||||||
|
ReadOnlyList.wrap(List.of(new PbsModuleVisibilityValidator.BarrelFile(barrelFileId, barrelAst))));
|
||||||
|
|
||||||
|
new PbsModuleVisibilityValidator().validate(ReadOnlyList.wrap(List.of(module)), diagnostics);
|
||||||
|
|
||||||
|
assertTrue(diagnostics.stream().anyMatch(d ->
|
||||||
|
d.getCode().equals(PbsLinkErrors.E_LINK_UNRESOLVED_BARREL_ENTRY.name())));
|
||||||
|
}
|
||||||
|
|
||||||
private PbsModuleVisibilityValidator.ModuleUnit module(
|
private PbsModuleVisibilityValidator.ModuleUnit module(
|
||||||
final String project,
|
final String project,
|
||||||
final String modulePath,
|
final String modulePath,
|
||||||
@ -257,7 +317,15 @@ class PbsModuleVisibilityTest {
|
|||||||
final String source,
|
final String source,
|
||||||
final FileId fileId,
|
final FileId fileId,
|
||||||
final DiagnosticSink diagnostics) {
|
final DiagnosticSink diagnostics) {
|
||||||
return PbsParser.parse(PbsLexer.lex(source, fileId, diagnostics), fileId, diagnostics);
|
return parseSource(source, fileId, diagnostics, PbsParser.ParseMode.ORDINARY);
|
||||||
|
}
|
||||||
|
|
||||||
|
private PbsAst.File parseSource(
|
||||||
|
final String source,
|
||||||
|
final FileId fileId,
|
||||||
|
final DiagnosticSink diagnostics,
|
||||||
|
final PbsParser.ParseMode parseMode) {
|
||||||
|
return PbsParser.parse(PbsLexer.lex(source, fileId, diagnostics), fileId, diagnostics, parseMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
private PbsAst.BarrelFile parseBarrel(
|
private PbsAst.BarrelFile parseBarrel(
|
||||||
|
|||||||
@ -0,0 +1,101 @@
|
|||||||
|
package p.studio.compiler.pbs.semantics;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import p.studio.compiler.models.SourceKind;
|
||||||
|
import p.studio.compiler.pbs.PbsFrontendCompiler;
|
||||||
|
import p.studio.compiler.source.diagnostics.DiagnosticSink;
|
||||||
|
import p.studio.compiler.source.identifiers.FileId;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
class PbsInterfaceModuleSemanticsTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldAcceptValidInterfaceModuleReservedMetadataShapes() {
|
||||||
|
final var source = """
|
||||||
|
[BuiltinType(name = "color", version = 1)]
|
||||||
|
declare builtin type Color(
|
||||||
|
[Slot(index = 0)] pub raw: int
|
||||||
|
) {
|
||||||
|
[IntrinsicCall(name = "pack", version = 1)]
|
||||||
|
fn pack() -> int;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare host Gfx {
|
||||||
|
[Host(module = "gfx", name = "draw_pixel", version = 1)]
|
||||||
|
fn draw_pixel(x: int, y: int, c: Color) -> void;
|
||||||
|
}
|
||||||
|
|
||||||
|
[BuiltinConst(target = "color", name = "white", version = 1)]
|
||||||
|
declare const WHITE: Color;
|
||||||
|
""";
|
||||||
|
final var diagnostics = DiagnosticSink.empty();
|
||||||
|
|
||||||
|
new PbsFrontendCompiler().compileFile(new FileId(0), source, diagnostics, SourceKind.SDK_INTERFACE);
|
||||||
|
|
||||||
|
assertFalse(diagnostics.stream().anyMatch(d ->
|
||||||
|
d.getCode().equals(PbsSemanticsErrors.E_SEM_MISSING_REQUIRED_RESERVED_ATTRIBUTE.name())));
|
||||||
|
assertFalse(diagnostics.stream().anyMatch(d ->
|
||||||
|
d.getCode().equals(PbsSemanticsErrors.E_SEM_INVALID_RESERVED_ATTRIBUTE_TARGET.name())));
|
||||||
|
assertFalse(diagnostics.stream().anyMatch(d ->
|
||||||
|
d.getCode().equals(PbsSemanticsErrors.E_SEM_MALFORMED_RESERVED_ATTRIBUTE.name())));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldRejectInvalidReservedAttributeTargetsAndShapesInInterfaceModule() {
|
||||||
|
final var source = """
|
||||||
|
declare host Gfx {
|
||||||
|
fn draw_pixel(x: int, y: int) -> void;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare contract Api {
|
||||||
|
[Host(module = "gfx", name = "draw_pixel", version = 1)]
|
||||||
|
fn run() -> void;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare builtin type Color(pub raw: int) {
|
||||||
|
[IntrinsicCall(version = 1)]
|
||||||
|
fn pack() -> int;
|
||||||
|
}
|
||||||
|
|
||||||
|
[BuiltinConst(target = "", name = "white", version = 0)]
|
||||||
|
declare const WHITE: int = 1;
|
||||||
|
""";
|
||||||
|
final var diagnostics = DiagnosticSink.empty();
|
||||||
|
|
||||||
|
new PbsFrontendCompiler().compileFile(new FileId(0), source, diagnostics, SourceKind.SDK_INTERFACE);
|
||||||
|
|
||||||
|
assertTrue(diagnostics.stream().anyMatch(d ->
|
||||||
|
d.getCode().equals(PbsSemanticsErrors.E_SEM_MISSING_REQUIRED_RESERVED_ATTRIBUTE.name())));
|
||||||
|
assertTrue(diagnostics.stream().anyMatch(d ->
|
||||||
|
d.getCode().equals(PbsSemanticsErrors.E_SEM_INVALID_RESERVED_ATTRIBUTE_TARGET.name())));
|
||||||
|
assertTrue(diagnostics.stream().anyMatch(d ->
|
||||||
|
d.getCode().equals(PbsSemanticsErrors.E_SEM_MALFORMED_RESERVED_ATTRIBUTE.name())));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldRejectExecutableDeclarationsInInterfaceModule() {
|
||||||
|
final var source = """
|
||||||
|
fn run() -> int { return 1; }
|
||||||
|
|
||||||
|
declare service Game {
|
||||||
|
fn tick() -> int { return 1; }
|
||||||
|
}
|
||||||
|
|
||||||
|
declare contract C { fn run() -> int; }
|
||||||
|
declare struct S(v: int);
|
||||||
|
implements C for S using s {
|
||||||
|
fn run() -> int { return 1; }
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
final var diagnostics = DiagnosticSink.empty();
|
||||||
|
|
||||||
|
new PbsFrontendCompiler().compileFile(new FileId(0), source, diagnostics, SourceKind.SDK_INTERFACE);
|
||||||
|
|
||||||
|
final var nonDeclarativeCount = diagnostics.stream()
|
||||||
|
.filter(d -> d.getCode().equals(PbsSemanticsErrors.E_SEM_INTERFACE_NON_DECLARATIVE_DECLARATION.name()))
|
||||||
|
.count();
|
||||||
|
assertTrue(nonDeclarativeCount >= 3);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -13,6 +13,7 @@ import p.studio.compiler.pbs.stdlib.StdlibEnvironment;
|
|||||||
import p.studio.compiler.pbs.stdlib.StdlibEnvironmentResolver;
|
import p.studio.compiler.pbs.stdlib.StdlibEnvironmentResolver;
|
||||||
import p.studio.compiler.pbs.stdlib.StdlibModuleSource;
|
import p.studio.compiler.pbs.stdlib.StdlibModuleSource;
|
||||||
import p.studio.compiler.pbs.linking.PbsLinkErrors;
|
import p.studio.compiler.pbs.linking.PbsLinkErrors;
|
||||||
|
import p.studio.compiler.pbs.semantics.PbsSemanticsErrors;
|
||||||
import p.studio.compiler.source.diagnostics.DiagnosticSink;
|
import p.studio.compiler.source.diagnostics.DiagnosticSink;
|
||||||
import p.studio.compiler.source.identifiers.ProjectId;
|
import p.studio.compiler.source.identifiers.ProjectId;
|
||||||
import p.studio.compiler.source.tables.FileTable;
|
import p.studio.compiler.source.tables.FileTable;
|
||||||
@ -219,7 +220,8 @@ class PBSFrontendPhaseServiceTest {
|
|||||||
LogAggregator.empty(),
|
LogAggregator.empty(),
|
||||||
BuildingIssueSink.empty());
|
BuildingIssueSink.empty());
|
||||||
|
|
||||||
assertTrue(diagnostics.isEmpty());
|
assertTrue(diagnostics.stream().anyMatch(d ->
|
||||||
|
d.getCode().equals(PbsSemanticsErrors.E_SEM_INTERFACE_NON_DECLARATIVE_DECLARATION.name())));
|
||||||
assertEquals(0, irBackend.getFunctions().size());
|
assertEquals(0, irBackend.getFunctions().size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user