Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP fix strict datamodel types no sourcemap #708

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/AnalyzeCli.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -358,9 +358,9 @@ int startAnalyze(const argparse::ArgumentParser& program)
if (auto sourceMapContents = readFile(*sourcemapPath))
{
robloxPlatform->updateSourceNodeMap(sourceMapContents.value());

robloxPlatform->handleSourcemapUpdate(
frontend, frontend.globals, !program.is_used("--no-strict-dm-types") && client.configuration.diagnostics.strictDatamodelTypes);
robloxPlatform->handleSourcemapUpdate(frontend, frontend.globals);
robloxPlatform->applyStrictDataModelTypesConfiguration(
!program.is_used("--no-strict-dm-types") && client.configuration.diagnostics.strictDatamodelTypes);
}
}
else
Expand Down
6 changes: 5 additions & 1 deletion src/include/Platform/RobloxPlatform.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,8 @@ static void from_json(const json& j, PluginNode& p)
}
}

Luau::TypeId getSourcemapType(const Luau::GlobalTypes& globals, Luau::TypeArena& arena, const SourceNodePtr& node);

class RobloxPlatform : public LSPPlatform
{
private:
Expand Down Expand Up @@ -173,7 +175,9 @@ class RobloxPlatform : public LSPPlatform

void updateSourceNodeMap(const std::string& sourceMapContents);

void handleSourcemapUpdate(Luau::Frontend& frontend, const Luau::GlobalTypes& globals, bool expressiveTypes);
void handleSourcemapUpdate(Luau::Frontend& frontend, const Luau::GlobalTypes& globals);

void applyStrictDataModelTypesConfiguration(bool expressiveTypes);

std::optional<Luau::AutocompleteEntryMap> completionCallback(const std::string& tag, std::optional<const Luau::ClassType*> ctx,
std::optional<std::string> contents, const Luau::ModuleName& moduleName) override;
Expand Down
27 changes: 27 additions & 0 deletions src/platform/roblox/RobloxLanguageServer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,31 @@ void RobloxPlatform::setupWithConfiguration(const ClientConfiguration& config)
"Failed to load sourcemap.json for workspace '" + workspaceFolder->name + "'. Instance information will not be available");
}
}

applyStrictDataModelTypesConfiguration(config.diagnostics.strictDatamodelTypes);
}

// Prepare module scope so that we can dynamically reassign the type of "script" to retrieve instance info
// TODO: expressiveTypes is used because of a Luau issue where we can't cast a most specific Instance type (which we create here)
// to another type. For the time being, we therefore make all our DataModel instance types marked as "any".
// Remove this once Luau has improved
void RobloxPlatform::applyStrictDataModelTypesConfiguration(bool expressiveTypes)
{
workspaceFolder->frontend.prepareModuleScope = [this, expressiveTypes](
const Luau::ModuleName& name, const Luau::ScopePtr& scope, bool forAutocomplete)
{
Luau::GlobalTypes& globals = forAutocomplete ? workspaceFolder->frontend.globalsForAutocomplete : workspaceFolder->frontend.globals;

// TODO: we hope to remove these in future!
if (!expressiveTypes && !forAutocomplete)
{
scope->bindings[Luau::AstName("script")] = Luau::Binding{globals.builtinTypes->anyType};
scope->bindings[Luau::AstName("workspace")] = Luau::Binding{globals.builtinTypes->anyType};
scope->bindings[Luau::AstName("game")] = Luau::Binding{globals.builtinTypes->anyType};
}

if (expressiveTypes || forAutocomplete)
if (auto node = isVirtualPath(name) ? getSourceNodeFromVirtualPath(name) : getSourceNodeFromRealPath(name))
scope->bindings[Luau::AstName("script")] = Luau::Binding{getSourcemapType(globals, instanceTypes, node.value())};
};
}
38 changes: 6 additions & 32 deletions src/platform/roblox/RobloxSourcemap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,6 @@ static std::optional<Luau::TypeId> getTypeIdForClass(const Luau::ScopePtr& globa
}
}

static Luau::TypeId getSourcemapType(const Luau::GlobalTypes& globals, Luau::TypeArena& arena, const SourceNodePtr& node);

static void injectChildrenLookupFunctions(
const Luau::GlobalTypes& globals, Luau::TypeArena& arena, Luau::ClassType* ctv, const Luau::TypeId& ty, const SourceNodePtr& node)
{
Expand Down Expand Up @@ -112,7 +110,7 @@ static void injectChildrenLookupFunctions(

// Retrieves the corresponding Luau type for a Sourcemap node
// If it does not yet exist, the type is produced
static Luau::TypeId getSourcemapType(const Luau::GlobalTypes& globals, Luau::TypeArena& arena, const SourceNodePtr& node)
Luau::TypeId getSourcemapType(const Luau::GlobalTypes& globals, Luau::TypeArena& arena, const SourceNodePtr& node)
{
// Gets the type corresponding to the sourcemap node if it exists
// Make sure to use the correct ty version (base typeChecker vs autocomplete typeChecker)
Expand Down Expand Up @@ -283,19 +281,15 @@ bool RobloxPlatform::updateSourceMapFromContents(const std::string& sourceMapCon
// Recreate instance types
instanceTypes.clear(); // NOTE: used across BOTH instances of handleSourcemapUpdate, don't clear in between!
auto config = workspaceFolder->client->getConfiguration(workspaceFolder->rootUri);
bool expressiveTypes = config.diagnostics.strictDatamodelTypes;

// NOTE: expressive types is always enabled for autocomplete, regardless of the setting!
// We pass the same setting even when we are registering autocomplete globals since
// the setting impacts what happens to diagnostics (as both calls overwrite frontend.prepareModuleScope)
workspaceFolder->client->sendTrace("Updating diagnostic types with sourcemap");
handleSourcemapUpdate(workspaceFolder->frontend, workspaceFolder->frontend.globals, expressiveTypes);
handleSourcemapUpdate(workspaceFolder->frontend, workspaceFolder->frontend.globals);
workspaceFolder->client->sendTrace("Updating autocomplete types with sourcemap");
handleSourcemapUpdate(workspaceFolder->frontend, workspaceFolder->frontend.globalsForAutocomplete, expressiveTypes);
handleSourcemapUpdate(workspaceFolder->frontend, workspaceFolder->frontend.globalsForAutocomplete);

workspaceFolder->client->sendTrace("Updating sourcemap contents COMPLETED");

if (expressiveTypes)
if (config.diagnostics.strictDatamodelTypes)
{
workspaceFolder->client->sendTrace("Refreshing diagnostics from sourcemap update as strictDatamodelTypes is enabled");
workspaceFolder->recomputeDiagnostics(config);
Expand Down Expand Up @@ -360,10 +354,8 @@ void RobloxPlatform::updateSourceNodeMap(const std::string& sourceMapContents)
}
}

// TODO: expressiveTypes is used because of a Luau issue where we can't cast a most specific Instance type (which we create here)
// to another type. For the time being, we therefore make all our DataModel instance types marked as "any".
// Remove this once Luau has improved
void RobloxPlatform::handleSourcemapUpdate(Luau::Frontend& frontend, const Luau::GlobalTypes& globals, bool expressiveTypes)

void RobloxPlatform::handleSourcemapUpdate(Luau::Frontend& frontend, const Luau::GlobalTypes& globals)
{
if (!rootSourceNode)
return;
Expand Down Expand Up @@ -444,24 +436,6 @@ void RobloxPlatform::handleSourcemapUpdate(Luau::Frontend& frontend, const Luau:
}
}
}

// Prepare module scope so that we can dynamically reassign the type of "script" to retrieve instance info
frontend.prepareModuleScope = [this, &frontend, expressiveTypes](const Luau::ModuleName& name, const Luau::ScopePtr& scope, bool forAutocomplete)
{
Luau::GlobalTypes& globals = forAutocomplete ? frontend.globalsForAutocomplete : frontend.globals;

// TODO: we hope to remove these in future!
if (!expressiveTypes && !forAutocomplete)
{
scope->bindings[Luau::AstName("script")] = Luau::Binding{globals.builtinTypes->anyType};
scope->bindings[Luau::AstName("workspace")] = Luau::Binding{globals.builtinTypes->anyType};
scope->bindings[Luau::AstName("game")] = Luau::Binding{globals.builtinTypes->anyType};
}

if (expressiveTypes || forAutocomplete)
if (auto node = isVirtualPath(name) ? getSourceNodeFromVirtualPath(name) : getSourceNodeFromRealPath(name))
scope->bindings[Luau::AstName("script")] = Luau::Binding{getSourcemapType(globals, instanceTypes, node.value())};
};
}

std::optional<SourceNodePtr> RobloxPlatform::getSourceNodeFromVirtualPath(const Luau::ModuleName& name) const
Expand Down
4 changes: 3 additions & 1 deletion tests/Autocomplete.test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -257,13 +257,15 @@ TEST_CASE_FIXTURE(Fixture, "instance_is_a_contains_classnames")

auto result = workspace.completion(params);

CHECK_EQ(result.size(), 6);
CHECK_EQ(result.size(), 8);
checkStringCompletionExists(result, "Instance");
checkStringCompletionExists(result, "Part");
checkStringCompletionExists(result, "TextLabel");
checkStringCompletionExists(result, "ReplicatedStorage");
checkStringCompletionExists(result, "ServiceProvider");
checkStringCompletionExists(result, "DataModel");
checkStringCompletionExists(result, "Workspace");
checkStringCompletionExists(result, "LuaSourceContainer");
}

TEST_CASE_FIXTURE(Fixture, "enum_is_a_contains_enum_items")
Expand Down
33 changes: 33 additions & 0 deletions tests/Sourcemap.test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,39 @@ TEST_CASE("getScriptFilePath doesn't pick .meta.json")
CHECK_EQ(node.getScriptFilePath(), "init.lua");
}

TEST_CASE_FIXTURE(Fixture, "datamodel_entry_points_typed_as_any_when_strict_datamodel_types_is_off_no_sourcemap")
{
client->globalConfig.diagnostics.strictDatamodelTypes = false;
auto result = check(R"(
--!strict
local x = game
local y = workspace
local z = script
)");

LUAU_LSP_REQUIRE_NO_ERRORS(result);
CHECK(Luau::toString(requireType("x")) == "any");
CHECK(Luau::toString(requireType("y")) == "any");
CHECK(Luau::toString(requireType("z")) == "any");
}

TEST_CASE_FIXTURE(Fixture, "datamodel_entry_points_typed_as_any_when_strict_datamodel_types_is_off_with_sourcemap")
{
client->globalConfig.diagnostics.strictDatamodelTypes = false;
loadSourcemap(R"({"name": "Game", "className": "DataModel", "children": []})");
auto result = check(R"(
--!strict
local x = game
local y = workspace
local z = script
)");

LUAU_LSP_REQUIRE_NO_ERRORS(result);
CHECK(Luau::toString(requireType("x")) == "any");
CHECK(Luau::toString(requireType("y")) == "any");
CHECK(Luau::toString(requireType("z")) == "any");
}

TEST_CASE_FIXTURE(Fixture, "can_access_children_via_dot_properties")
{
client->globalConfig.diagnostics.strictDatamodelTypes = true;
Expand Down
8 changes: 8 additions & 0 deletions tests/testdata/standard_definitions.d.luau
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,17 @@ end
declare class DataModel extends ServiceProvider
end

declare class Workspace extends Instance
end

declare class LuaSourceContainer extends Instance
end

declare Instance: {
new: ((className: string, parent: Instance?) -> Instance),
fromExisting: ((existingInstance: Instance) -> Instance),
}

declare game: DataModel
declare workspace: Workspace
declare script: LuaSourceContainer
Loading