Type annotation help

Hello. Ive been recently learning about type checking and stuff and I can’t figure out how to get rid of this blue line. Im not sure what I did wrong here. If anyone could help, that would be greatly appriciated!

--!strict return function(label: TextLabel | TextButton, fullText: string, delayPerChar: number?) label.Text = fullText end

Heres what it looks like in studio:


This is the “Type Error” im getting:

I don’t know how to fully explain the logic of this verbally (I’ll try my best), but essentially what’s happening is that the | operator in Luau type checking takes all the elements from all the types involved and squash them into a single group, so when you access (TextLabel | TextButton).Text you’re not accessing any Text property directly, but a table of all “Text” properties available from all the sources (Text from TextLabel and Text from TextButton); Hence, why it expects a table.

The solution is to type the parameter with & instead of |. The & operator takes the matching properties/objects and just assumes they’re the same element, instead of grouping them “side-by-side” like |.
Your solved snippet is:

--!strict
return function(label: TextLabel & TextButton, fullText: string, delayPerChar: number?)
	label.Text = fullText
end
Edit: Additional information

The idea behind the difference between | and & boils down to the concept of having a variable that could be one of various types that either have similar properties, or don’t.

In other words, the | operator should be used when all the types involved do not share properties, or their respective properties aren’t common in type.
On the other hand, & should be used when the types in question are similar, they do share properties (such as Text in TextLabels and TextButtons, it’s a similar property in the sense that both take a string, and therefore they’re no different type-wise).

To set up proper examples:

-- Ideal use case of |
local foo1: (string | number) -- string and number do not share properties, therefore they're not similar.

-- Ideal use case of &
local foo2: (Player & Folder) -- although unrelated at first glance, Player and Folder instances share the similar Name property, therefore, & can be used to indicate an access to this common property is intended: to each its own, but similar in type nevertheless.

This seems to likely be a Roblox bug, I’m not able to reproduce this error with the exact same code.

This isn’t an actual solution. To use the function, you’d then have to ensure all possible TextLabels and TextButtons are typed to match that. The issue OP is facing isn’t related to how label is typed either, the Text property in both TextLabel and TextButton is a string, so its likely a Roblox issue.

How come?

I do experience the warning going off by the type checker the original poster is expriencing, and it just so happens to disappear when types are intersected instead of unionized.

Perhaps my post isn’t all clear for those who don’t actually read through the logic behind the provided explanation and additional information, so I’ll go through all that again while quoting the exact definitions as of this moment from the Type checking - Luau website:

A union type represents one of the types in this set. If you try to pass a union onto another thing that expects a more specific type, it will fail.

For this case, the ‘set’ represents the union between the TextLabel and TextButton types. Since it’s one of the types in the set, and not a specific type in the set, it will fail: accessing the Text property in the unionized type will access the figure {Text from TextLabel, Text from TextButton}, not the specific type, but the union between those (TextLabel.Text ~= (TextLabel | TextButton).Text).
Therefore the type checker says it “Expected type table, got ‘TextButton | TextLabel’ instead” (since Text can be sourced from TextButton | TextLabel in its unionized nature).

On the other hand,

An intersection type represents all of the types in this set. It’s useful for two main things: to join multiple tables together, or to specify overloadable functions.

For this particular case, having two Text properties from different objects is, in essence, an overload: an intersected type joins both objects, and intersects their common properties into type overloads, as opposed to unions, which wouldn’t overload anything since they come from different sources, they’re set side-by-side, not overloaded or intersected (worth the term redundancy).


As for why it doesn’t show up any warning for you, perhaps you have enabled the new Type Checker beta? I have heard (so take this with a grain of salt) it’s able to infer ambiguous expressions better and is more ‘tolerant’, more ‘smart’ in some sense, but also less rough and less direct, proper qualities of the level of strictness of a type checker I would personally use.

You’re overcomplicating this a lot. The point of OP’s function is to set the Text of a TextLabel or TextButton. You’re suggesting to combine the two types into something not practical nor logical. Types aren’t that complicated.

local label: TextLabel | TextButton -- label is either a TextLabel or a TextButton
local label: TextLabel & TextButton -- label is a TextLabel AND a TextButton,
-- meaning all applications of it require this special typing to be used
1 Like

I argue that’s just partially true.
Yes, strictly speaking, no object in Roblox is literally both a TextLabel and a TextButton.
But type systems are abstractions, their whole inflection is based on that principle. We use them to capture useful invariants for convenience.

Here, the useful invariant is: “whatever you pass in, it has a Text property.”
And intersection is the cleanest way to express that invariant.

The original poster’s intent isn’t “giving a value that is exactly a TextLabel or exactly a TextButton.”
The intent is, as you well describe it with “Types aren’t that complicated”:
“Give me anything that supports .Text: string.”

That’s a structural requirement, not a nominal one.
Henceforth, just structurally, both TextLabel and TextButton support .Text: string.

So if I write:

label: TextLabel & TextButton

It’s telling the program: “I don’t care what you are, as long as you satisfy all the overlapping members from these types.”
That is practical and logical because it encompasses exactly the function contract the original poster cares about, thus not “overcomplicating this a lot”, but rather actually simplifying it.

Compare:

  • TextLabel | TextButton
    • Means “one or the other, you’ll need to check which one.”
  • TextLabel & TextButton
    • Means “whatever it is, it behaves like both where they overlap.”

For a function whose sole job is to set .Text, as the original poster requires, the second description is more direct and less verbose.

thats just not how it works bro…
if you actually tried putting it into code, you would see this:
Type Error: Type 'TextLabel' could not be converted into 'TextButton & TextLabel' caused by: Not all intersection parts are compatible. Type 'TextLabel' could not be converted into 'TextButton'

For OP, I did confirm the issue is because of Roblox’s old type solver, just enable the new beta and the error will be gone.
image

3 Likes

Where’s the argument proving otherwise in stating that?..

Luau’s type system is structural: it doesn’t care about Roblox’s inheritance tree when resolving intersections (operations from type checking are contractions, not classes themselves).
Both TextLabel and TextButton share .Text: string.
So by the rules of the type checker, TextLabel & TextButton is a valid way to capture “anything from the given pool that shares properties, such as .Text”.

This is literally how the type system is designed.

That’s for variable declaration. We’re discussing the type implications in function parameters under invariants, as shown in the original post and I stated explicitly in my past post. Declaration is a whole new case, and it’s true this method wouldn’t quite fit for that case (which isn’t the one up to debate).

This:



As opposed to:


If in need of empirical showcase.

the error I showed is when you input a TextLabel into the function, yes the type error goes away, but you still actually need to input the label without that erroring.

the original type is correct, the solution is to enable the type solver beta

1 Like

That sounds more like it, exactly.

Original post is about the type error: type error goes away, topic solved.

It never introduces anything about calling the function. And just therefore, I posted the most clean and direct solution to the problem at hand. Additional implications are up to the specific use case for this topic, which aren’t specified.

1 Like

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