New type system regressions

Reproduction Steps
The most recent update to luau’s type system has caused a number of false positive script analysis warnings that were working perfectly fine before.

Create a script in a new place and paste the following code:

Repro code:
--!strict

-- Regression #1 - Issues with type relation on recursive/union types
do
	type RecolorMap = {[string]: RecolorMap | Color3}

	-- This used to not emit a warning, but does now:
	local hatRecolorMap: RecolorMap = {
		Brim = Color3.fromRGB(255, 0, 0),
		Top = Color3.fromRGB(255, 0, 0),
	}

	-- However, this won't emit a warning (as it shouldn't):
	local y: RecolorMap = ({
		Foo = Color3.fromRGB(255, 0, 0),
		Bar = Color3.fromRGB(255, 0, 0),
	} :: {[string]: Color3})
	
	-- Unfortunately, my use case for this type involves a deep table that has
	-- to be asserted as any type first:
	local armorRecolorMap: RecolorMap = ({
		Leggings = {
			Glow = Color3.fromRGB(255, 0, 0),
			Shoes = Color3.fromRGB(255, 0, 0),
		},
		Belt = Color3.fromRGB(255, 0, 0),
	} :: any)
end

-- Regression #2: Bi-directional typing is failing in certain assignment
-- statements which used to work
do
	type PartialThing = {
		Foo: string?,
		Fighters: string?,
	}
	
	-- This used to not emit a warning, but does now:
	local ListOfPartialThings: {[string]: PartialThing} = {
		Thing1 = {
			Foo = 'Bar',
		},
		Thing2 = {
			Fighters = 'Dave',
		}
	}
	
	-- There seems to be no workaround? It seems to have problems relating
	-- record types with optional fields in general. This used to work
	-- just fine.
	local ListOfPartialThings: {[string]: PartialThing} = {
		Thing1 = {
			Foo = 'Bar',
		} :: PartialThing,
		Thing2 = {
			Fighters = 'Dave',
		} :: PartialThing
	}
end

Expected Behavior
These examples used to not, and should not output any warnings.

Actual Behavior
There are now false positive warnings for certain recursive/union types that used to work perfectly fine:

As well as issues with type relation in general with types that have optional fields:

Workaround
The first example has a workaround; the second example you have to type-assert each individual field as any, which basically defeats the purpose of having type safety here in the first place.

Issue Area: Studio
Issue Type: Other
Impact: Very High
Frequency: Constantly
Date First Experienced: 2021-11-17 00:11:00 (-07:00)
Date Last Experienced: 2021-11-17 00:11:00 (-07:00)

1 Like

We’ll disable this change for mow, and reenable it once it can cope with examples like this.

In case you’re wondering why the change was made, it’s because the old system was unsound. To see why, introduce a temporary variable to your first example:

type RecolorMap = {[string]: RecolorMap | Color3}
local tmp = {
  Brim = Color3.fromRGB(255, 0, 0),
  Top = Color3.fromRGB(255, 0, 0),
}
local hatRecolorMap: RecolorMap = tmp

Type inference infers that tmp has type { Brim: Color3, Top: Color3 }, but this means that the followng typechecks even though it shouldn’t

hatRecolorMap.Brim = {}
local x : Color3 = tmp.Brim

Fortunately, I think we can use a bit of bidirectional type checking so that examples like yours that are explicitly typed do what you expect.

3 Likes

This should be fixed now, thanks for reporting it!

1 Like

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.