Skip to content

Refining table properties to generate readonly discriminants #1749

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

Closed
Closed
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
20 changes: 14 additions & 6 deletions Analysis/src/ConstraintGenerator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ LUAU_FASTFLAGVARIABLE(LuauInferLocalTypesInMultipleAssignments)
LUAU_FASTFLAGVARIABLE(LuauDoNotLeakNilInRefinement)
LUAU_FASTFLAGVARIABLE(LuauExtraFollows)
LUAU_FASTFLAG(LuauUserTypeFunTypecheck)
LUAU_FASTFLAGVARIABLE(LuauRefineJustTheReadProperty)

namespace Luau
{
Expand Down Expand Up @@ -529,11 +530,18 @@ void ConstraintGenerator::computeRefinement(

TypeId nextDiscriminantTy = arena->addType(TableType{});
NotNull<TableType> table{getMutable<TableType>(nextDiscriminantTy)};
// When we fully support read-write properties (i.e. when we allow properties with
// completely disparate read and write types), then the following property can be
// set to read-only since refinements only tell us about what we read. This cannot
// be allowed yet though because it causes read and write types to diverge.
table->props[*key->propName] = Property::rw(discriminantTy);
if (FFlag::LuauRefineJustTheReadProperty)
{
table->props[*key->propName] = Property::readonly(discriminantTy);
}
else
{
// When we fully support read-write properties (i.e. when we allow properties with
// completely disparate read and write types), then the following property can be
// set to read-only since refinements only tell us about what we read. This cannot
// be allowed yet though because it causes read and write types to diverge.
table->props[*key->propName] = Property::rw(discriminantTy);
}
table->scope = scope.get();
table->state = TableState::Sealed;

Expand All @@ -547,7 +555,7 @@ void ConstraintGenerator::computeRefinement(
refis->get(proposition->key->def)->shouldAppendNilType =
(sense || !eq) && containsSubscriptedDefinition(proposition->key->def) && !proposition->implicitFromCall;
}
else
else
{
refis->get(proposition->key->def)->shouldAppendNilType = (sense || !eq) && containsSubscriptedDefinition(proposition->key->def);
}
Expand Down
47 changes: 46 additions & 1 deletion tests/TypeInfer.refinements.test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ LUAU_FASTFLAG(LuauIntersectNotNil)
LUAU_FASTFLAG(LuauSkipNoRefineDuringRefinement)
LUAU_FASTFLAG(LuauFunctionCallsAreNotNilable)
LUAU_FASTFLAG(LuauDoNotLeakNilInRefinement)
LUAU_FASTFLAG(LuauRefineJustTheReadProperty)

using namespace Luau;

Expand Down Expand Up @@ -2538,7 +2539,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "function_calls_are_not_nillable")
return nil
end
)"));

}

TEST_CASE_FIXTURE(BuiltinsFixture, "oss_1528_method_calls_are_not_nillable")
Expand All @@ -2562,4 +2562,49 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "oss_1528_method_calls_are_not_nillable")
)"));
}

TEST_CASE_FIXTURE(Fixture, "refine_just_the_read_property")
{
ScopedFastFlag sff[]{
{FFlag::LuauSolverV2, true},
{FFlag::LuauRefineJustTheReadProperty, true},
};

CheckResult result = check(R"(
type Foo = { p: boolean }

function f(x: Foo)
if x.p == true then return end

x.p = true
x.p = false
end
)");

LUAU_REQUIRE_NO_ERRORS(result);

// The first check is correct because it reflects the state of the program by that point.
// The second check is not. It fails to account for transitive typestate change from the
// previous statement.
CHECK_EQ("Foo & { read p: ~true }", toString(requireTypeAtPosition({6, 12})));
CHECK_EQ("Foo & { read p: ~true }", toString(requireTypeAtPosition({7, 12})));
}

TEST_CASE_FIXTURE(Fixture, "refine_just_the_read_property_typestate")
{
ScopedFastFlag sff[]{
{FFlag::LuauSolverV2, true},
{FFlag::LuauRefineJustTheReadProperty, true},
};

CheckResult result = check(R"(
type Foo = { p: {}? }

function f(x: Foo)
if not x.p then x.p = {} end
end
)");

LUAU_REQUIRE_NO_ERRORS(result);
}

TEST_SUITE_END();
Loading