Skip to content
This repository has been archived by the owner on Jun 12, 2022. It is now read-only.

Commit

Permalink
Fully type datamodel tree
Browse files Browse the repository at this point in the history
- Converted to shared ptr for all source nodes
- Used LazyTypeVar for instances
- Fully type parent and children

Closes #15
  • Loading branch information
JohnnyMorganz committed Apr 30, 2022
1 parent 35ef4bd commit 2fd4497
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 56 deletions.
20 changes: 11 additions & 9 deletions RequireResolver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ std::optional<std::filesystem::path> RojoResolver::getRelevantFilePath(const Sou
return std::nullopt;
}

std::optional<std::shared_ptr<SourceNode>> RojoResolver::findChildWithName(const SourceNode& node, const std::string_view& name)
std::optional<SourceNodePtr> RojoResolver::findChildWithName(const SourceNode& node, const std::string_view& name)
{
for (const auto& child : node.children)
{
Expand Down Expand Up @@ -310,7 +310,8 @@ std::optional<ResolvedSourceMap> RojoResolver::parseProjectFile(const std::files
}
writePathsToMap(rootNode, base, pathToVirtualMap);

return ResolvedSourceMap{rootNode, pathToVirtualMap};
SourceNodePtr rootNodePtr = std::make_shared<SourceNode>(rootNode);
return ResolvedSourceMap{rootNodePtr, pathToVirtualMap};
}
catch (const std::exception& ex)
{
Expand Down Expand Up @@ -341,7 +342,8 @@ std::optional<ResolvedSourceMap> RojoResolver::parseSourceMap(const std::filesys
}
writePathsToMap(rootNode, base, pathToVirtualMap);

return ResolvedSourceMap{rootNode, pathToVirtualMap};
SourceNodePtr rootNodePtr = std::make_shared<SourceNode>(rootNode);
return ResolvedSourceMap{rootNodePtr, pathToVirtualMap};
}
catch (const std::exception& ex)
{
Expand All @@ -358,22 +360,22 @@ std::optional<ResolvedSourceMap> RojoResolver::parseSourceMap(const std::filesys
* @param requirePath A fully-qualified path to an Instance. e.g. `game/ReplicatedStorage/Script`
* @param root The root of the instance tree
*/
std::optional<SourceNode> RojoResolver::resolveRequireToSourceNode(const std::string& requirePath, const SourceNode& root)
std::optional<SourceNodePtr> RojoResolver::resolveRequireToSourceNode(const std::string& requirePath, const SourceNodePtr& root)
{
// TODO: this function is O(n*m) [n = depth of instance tree, m = width of instance tree]. Could we improve this?

auto pathParts = Luau::split(requirePath, '/');
SourceNode currentNode = root;
SourceNodePtr currentNode = root;

auto it = ++pathParts.begin(); // Skip first element
while (it != pathParts.end())
{
auto part = *it;
auto child = findChildWithName(currentNode, part);
auto child = findChildWithName(*currentNode, part);

if (child.has_value())
{
currentNode = *child.value();
currentNode = child.value();
}
else
{
Expand All @@ -386,11 +388,11 @@ std::optional<SourceNode> RojoResolver::resolveRequireToSourceNode(const std::st
return currentNode;
}

std::optional<std::filesystem::path> RojoResolver::resolveRequireToRealPath(const std::string& requirePath, const SourceNode& root)
std::optional<std::filesystem::path> RojoResolver::resolveRequireToRealPath(const std::string& requirePath, const SourceNodePtr& root)
{
if (auto node = RojoResolver::resolveRequireToSourceNode(requirePath, root))
{
return getRelevantFilePath(node.value());
return getRelevantFilePath(*node.value());
}
return std::nullopt;
}
Expand Down
12 changes: 7 additions & 5 deletions RequireResolver.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,28 +10,30 @@

#include "Luau/FileResolver.h"

using SourceNodePtr = std::shared_ptr<struct SourceNode>;

struct SourceNode
{
std::string name;
std::optional<std::string> className; // TODO: make no longer optional when removing project file parsing
std::vector<std::filesystem::path> filePaths;
std::vector<std::shared_ptr<SourceNode>> children;
std::vector<SourceNodePtr> children;
};

struct ResolvedSourceMap
{
SourceNode root; // The resolved root
SourceNodePtr root; // The resolved root
std::unordered_map<std::string, std::string> realPathToVirtualMap; // Map between real (canonical) file paths and virtual Rojo paths
};

namespace RojoResolver
{
std::optional<ResolvedSourceMap> parseProjectFile(const std::filesystem::path& projectFilePath);
std::optional<ResolvedSourceMap> parseSourceMap(const std::filesystem::path& projectFilePath);
std::optional<SourceNode> resolveRequireToSourceNode(const std::string& requirePath, const SourceNode& root);
std::optional<std::filesystem::path> resolveRequireToRealPath(const std::string& requirePath, const SourceNode& root);
std::optional<SourceNodePtr> resolveRequireToSourceNode(const std::string& requirePath, const SourceNodePtr& root);
std::optional<std::filesystem::path> resolveRequireToRealPath(const std::string& requirePath, const SourceNodePtr& root);
std::optional<std::filesystem::path> getRelevantFilePath(const SourceNode& node);
std::optional<std::shared_ptr<SourceNode>> findChildWithName(const SourceNode& node, const std::string_view& name);
std::optional<SourceNodePtr> findChildWithName(const SourceNode& node, const std::string_view& name);
Luau::SourceCode::Type sourceCodeTypeFromClassName(const std::string& className);
Luau::SourceCode::Type sourceCodeTypeFromPath(const std::filesystem::path& path);
std::optional<std::string> resolveRealPathToVirtual(const ResolvedSourceMap& sourceMap, const std::filesystem::path& filePath);
Expand Down
126 changes: 84 additions & 42 deletions main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -262,11 +262,10 @@ struct CliFileResolver : Luau::FileResolver
}
else if (isManagedModule(name))
{
std::optional<SourceNode> sourceNode = RojoResolver::resolveRequireToSourceNode(name, sourceMap.root);
if (sourceNode.has_value())
if (std::optional<SourceNodePtr> sourceNodePtr = RojoResolver::resolveRequireToSourceNode(name, sourceMap.root))
{
auto path = RojoResolver::getRelevantFilePath(sourceNode.value());
if (path.has_value())
SourceNode sourceNode = *sourceNodePtr.value();
if (auto path = RojoResolver::getRelevantFilePath(sourceNode))
{
source = readFile(path.value());

Expand All @@ -285,10 +284,10 @@ struct CliFileResolver : Luau::FileResolver
}
}

if (sourceNode.value().className.has_value())
if (sourceNode.className.has_value())
{

sourceType = RojoResolver::sourceCodeTypeFromClassName(sourceNode.value().className.value());
sourceType = RojoResolver::sourceCodeTypeFromClassName(sourceNode.className.value());
}
else
{
Expand Down Expand Up @@ -435,43 +434,88 @@ struct CliConfigResolver : Luau::ConfigResolver
}
};

std::optional<Luau::TypeId> makeInstanceType(Luau::TypeArena& typeArena, const Luau::ScopePtr& globalScope, const SourceNode& node)
std::optional<Luau::TypeId> getTypeIdForClass(const Luau::ScopePtr& globalScope, std::optional<std::string> className)
{
std::optional<Luau::TypeFun> baseType;
if (node.className.has_value())
if (className.has_value())
{
baseType = globalScope->lookupType(node.className.value());
baseType = globalScope->lookupType(className.value());
}
if (!baseType.has_value())
{
baseType = globalScope->lookupType("Instance");
}
// If we reach this stage, we couldn't find the class name nor the "Instance" type
// This most likely means a valid definitions file was not provided
if (!baseType.has_value())

if (baseType.has_value())
{
return baseType.value().type;
}
else
{
// If we reach this stage, we couldn't find the class name nor the "Instance" type
// This most likely means a valid definitions file was not provided
return std::nullopt;
}
}

auto typeId = baseType.value().type;
Luau::TypeId makeLazyInstanceType(Luau::TypeArena& arena, const Luau::ScopePtr& globalScope, const SourceNodePtr& nodePtr,
std::optional<Luau::TypeId> parent, const SourceNodePtr& rootNode, const std::string& virtualPath)
{

if (node.children.size() > 0)
Luau::LazyTypeVar ltv;
// TODO: capturing shared_ptr in a lambda is a memory leak, we should probably fix this
ltv.thunk = [&arena, globalScope, nodePtr, parent, virtualPath, rootNode]()
{
// Add the children
Luau::TableTypeVar children{Luau::TableState::Sealed, globalScope->level};
for (const auto& child : node.children)
auto node = *nodePtr;

// TODO: we should cache created instance types and reuse them where possible

// Look up the base class instance
auto baseTypeId = getTypeIdForClass(globalScope, node.className);
if (!baseTypeId.has_value())
{
auto childType = makeInstanceType(typeArena, globalScope, *child);
if (childType.has_value())
return Luau::getSingletonTypes().anyType;
}

// Create the ClassTypeVar representing the instance
Luau::ClassTypeVar ctv{node.name, {}, baseTypeId, std::nullopt, {}, {}, "InstanceModules"};
auto typeId = arena.addType(std::move(ctv));

// Attach Parent and Children info
// Get the mutable version of the type var
if (Luau::ClassTypeVar* ctv = Luau::getMutable<Luau::ClassTypeVar>(typeId))
{
// Add the parent
if (parent.has_value())
{
ctv->props["Parent"] = Luau::makeProperty(parent.value());
}
else
{
// Search for the parent type
if (auto parentPath = getParentPath(virtualPath))
{
// TODO: This is an O(n) search
if (auto parentNode = RojoResolver::resolveRequireToSourceNode(parentPath.value(), rootNode))
{
ctv->props["Parent"] = Luau::makeProperty(
makeLazyInstanceType(arena, globalScope, parentNode.value(), std::nullopt, rootNode, parentPath.value()));
}
}
}

// Add the children
for (const auto& child : node.children)
{
auto childProperty = Luau::makeProperty(childType.value(), "@luau/instance");
children.props[(*child).name] = childProperty;
auto childName = (*child).name;
auto childPath = virtualPath + "/" + childName;
ctv->props[childName] = Luau::makeProperty(makeLazyInstanceType(arena, globalScope, child, typeId, rootNode, childPath));
}
}
Luau::TypeId childId = typeArena.addType(children);
typeId = Luau::makeIntersection(typeArena, {typeId, childId});
}
return typeId;
return typeId;
};

return arena.addType(std::move(ltv));
}

// Magic function for `Instance:IsA("ClassName")` predicate
Expand Down Expand Up @@ -664,7 +708,7 @@ int main(int argc, char** argv)
{
fileResolver.sourceMap = sourceMap.value();
if (dumpMap)
dumpSourceMap(fileResolver.sourceMap.root, 0);
dumpSourceMap(*fileResolver.sourceMap.root, 0);
}
}
else if (projectPath.has_value())
Expand All @@ -674,7 +718,7 @@ int main(int argc, char** argv)
{
fileResolver.sourceMap = sourceMap.value();
if (dumpMap)
dumpSourceMap(fileResolver.sourceMap.root, 0);
dumpSourceMap(*fileResolver.sourceMap.root, 0);
}
}

Expand Down Expand Up @@ -718,25 +762,24 @@ int main(int argc, char** argv)
if (success)
{
// Try to extend the globally registered types with the project format
auto root = fileResolver.sourceMap.root;
auto rootPtr = fileResolver.sourceMap.root;
auto root = *rootPtr;
if (root.className.has_value() && root.className.value() == "DataModel")
{
for (const auto& services : root.children)
for (const auto& service : root.children)
{
auto serviceType = frontend.typeChecker.globalScope->lookupType((*services).name); // TODO: change to className
if (serviceType.has_value())
auto serviceName = (*service).name; // TODO: change to className
if (auto serviceType = frontend.typeChecker.globalScope->lookupType(serviceName))
{
if (Luau::ClassTypeVar* ctv = Luau::getMutable<Luau::ClassTypeVar>(serviceType.value().type))
{
// Extend the props to include the children
for (const auto& child : (*services).children)
for (const auto& child : (*service).children)
{
auto childType = makeInstanceType(frontend.typeChecker.globalTypes, frontend.typeChecker.globalScope, *child);
if (childType.has_value())
{

ctv->props[(*child).name] = makeProperty(childType.value(), "@luau/serviceChild");
}
auto childName = (*child).name;
ctv->props[childName] =
Luau::makeProperty(makeLazyInstanceType(frontend.typeChecker.globalTypes, frontend.typeChecker.globalScope, child,
serviceType.value().type, rootPtr, "game/" + serviceName + "/" + childName));
}
}
}
Expand Down Expand Up @@ -770,11 +813,10 @@ int main(int argc, char** argv)
LUAU_ASSERT(scope->returnType);
auto typeArena = scope->returnType->owningArena;
LUAU_ASSERT(typeArena);
auto ty = makeInstanceType(*typeArena, scope, node.value());
if (!ty.has_value())
return;

scope->bindings[Luau::AstName("script")] = Luau::Binding{ty.value(), Luau::Location{}, {}, {}, std::nullopt};
scope->bindings[Luau::AstName("script")] =
Luau::Binding{makeLazyInstanceType(*typeArena, scope, node.value(), std::nullopt, fileResolver.sourceMap.root, virtualPath.value()),
Luau::Location{}, {}, {}, std::nullopt};
};

Luau::freeze(frontend.typeChecker.globalTypes);
Expand Down

0 comments on commit 2fd4497

Please sign in to comment.