Skip to content

Conversation

@gregli-msft
Copy link
Contributor

Currently, there is a named types table in the Engine, that contains Number, Text, Boolean, etc. The problem is that there isn't one list of named types that are appropriate for consumers of the Engine: for example, Canvas doesn't support Decimal, while Dataverse formula columns support DateTimeNoTZ but nobody else does.

This change pulls the named types out and makes that something that needs to be setup in order to use UDF/UDTs. RecalcEngine does this automatically, with the correct types, for all consumers of the C# interpreter. Dataverse doesn't need to worry about it as UDF/UDTs aren't usable there. Canvas will require an update.

In addition, the Number type name needs to be added to the symbol table of the Engine as an alias for Float or Decimal as appropriate. Number should be setup with the default numeric type, allowing UDT/UDFs to be type agnostic for situations where it doesn't matter. Using Float or Decimal explicitly is always specific. Setting this up is in addition to passing the NumberIsFloat flag into the Parser. As Canvas doesn't have Decimal yet, and has only ever had Float, and it is the only product with UDT/UDFs today, this is not a breaking change.

Finally, the list of reserved type names has been updated, attempting a small amount of future proofing.

@gregli-msft gregli-msft requested a review from a team as a code owner January 28, 2026 21:19
@gregli-msft gregli-msft marked this pull request as draft January 28, 2026 21:19
}

[Obsolete("Use WithBuiltInNamedTypes instead and pass in the list of named types.")]
public static SymbolTable WithBPrimitiveTypes()
Copy link
Contributor

@CarlosFigueiraMSFT CarlosFigueiraMSFT Jan 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

B

typo? With_B_PrimitiveTypes #Resolved

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, fixed.

/// Builtin Types supported by this engine.
/// </summary>
public ReadOnlySymbolTable PrimitiveTypes { get; protected internal set; } = ReadOnlySymbolTable.PrimitiveTypesTableInstance;
public ReadOnlySymbolTable BuiltInNamedTypes { get; protected internal set; }
Copy link
Contributor

@jas-valgotar jas-valgotar Jan 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

protected internal set

If hosts need to set it, does this need to be init now? #Resolved

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed this so that Engine constructor sets it after validating it is sound. So, init has been removed.


if (!nameResolver.LookupType(returnTypeToken.Name, out var returnTypeFormulaType))
// only the return type from a UDF can be a void type, it is not valid in other type contexts
if (returnTypeToken.Name == FormulaType.Void.Name)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if (returnTypeToken.Name == FormulaType.Void.Name)

Will this brake if someone added "Void" named Type in resolver?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Things could go haywire if someone added Void as a type in the builtinnamedtypes or as a UDT, but there are checks to prevent both from happening now. Is there another way you think it could be done?

{
errors.Add(new TexlError(arg.TypeIdent, DocumentErrorSeverity.Severe, TexlStrings.ErrUDF_InvalidParamType, arg.TypeIdent.Name));
isParamCheckSuccessful = false;
}
Copy link
Contributor

@jas-valgotar jas-valgotar Jan 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we not need to block Void Type in parameter? #Resolved

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No because it shouldn't be in the built in types list. I have a check in the Engine constructor that Void is not present.

{
}

public RecalcEngine(PowerFxConfig powerFxConfig, bool numberTypeIsFloat)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

numberTypeIsFloat

If this is UDF specific, can we move it to TryAddUserDefinitions and use numberIsFloat from parseroption?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried this approach first and it quickly becomes a mess. The binder doesn't have information about the parser options that were used. This flag and line 91 below take care of it completely by modifying the symbol table up front. We needed to parameterize the symbol table anyway for the Engine since not every host has the same built in types, Number is just taking advantage of this.

/// Builtin Types supported by this engine.
/// </summary>
public ReadOnlySymbolTable PrimitiveTypes { get; protected internal set; } = ReadOnlySymbolTable.PrimitiveTypesTableInstance;
public ReadOnlySymbolTable BuiltInNamedTypes { get; protected internal set; }
Copy link
Contributor

@jas-valgotar jas-valgotar Jan 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BuiltInNamedTypes

possible to avoid API breaking change? #Resolved

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've changed this further so that only the Engine constructor can set it, and yes you are correct, it does not need to be init anymore.

@@ -86,7 +86,7 @@ public Engine(PowerFxConfig powerFxConfig)
/// <summary>
/// Builtin Types supported by this engine.
Copy link
Contributor

@jas-valgotar jas-valgotar Jan 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Builtin Types supported by this engine.

Should we update this to, Builtin Types supported by User Defined Function. #Resolved

return argIndex == 1;
}

// This list is effectively intersected with the BuiltInNamedTypes in the Engine, with outliers generating errors before this list is checked.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

intersected

If it's intersected, do we need DType.Float?

Copy link
Contributor Author

@gregli-msft gregli-msft Feb 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I understand the question exactly. We need DType.Number (Float) in this list so that if a host supports Float, then it would be OK as a supported JSON type. DType.Decimal is a good example where it exists in this list, but wouldn't work in Canvas today.

/// Helper to create a symbol table with primitive types.
/// </summary>
/// <returns>SymbolTable with primitive types.</returns>
public static SymbolTable WithPrimitiveTypes()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

public static SymbolTable WithPrimitiveTypes()

API breaking, can we keep it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's already a breaking change because it now requires an argument. I left the original in place and deprecated. I doubt anyone was calling this outside of these libraries.

internal DType _type { get; private protected init; }
#pragma warning restore SA1300 // Element should begin with upper-case letter

public virtual DName Name { get; }
Copy link
Contributor

@jas-valgotar jas-valgotar Jan 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

public virtual DName Name { get; }

Can we not introduce this property since this is only useful for Internal Primitive Types? I would prefer hard coded strings how it used to be. #Resolved

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I moved this to a separate static class with readonly members, I think this may make it higher performance. I do not want to use raw strings that are not type checked and a typo could be hard to find.


// TODO: Add more future type names
private static readonly ISet<string> _restrictedTypeNames = new HashSet<string> { "Record", "Table", "Currency" };
private static readonly ISet<string> _restrictedTypeNames = new HashSet<string> { "Record", "Table", "Currency", "Number", "Object", "Null", "None", "Blank", "Void", "Array", "Untyped", "UnTyped", "Int", "Integer" };
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Number", "Object", "Null", "None", "Blank", "Void", "Array", "Untyped", "UnTyped", "Int", "Integer"

possibly a breaking change for Canvas? Do we need to communicate this to wider group?

"Email", "Phone", "Address",
"Language", "Locale",
"MultilineText", "MultiLineText", "TextArea",
"SingleineText", "SingleLineText",
Copy link
Contributor

@CarlosFigueiraMSFT CarlosFigueiraMSFT Jan 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Singleine

typo? SIngleline? #Resolved

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, fixed.

@jas-valgotar
Copy link
Contributor

✅ No public API change.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants