diff --git a/src/AnalyzeCli.cpp b/src/AnalyzeCli.cpp index 65135d38..2d45f03e 100644 --- a/src/AnalyzeCli.cpp +++ b/src/AnalyzeCli.cpp @@ -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 diff --git a/src/include/Platform/RobloxPlatform.hpp b/src/include/Platform/RobloxPlatform.hpp index 8f35e10a..05feeafe 100644 --- a/src/include/Platform/RobloxPlatform.hpp +++ b/src/include/Platform/RobloxPlatform.hpp @@ -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: @@ -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 completionCallback(const std::string& tag, std::optional ctx, std::optional contents, const Luau::ModuleName& moduleName) override; diff --git a/src/platform/roblox/RobloxLanguageServer.cpp b/src/platform/roblox/RobloxLanguageServer.cpp index fcdb012c..ea876e97 100644 --- a/src/platform/roblox/RobloxLanguageServer.cpp +++ b/src/platform/roblox/RobloxLanguageServer.cpp @@ -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())}; + }; } diff --git a/src/platform/roblox/RobloxSourcemap.cpp b/src/platform/roblox/RobloxSourcemap.cpp index c4ff2cc9..49948419 100644 --- a/src/platform/roblox/RobloxSourcemap.cpp +++ b/src/platform/roblox/RobloxSourcemap.cpp @@ -49,8 +49,6 @@ static std::optional 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) { @@ -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) @@ -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); @@ -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; @@ -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 RobloxPlatform::getSourceNodeFromVirtualPath(const Luau::ModuleName& name) const diff --git a/tests/Autocomplete.test.cpp b/tests/Autocomplete.test.cpp index c5a9be30..2c4c4da8 100644 --- a/tests/Autocomplete.test.cpp +++ b/tests/Autocomplete.test.cpp @@ -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") diff --git a/tests/Sourcemap.test.cpp b/tests/Sourcemap.test.cpp index f9043271..4d22e2ff 100644 --- a/tests/Sourcemap.test.cpp +++ b/tests/Sourcemap.test.cpp @@ -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; diff --git a/tests/testdata/standard_definitions.d.luau b/tests/testdata/standard_definitions.d.luau index 4c654c9f..e81dbd77 100644 --- a/tests/testdata/standard_definitions.d.luau +++ b/tests/testdata/standard_definitions.d.luau @@ -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