Skip to content

Commit

Permalink
Support Dynamic Dispatch Over Contract Impls Defined in Dep Modules
Browse files Browse the repository at this point in the history
This enables treating contract impls from anywhere in scope consistently.

Next on this front I'd really love to enable generic procedures with requires clauses to be called via oneof concrete type params if all combinations are valid contract impls.
  • Loading branch information
JasonSteving99 committed Oct 5, 2023
1 parent 361cfce commit 5c528c3
Show file tree
Hide file tree
Showing 6 changed files with 137 additions and 76 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,16 @@ greet(cat);

print("----------------------------------------------------------------------------------------------------");

# TODO(steving) The commented out code block below would be a demonstration of almost the full expressive power of the
# TODO(steving) typical OO-paradigm languages within Claro. There's nothing stopping these TODO's from being
# TODO(steving) resolved aside from just getting the time to get to these features. In fact, it should be somewhat
# TODO(steving) straightforward to resolve these.
#var animals: [oneof<Dog::Dog, Cat::Cat>] = [dog, cat];
#for (animal in animals) {
# # TODO(steving) Claro needs to support Dynamic Dispatch over contracts defined/implemented in dep modules.
# print(Greeter::Greeter::getGreeting(animal));
# The below code block is a demonstration of almost the full expressive power of the typical OO-paradigm languages
# within Claro.
var animals: [oneof<Dog::Dog, Cat::Cat>] = [dog, cat];
for (animal in animals) {
# Dynamic dispatch over the animals without using any notion of subtyping.
print(Greeter::Greeter::getGreeting(animal));
# # TODO(steving) Claro needs to relax generic procedure call checks to allow the call to be made in this situation.
# # TODO(steving) This is a complex situation to be sure, but the idea here is that since the concrete type given
# # TODO(steving) for the generic type param `T` is a oneof<> where, for all variants the required contract has been
# # TODO(steving) implemented, then by definition, Claro should allow the call and then the monomorphization will
# # TODO(steving) make use of dynamic dispatch over the oneof<>.
# greet(animal);
#}
}
Original file line number Diff line number Diff line change
Expand Up @@ -258,21 +258,30 @@ private void checkTypesAndGenJavaSourceForSrcFiles(
this.PACKAGE_STRING.ifPresent(s -> mainSrcFileParser.package_string = s.equals("") ? "" : "package " + s + ";\n\n");

try {
// Before even parsing the given .claro files, handle the given dependencies by accounting for all dep modules.
// I need to set up the ScopedHeap with all symbols exported by the direct module deps. Additionally, this is
// where the parsers will get configured with the necessary state to enable parsing references to bindings
// exported by the dep modules (e.g. `MyDep::foo(...)`) as module references rather than contract references.
setupModuleDepBindings(scopedHeap, this.MODULE_DEPS);
ImmutableList.Builder<ContractDefinitionStmt> importedContractDefinitionStmts = ImmutableList.builder();
// In addition to the direct module deps, I'll also need to setup the ScopedHeap with all
// types+initializers+unwrappers that were exported by any transitive dep modules exported by any of this module's
// direct dep modules. This is in order for compilation to comprehend the transitive exported types that direct
// dep modules are referencing in its exported types and procedure signatures.
for (SrcFile transitiveDepModuleSrcFile : this.TRANSITIVE_MODULE_DEPS) {
SerializedClaroModule parsedModule =
SerializedClaroModule.parseDelimitedFrom(transitiveDepModuleSrcFile.getFileInputStream());
registerDepModuleExportedTypes(scopedHeap, Optional.empty(), parsedModule);
registerDepModuleExportedTypeInitializersAndUnwrappers(scopedHeap, Optional.empty(), parsedModule);
{
ImmutableList.Builder<SerializedClaroModule> transitiveModules = ImmutableList.builder();
for (SrcFile transitiveDepModuleSrcFile : this.TRANSITIVE_MODULE_DEPS) {
SerializedClaroModule parsedModule =
SerializedClaroModule.parseDelimitedFrom(transitiveDepModuleSrcFile.getFileInputStream());
transitiveModules.add(parsedModule);
importedContractDefinitionStmts.addAll(
registerDepModuleExportedTypes(scopedHeap, Optional.empty(), parsedModule));
registerDepModuleExportedTypeInitializersAndUnwrappers(scopedHeap, Optional.empty(), parsedModule);
}
// Ensure the contract impls are all registered *after* modules are defined.
transitiveModules.build().forEach(p -> registerDepModuleContractImpls(scopedHeap, p));
}
// Before even parsing the given .claro files, handle the given dependencies by accounting for all dep modules.
// I need to set up the ScopedHeap with all symbols exported by the direct module deps. Additionally, this is
// where the parsers will get configured with the necessary state to enable parsing references to bindings
// exported by the dep modules (e.g. `MyDep::foo(...)`) as module references rather than contract references.
importedContractDefinitionStmts.addAll(
setupModuleDepBindings(scopedHeap, this.MODULE_DEPS));

// If this is compiled as a module, then to be safe to disambiguate types defined in other modules from this one
// I'll need to save the unique module name of this module under the special name $THIS_MODULE$.
Expand All @@ -294,6 +303,7 @@ private void checkTypesAndGenJavaSourceForSrcFiles(
}
// Push these parsed non-main src programs to where they'll be found for type checking and codegen.
ProgramNode.nonMainFiles = parsedNonMainSrcFilePrograms.build();
ProgramNode.importedContractDefinitionStmts = importedContractDefinitionStmts.build();
// Optionally push the module api file to where it'll be found during type checking to validate that the
// nonMainSrcFilePrograms actually do export the necessary bindings.
if (optionalModuleApiParser.isPresent()) {
Expand Down Expand Up @@ -393,18 +403,27 @@ private void checkTypesAndGenJavaSourceForSrcFiles(
"Internal Compiler Error! Should be unreachable. JavaSourceCompilerBackend failed to exit with explicit error code.");
}

private void setupModuleDepBindings(ScopedHeap scopedHeap, ImmutableMap<String, SrcFile> moduleDeps) throws Exception {
private ImmutableList<ContractDefinitionStmt> setupModuleDepBindings(
ScopedHeap scopedHeap, ImmutableMap<String, SrcFile> moduleDeps) throws Exception {
ImmutableMap.Builder<String, SerializedClaroModule> parsedClaroModuleProtosBuilder = ImmutableMap.builder();
ImmutableList.Builder<ContractDefinitionStmt> importedContractDefinitionStmts = ImmutableList.builder();
// Setup dep module exported types.
for (Map.Entry<String, SrcFile> moduleDep : moduleDeps.entrySet()) {
SerializedClaroModule parsedModule =
SerializedClaroModule.parseDelimitedFrom(moduleDep.getValue().getFileInputStream());
parsedClaroModuleProtosBuilder.put(moduleDep.getKey(), parsedModule);
{
ImmutableList.Builder<SerializedClaroModule> parsedModules = ImmutableList.builder();
for (Map.Entry<String, SrcFile> moduleDep : moduleDeps.entrySet()) {
SerializedClaroModule parsedModule =
SerializedClaroModule.parseDelimitedFrom(moduleDep.getValue().getFileInputStream());
parsedModules.add(parsedModule);
parsedClaroModuleProtosBuilder.put(moduleDep.getKey(), parsedModule);

// First thing, register this dep module somewhere central that can be referenced by both codegen and the parsers.
ScopedHeap.currProgramDepModules.put(moduleDep.getKey(), /*isUsed=*/false, parsedModule.getModuleDescriptor());
// First thing, register this dep module somewhere central that can be referenced by both codegen and the parsers.
ScopedHeap.currProgramDepModules.put(moduleDep.getKey(), /*isUsed=*/false, parsedModule.getModuleDescriptor());

registerDepModuleExportedTypes(scopedHeap, Optional.of(moduleDep.getKey()), parsedModule);
importedContractDefinitionStmts.addAll(
registerDepModuleExportedTypes(scopedHeap, Optional.of(moduleDep.getKey()), parsedModule));
}
// Ensure the contract impls are all registered *after* modules are defined.
parsedModules.build().forEach(p -> registerDepModuleContractImpls(scopedHeap, p));
}

// After having successfully parsed all dep module files and registered all of their exported types, it's time to
Expand Down Expand Up @@ -442,6 +461,8 @@ private void setupModuleDepBindings(ScopedHeap scopedHeap, ImmutableMap<String,
Map.Entry::getKey,
e -> e.getValue().getExportedTypeDefinitions()
));

return importedContractDefinitionStmts.build();
}

private static ProcedureDefinitionStmt syntheticProcedureDefStmt = null;
Expand Down Expand Up @@ -695,7 +716,7 @@ private static void registerDepModuleExportedTypeInitializersAndUnwrappers(
// transitive exported deps of those direct deps will be unnamed. This is in keeping with Claro enabling module
// consumers to actually utilize transitive exported types from dep modules w/o actually placing a direct dep on those
// modules so long as they never try to explicitly *name* any types from those transitive exported dep modules.
private static void registerDepModuleExportedTypes(
private static ImmutableList<ContractDefinitionStmt> registerDepModuleExportedTypes(
ScopedHeap scopedHeap, Optional<String> optionalModuleName, SerializedClaroModule parsedModule) {
// Register any alias defs found in the module.
for (Map.Entry<String, TypeProtos.TypeProto> exportedAliasDef :
Expand Down Expand Up @@ -764,6 +785,7 @@ private static void registerDepModuleExportedTypes(
parsedModule.getModuleDescriptor().getUniqueModuleName(), disambiguatedAtomIdentifier, i);
}

ImmutableList.Builder<ContractDefinitionStmt> importedContractDefinitionStmts = ImmutableList.builder();
// Register any ContractDefs found in the module.
parsedModule.getExportedContractDefinitionsList().forEach(
contractDef -> {
Expand Down Expand Up @@ -837,32 +859,12 @@ private static void registerDepModuleExportedTypes(
scopedHeap.markIdentifierUsed(normalizedProcedureName);
}
);
// Keep track of all of these generated ContractDefinitionStmts so that I can register any potential dynamic
// dispatch procedures later.
importedContractDefinitionStmts.add(contractDefinitionStmt);
}
);

// Register any contract impls found in the module.
parsedModule.getExportedContractImplementationsList().forEach(
contractImpl -> registerExportedContractImplementation(
contractImpl.getImplementedContractName(),
// TODO(steving) TESTING!!! UPDATE THIS, I NEED TO BE ABLE TO HANDLE THE CASE WHERE THE IMPLEMENTED
// CONTRACT WAS ACTUALLY DEFINED IN SOME *OTHER* TRANSITIVE DEP MODULE.
// NOTE: This will probably require some re-ordering such that all contracts from all modules are
// defined before any implementations are registered.
parsedModule.getModuleDescriptor().getProjectPackage(),
parsedModule.getModuleDescriptor().getUniqueModuleName(),
contractImpl.getConcreteTypeParamsList()
.stream()
.map(Types::parseTypeProto)
.collect(ImmutableList.toImmutableList()),
contractImpl.getConcreteSignaturesList().stream()
.collect(ImmutableMap.toImmutableMap(
SerializedClaroModule.Procedure::getName,
JavaSourceCompilerBackend::getProcedureTypeFromProto
)),
scopedHeap
)
);

// Register any HttpServiceDefs found in the module.
parsedModule.getExportedHttpServiceDefinitionsList().forEach(
httpServiceDef -> {
Expand Down Expand Up @@ -893,6 +895,33 @@ private static void registerDepModuleExportedTypes(
);
}
});

return importedContractDefinitionStmts.build();
}

private static void registerDepModuleContractImpls(ScopedHeap scopedHeap, SerializedClaroModule parsedModule) {
// Register any contract impls found in the module.
parsedModule.getExportedContractImplementationsList().forEach(
contractImpl -> registerExportedContractImplementation(
contractImpl.getImplementedContractName(),
// TODO(steving) TESTING!!! UPDATE THIS, I NEED TO BE ABLE TO HANDLE THE CASE WHERE THE IMPLEMENTED
// CONTRACT WAS ACTUALLY DEFINED IN SOME *OTHER* TRANSITIVE DEP MODULE.
// NOTE: This will probably require some re-ordering such that all contracts from all modules are
// defined before any implementations are registered.
parsedModule.getModuleDescriptor().getProjectPackage(),
parsedModule.getModuleDescriptor().getUniqueModuleName(),
contractImpl.getConcreteTypeParamsList()
.stream()
.map(Types::parseTypeProto)
.collect(ImmutableList.toImmutableList()),
contractImpl.getConcreteSignaturesList().stream()
.collect(ImmutableMap.toImmutableMap(
SerializedClaroModule.Procedure::getName,
JavaSourceCompilerBackend::getProcedureTypeFromProto
)),
scopedHeap
)
);
}

private static Types.ProcedureType getProcedureTypeFromProto(SerializedClaroModule.Procedure procedureProto) {
Expand Down Expand Up @@ -1117,6 +1146,13 @@ public static void registerExportedContractImplementation(
Hashing.sha256().hashUnencodedChars(disambiguatedContractName + disambiguatedContractImplName)
)
);
ImmutableList<String> contractTypeParamNames =
((Types.$Contract) scopedHeap.getValidatedIdentifierType(disambiguatedContractName)).getTypeParamNames();
ContractDefinitionStmt.contractImplementationsByContractName.get(disambiguatedContractName)
.add(IntStream.range(0, concreteSignaturesByName.size()).boxed().collect(ImmutableMap.toImmutableMap(
contractTypeParamNames::get,
concreteTypeParams::get
)));

// Register the actual contract implementation procedures.
concreteSignaturesByName.forEach(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ public class ProgramNode {
public StmtListNode stmtListNode;
public static final Stack<Runnable> miscErrorsFound = new Stack<>();
public static ImmutableList<ProgramNode> nonMainFiles = ImmutableList.of();
public static ImmutableList<ContractDefinitionStmt> importedContractDefinitionStmts;
public static Optional<ModuleNode> moduleApiDef = Optional.empty();

// By default, don't support any StdLib.
Expand Down Expand Up @@ -215,6 +216,11 @@ public StringBuilder generateJavaSourceOutput(ScopedHeap scopedHeap) {
// for things like type/procedure defs.
programJavaSource.javaSourceBody().setLength(0);
}
// Make sure to codegen any potential dynamic dispatch handlers from dep contract defs.
for (ContractDefinitionStmt importedContractDefinitionStmt : ProgramNode.importedContractDefinitionStmts) {
programJavaSource =
programJavaSource.createMerged(importedContractDefinitionStmt.generateJavaSourceOutput(scopedHeap));
}
// Now do codegen on this current program, implied to be the "main" src file. Do NOT throw away the javaSourceBody
// on this main src file as this is the actual "program" that the programmer wants to be able to run.
programJavaSource =
Expand Down Expand Up @@ -472,6 +478,9 @@ private void performContractDiscoveryPhase(StmtListNode stmtListNode, ScopedHeap
for (ContractDefinitionStmt currContract : contractDefinitionStmts.build()) {
currContract.registerDynamicDispatchHandlers(scopedHeap);
}
for (ContractDefinitionStmt currContract : importedContractDefinitionStmts) {
currContract.registerDynamicDispatchHandlers(scopedHeap);
}
}

private void performContractTypeValidationPhase(StmtListNode stmtListNode, ScopedHeap scopedHeap) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -685,19 +685,23 @@ public GeneratedJavaSource generateJavaSourceOutput(ScopedHeap scopedHeap) {
GeneratedJavaSource res =
GeneratedJavaSource.forJavaSourceBody(
new StringBuilder(
((Types.$ContractImplementation) scopedHeap.getValidatedIdentifierType(
ContractImplementationStmt.getContractTypeString(
this.contractName, this.resolvedContractConcreteTypes.stream()
.map(Type::toString)
.collect(Collectors.toList()))))
.getOptionalDefiningModuleDisambiguator()
.flatMap(optionalDefiningModuleDisambiguator -> ScopedHeap.getModuleNameFromDisambiguator(optionalDefiningModuleDisambiguator)
.map(defModuleName ->
ScopedHeap.currProgramDepModules.rowMap().get(defModuleName)
.values().stream().findFirst().get()))
(this.isDynamicDispatch
// If it is dynamic dispatch, then we know that the dynamic dispatch impl is in the current module.
? ScopedHeap.currProgramDepModules.row("$THIS_MODULE$").values().stream().findFirst()
// Else the impl could be somewhere else.
: ((Types.$ContractImplementation) scopedHeap.getValidatedIdentifierType(
ContractImplementationStmt.getContractTypeString(
this.contractName, this.resolvedContractConcreteTypes.stream()
.map(Type::toString)
.collect(Collectors.toList()))))
.getOptionalDefiningModuleDisambiguator()
.flatMap(optionalDefiningModuleDisambiguator -> ScopedHeap.getModuleNameFromDisambiguator(optionalDefiningModuleDisambiguator)
.map(defModuleName ->
ScopedHeap.currProgramDepModules.rowMap().get(defModuleName)
.values().stream().findFirst().get())))
.map(d -> String.format("%s.%s.", d.getProjectPackage(), d.getUniqueModuleName()))
.orElse(""))
.append(this.referencedContractImplName).append('.'));
.orElse("")
).append(this.referencedContractImplName).append('.'));

// In order to avoid using names that are way too long for Java, in the case of statically dispatched contract
// procedure calls, we're going to hash all names within this contract implementation. I won't worry about
Expand Down
Loading

0 comments on commit 5c528c3

Please sign in to comment.