Skip to content

Template semantics - #1006

Open
Kronos3 wants to merge 32 commits into
feature/templatesfrom
template-semantics
Open

Template semantics#1006
Kronos3 wants to merge 32 commits into
feature/templatesfrom
template-semantics

Conversation

@Kronos3

@Kronos3 Kronos3 commented May 27, 2026

Copy link
Copy Markdown
Collaborator

This PR includes the implementation of templates. I'm opening this PR so it's easier to track work and look at diffs, it is not ready for review/merge yet.

@Kronos3
Kronos3 force-pushed the template-semantics branch from 7528e98 to 5fdf152 Compare June 21, 2026 15:34
@Kronos3
Kronos3 marked this pull request as ready for review June 21, 2026 15:56
@Kronos3

Kronos3 commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator Author

@bocchino I've dug into the convertValueToType conversion issue that we discussed today and I'd like to encode my findings so we can find a path forward. It's quite a long block of text, sorry!

Test case

array B = [2] U32

module template M(constant b: B) {
  constant c = [1, b]
}

expand M(constant [2, 3])

Error Diagnosis

The error: cannot convert value 1 to type array of Integer. This error occurs at the point where FPP tries to promote the primitive integer 1 to the commonType(1, typeof(b)

The answer was actually encoded exactly in this error message though the message itself was misleading. See this snippet below:

override def toString = size match {
case Some(n) => "[" ++ n.toString ++ "] " ++ eltType.toString
case None => "array of " ++ eltType.toString
}

When an anonymous array size is known, the name of the array is [size] EltType and when it's unknown (None) it's array of EltType. When the integer promoted, the array length must exist which is the reason why this conversion fails:

if (this.getType.isPromotableToArray)
for {
size <- anonArray.size
elt <- this.convertToType(anonArray.eltType)
}
yield Value.AnonArray(List.fill(size)(elt))

Note: This is not a simple case of FinalizeType needing to be run because there is no actual type to finalize. This is an anonymous array type that needs to be finalized which is not possible since the syntax should implicitly provide a size. See below for the cause.

Cause

As an initial note, the [unknown] Integer type that FPP attempts to promote 1 to is coming from the commonType computed from the CheckExprTypes pass earlier in the execution.

Let's read the FPP spec on commonType:

image

I believe the issue is with step 6. We are stripping away the concrete array type for an anonymous array with unknown size. This was ok before because there was never a case where this case was hit. There was never a case in which a value of concrete array type could be constructed and that value be commonType-ed with another type. Templates constant parameters constructing values with any concrete type.

As a side note: I think anonymous arrays of None size should never be possible at any point in the analysis. Anonymous arrays stem from array literals which should always be trivially determined since you can just count the number of elements in the syntax. It may be worth keeping this in the semantics as a mechanism to allow concrete Arrays to wrap AnonArray which would allow None sizes.

A side-note to my side note: Anonymous arrays of None size will be a valid thing when we have [0, ...] ellipsis operators.

Fix

We need to change the semantics of commonType to say:

// (Array, Array) case

  1. Otherwise if T1 and T2 are array types, throw an error (I'm fairly sure there is there no valid commonType solution here, but maybe you can think of something?)

// (Array, AnonArray)
// (AnonArray, Array)

  1. Otherwise if one of T1 or T2 is an array type A equivalent to [n1] T' and the other is an anonymous array type [n2] T'':

    a. If T'' is convertible to T' and n1 == n2, let T be A
    b. Otherwise the attempted resolution is invalid. Throw an error

// (Array, promotableToArray(T))
// (promotableToArray(T), Array)

  1. Otherwise if one of T1 or T2 is an array type A equivalent to [n] T' and the other is a type convertible to an anonymous array type T'':

    a. If T'' is convertible to T', let T be A
    b. Otherwise the attempted resolution is invalid. Throw an error

... continue onward with the same rules

Implementation note!

commonType is computed before concrete array sizes are known, I'm not sure if these rules can actually be implemented as stated above at the moment?

I'm mainly referring to Step 7a where we check n1 == n2. We could theoretically defer that check later when we attempt to convert the value to the concrete type.

@bocchino

bocchino commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator

I believe the issue is with step 6.

I think it's trying to apply step 8 (common type of Integer and [n] U32) at a point where n is unknown. I don't think we need to revise the spec. I think we need to revise the implementation. One way to do it is to accumulate constraints at the point of type checking where the sizes aren't known, and check the constraints later after we know the sizes.

@bocchino

bocchino commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator

Otherwise if T1 and T2 are array types, throw an error (I'm fairly sure there is there no valid commonType solution here, but maybe you can think of something?)

I think the spec is correct here. For example, if A and B are both array types with shape [2] U32, then the common type should be the shape (anonymous array). The common type should be the least type to which both types are assignable.

@bocchino

bocchino commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator

One way to do it is to accumulate constraints at the point of type checking where the sizes aren't known, and check the constraints later after we know the sizes.

Actually I suspect what may be happening is this:

  1. Before adding template parameters, we could compute partial type info (missing array sizes) and that was enough to compute values; then we use the values to finalize the types and finish the type checking.
  2. With template parameters, the partial type info is not sufficient to compute the values under the current rules -- that's what seems to be causing the crash. Specifically, we are trying to convert the value 1: Integer to the partial type "array of unknown size" and the system can't do that. We never needed to do that before, but now we do.

So a solution may be to relax the value conversion rules to allow the temporary formation of array values with unknown sizes, analogously to how we temporarily form array types with unknown sizes. That should provide enough scalar value info to finalize the array values and the array types.

expr.data match {
case Ast.ExprIdent(e) => Right(List(AstNode.create(e, expr.id)))
case Ast.ExprDot(e, id) => for (left <- exprToIdentList(e)) yield left :+ id
// TODO(tumbar) Make error messages specific to the parameter type

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

TODO!

Comment on lines +187 to +189
nestedScope <- nestedScope.put(NameGroup.Value)(param.getUnqualifiedName, param)
nestedScope <- nestedScope.put(NameGroup.Type)(param.getUnqualifiedName, param)
nestedScope <- nestedScope.put(NameGroup.PortInterfaceInstance)(param.getUnqualifiedName, param)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Don't add to all name-groups

val eltType = a.typeMap(node.id) match {
case Type.AnonArray(_, eltType) => eltType
case Type.Array(_, Type.AnonArray(_, eltType), _, _) => eltType
case _ => throw InternalError("element type of array expression should be AnonArray")

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

"or Array"

override def toString = size match {
case Some(n) => "[" ++ n.toString ++ "] " ++ eltType.toString
case None => "array of " ++ eltType.toString
case None => "[unknown] " ++ eltType.toString

@Kronos3 Kronos3 Aug 5, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

revert this back

includingLoc: Option[Location] = None /* Location where this location is included */
/* Location where this location is included */
includeLoc: Option[Location] = None,
/* Location where this location is included */

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

"expanded"

pos: Position, /* The position */
includingLoc: Option[Location] = None /* Location where this location is included */
/* Location where this location is included */
includeLoc: Option[Location] = None,

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Make into includingLoc and expandingLoc

// Enum symbol: if this is in scope, then we are in
// the enum definition, so it already has a type
case Symbol.EnumConstant(node) => Right(a)
// Template parameter symbol: we are already inside the template expansion

@bocchino bocchino Aug 6, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggested change
// Template parameter symbol: we are already inside the template expansion
// Template constant argument symbol: we are already inside the template expansion

val scope = expansion.scope

// We do use-analysis on the scope of the definition + param scope
// This is a bit unorthadox but we need to build a new nested scope from scratch

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggested change
// This is a bit unorthadox but we need to build a new nested scope from scratch
// This is a bit unorthodox but we need to build a new nested scope from scratch

}
}

// FIXME(tumbar) This is probably not needed

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Let's fix this in this PR or open an issue to track it.

defNode: Ast.Annotated[AstNode[Ast.DefModuleTemplate]],
/** The AST node expanding the template */
expansion: Ast.Annotated[AstNode[Ast.SpecTemplateExpand]],
/** Concrete parameters given to this template during expansion */

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggested change
/** Concrete parameters given to this template during expansion */
/** Arguments bound to the parameters of this template during expansion */

expansion: Ast.Annotated[AstNode[Ast.SpecTemplateExpand]],
/** Concrete parameters given to this template during expansion */
params: Map[String, TemplateArgSymbol],
/** Scope where parameter symbols are entered */

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggested change
/** Scope where parameter symbols are entered */
/** Scope where template argument symbols are entered */

expr.data match {
case Ast.ExprIdent(e) => Right(List(AstNode.create(e, expr.id)))
case Ast.ExprDot(e, id) => for (left <- exprToIdentList(e)) yield left :+ id
// TODO(tumbar) Make error messages specific to the parameter type

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Let's fix this TODO here or open an issue to track it.

QualIdent.NodeList.split(nodeList) match {
case (Nil, name) => QualIdent.Unqualified(name.data)
case (qualifier, name) => {
// TODO(tumbar) Don't duplicate node ids

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Let's fix this TODO here or open an issue to track it.

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.

2 participants