Skip to content

Templates, Absolute Qualifiers, Scala Parity - #112

Merged
Kronos3 merged 215 commits into
masterfrom
feature/templates
Oct 11, 2026
Merged

Kronos3 merged 215 commits into
masterfrom
feature/templates

Conversation

@Kronos3

@Kronos3 Kronos3 commented Oct 11, 2026

Copy link
Copy Markdown
Collaborator

This PR brings in the FPP Templates branch we have finally merged into FPP Scala main. It also brings in absolute qualfied identifiers and puts the implementation and diagnostics much closer in-line with Scala.

All tests fpp-check tests are now fully represented (including template tests).

For the implementation internals, the analyzer pattern is now similar to the Scala version where we can mixin analyzers to decend into different definitions.

Scala divergences:

Opus 5.5 has found the following divergences between Rust/Scala. Some of these are Scala divergences from the spec and others are implementation details:

Spec over Scala (policy 3):

  • Form feed outside strings/comments is rejected (spec: no whitespace but spaces).
  • A backslash-newline inside a single-line string is rejected.
  • Mixed integer/float arithmetic stays in f64; Scala rounds through f32 (see the
    ledger §2.2).
  • TypeOption common types unwrap aliases (typeoption-alias-bug.md).
  • sizeof(string size N) range-checks N; Scala does not.
  • An arithmetic binop whose common type is an alias or enum has type Integer
    (Type-Checking.adoc, "Arithmetic and Concatenation"); Scala gives it the
    common type, so e.g. (q * q) >> 2 with q of an alias type is accepted here
    and rejected by Scala (template/alias_param_binop_shift_ok).
  • A module resides in every name group, the system group included, so a module
    and a same-named system collide (Scoping-of-Names.adoc); Scala accepts
    (redef/module_system).
  • A state machine used as a type name is rejected regardless of definition
    order; Scala accepts it when the state machine comes first
    (type/state_machine_not_a_type).
  • A parameter's default opcode is the previous opcode plus one with no 32-bit
    truncation (param/default_opcode_after_32_bits); Scala truncates.

i128 (policy 2): values past the i128 range are reported ("arithmetic result is
too large to represent", the default id/opcode overflow tests); Scala's BigInt
accepts them. An explicit id or opcode at the i128 maximum is reported even when
no later default needs it.

Where Scala crashes (InternalError/MatchError), Rust reports a spec-based error
(non-numeric instance attributes and port numbers, a special port whose implied
use names a module, string size "abc"-style cases where Rust has a check).

Collections above four entries: Scala iterates in hash order, Rust in insertion
order, so a "first of many" error can name a different element.

Diagnostics: wording differs where the meaning is the same; Rust spans may cover
more context; extra diagnostics after the first are allowed (policy 1),
including a few identical repeats (an unresolvable template interface argument
used several times is reported once per use).

ComponentInstance::file stays relative to the source file (ledger §2.4).

Scala Parity

Scala fpp-check suite (724 test rows, every Scala test is in the Rust suite):

class audit start final
Rust accepts, Scala rejects 3 0
Rust rejects, Scala accepts 17 0
first error at a different location 4 0
Scala tests missing from Rust 42 0
both accept 171 188
same first error, same text (+ later extras) 265 + 17 429 + 72
same first error, different wording (+ extras) 189 + 58 34 + 1

Port the Scala absolute-name delta to the AST and the parser:

- fpp_ast: ExprKind::Ident { value, is_absolute } (Scala ExprIdent) and
  QualIdent::Unqualified(Unqualified { name, is_absolute }) (Scala
  QualIdent.Unqualified). QualIdent::is_absolute (Qualified delegates to its
  qualifier), QualIdent::to_ident_list, QualIdentNodeList with
  qual_ident_node_list::{split, qualifier, name}, QualIdent::from_node_list,
  Expr::get_ident_list_opt, Expr::get_is_absolute_opt and
  TypeName::get_is_absolute_opt. Display and Serialize print the leading dot
  of an absolute name. Every level of a qualified identifier gets its own
  span (A.B in A.B.C covers A.B only). Remove the unused QualIdentKind and
  the Rust-only write_parts.
- fpp_parser: optional leading dot in identExpr, qualIdent,
  portInstanceIdentifier and tlmChannelIdentifier (interface_instance_member
  is inlined into the last two, on a new qual_ident_node_list), and dispatch
  on Dot in type_name, tlm_packet_member and expr_primary.
- DefComponentInstance.base_id is a required Expr, as in Scala and the spec;
  the parser requires `base id` and the analysis no longer defaults it to 0.
- QualifiedName::from(&QualIdent) and the constant-use names in
  BasicUseAnalyzer/UseAnalyzer come from to_ident_list/get_ident_list_opt,
  which fixes the reordered qualifiers of 4+ part qualified identifiers and
  3+ part dotted expressions.
- fpp_query: the syntactic template look-up honours is_absolute.
- Absolute names resolve like relative ones in fpp_analysis for now.

Tests: copy Scala's fpp-syntax syntax.fpp, state-machine.fpp and
template.fpp (the Rust-only template cases move to template-forms.fpp),
port the ParserSpec groups with absolute names plus def component instance,
add spec_loc four_part_name_{ok,path_error}, dependency tests for a 3-part
constant use and a 4-part type use, and an fpp_query absolute template
look-up test. The SM refs choice_cycle, cycle_and_types and choice_* change
order with the node ids (hash-ordered sets). Regenerate the Python mirror.
The renderer prints the Scala headline and the note "symbol is defined
here" at the definition; every construction site passes the bare reason
(the sites that built the prefix themselves no longer do), and the sites
that pointed the note at the definition's name point it at the definition,
as Scala's symbol.getLoc does.
Scala Map/Set-backed collections whose iteration order is observable use
insertion order (IndexMap/IndexSet): exact parity with Scala's immutable
Map/Set up to four entries, deterministic beyond.
Span::tu_location, get_dir_path (the current directory for stdin),
get_relative_path (joined and normalized lexically, without reading the file
system), compare and compare_opt mirror Scala's Location. The shared path
normalization now matches java.nio.file.Path.normalize at the root and for a
leading .. of a relative path.
A redefinition error now points at the redefining definition and the
previous one through each symbol's location, the start of its definition,
in the generic name-symbol map and in the module path of EnterSymbols
alike. This drops the Rust-only SymbolInterface::redefined_loc.
ComponentInstance::from_def resolves its component through
Analysis::get_component, and the interface imports in CheckInterfaceDefs
and CheckComponentDefs go through Analysis::get_interface_symbol and
Analysis::get_interface, so a module named where a component or an
interface is required is reported as 'invalid use of symbol M: not a
component symbol' / 'not a interface symbol' instead of being dropped.

Analysis::get_interface and Analysis::is_general_port move into
analysis.rs next to the Scala helpers they mirror; get_interface now
returns SemanticResult<Option<Interface>>.

check_for_duplicate_parameter reports at the formal parameter (which
starts at a 'ref' keyword), and CheckPortDefs calls it instead of an
inline copy.
A failed interface import is reported at the import specifier as 'failed
to import interface', with the error that caused it attached as notes,
as Scala's InterfaceImport prints it. InterfaceImplements attaches its
cause the same way, through the new Diagnostic::span_note_diagnostic.

PassiveAsync lists the import specifiers outermost first, the async
input port note points at the port definition and says 'port definition
is here', and the init-specifier, component-instance, state-machine
instance, duplicate-parameter and overlapping-id-range messages use
Scala's wording.
ExpandTemplates walks the body of a template where the template is
expanded as well as where it is defined, threading the template definition
it is inside of, so a model with an expansion before an invalid template
reports the error the expansion meets first. The list driver is now
transform_list over trans_unit, and the separate walk over a template body
is gone.
Type Display prints bool, 'array A', 'enum E', 'struct S', an unsized
anonymous array as 'array of T' and an anonymous struct as '{ a: T }' in
stored member order. Value Display prints the kind of a primitive value
('1: U8'), floats as Java's Double.toString, enum constants as '(X,0): E',
and the elements and members of aggregates. Anonymous struct value members
are kept in insertion order, so they print in the order Scala prints them.
A diagnostic now follows the include and expansion locations of each
location in the chain, as Location.toString does, so an error inside an
expansion whose specifier sits in an included file also reports where that
file is included. The flat include_spans and expand_spans stay for the
Python bindings.
…ators and stuck lists

A bare deployment or external keyword reached a bump that asserted on the
next keyword; dispatch now looks ahead. TokenSet's last token kind is
SHIFT_RIGHT, so recovery at << or >> no longer trips the debug assertion.
A member list iteration that consumes nothing now bumps the token or
leaves the list at a foreign closing delimiter instead of looping until
the step limit. Add a robustness test that places every token in every
member position and checks the parse is panic-free and lossless.
CheckSpecLocs and BuildSpecLocMap resolve a location specifier path with
Span::get_relative_path, as Scala's Location.getRelativePath does, so a path
with . or .. components names the same file as its normalized form. A
specifier whose symbol is absolute (leading dot) is keyed without the
enclosing module prefix. CheckSpecLocs is split into check_path, keyed by the
enclosing scope names, and check_dictionary_specifier, keyed by the qualified
name, as in Scala.

Ports spec_loc/shadowed_ok and shadowed_error, and adds Rust-only tests for
normalized paths.
Analysis::get_interface_instance and Analysis::get_interface now live in
analysis.rs next to their symbol helpers and return SemanticResult, built on
get_interface_instance_symbol / get_interface_symbol as in Analysis.scala:
a module named as a connection endpoint, topology port, implemented
interface, interface template argument or interface parameter type is
rejected, and a topology used before it is resolved reports 'this topology
could not be resolved before it was used here'. CheckTopologyInstances
resolves instance and import specifiers through
get_interface_instance_symbol, so 'instance M' / 'import M' naming a module
is rejected.

Port the Scala delta 4a8aa9e85: get_represented_component_instance reports
at the use under the use's name with the note at the represented
definition, and TlmChannelIdentifier resolves a bound instance template
parameter. Port tlm_packets/template_arg_channel and template_arg_topology.
PortInstanceType::show takes the analysis and prints a defined port as
the qualified name of its definition, as Scala's Type.show(a) does, so a
connection between two port types with the same unqualified name reads
'cannot connect port types M.P and N.P'. The Display impl keeps Scala's
toString (the unqualified name, or 'serial'). Connection::check_types and
check_serial_with_typed_input take the analysis to pass it through.
CheckDictionaryDefs visits module members, component members and the members
of template expansions, as Scala's ModuleAnalyzer and ComponentAnalyzer do, so
it no longer reaches definitions inside state machines. A dictionary constant
whose type is an alias type is rejected, as in Scala, where an alias type is
neither an integer nor a floating-point type.
ConstructDictionaryMap is a visitor, as in Scala: it visits module members and
the members of template expansions in model order, constructs the dictionary
of each deployment topology it reaches, and builds each telemetry packet set
from its packet specifiers. A topology copied by an expansion is visited at
the expansion and a topology in an included file at the include, so the first
error is the one Scala reports.
EnterSymbols and EnterTemplateExpansionSymbols (renamed from
EnterTemplateSymbols) now share the module entering of Scala's
EnterModuleSymbols: a module is looked up by name in the current scope,
re-opened if it is a module, and created and entered into every name group
otherwise, with update_map recording its parent. After merging an
expansion, EnterTemplateExpansionSymbols walks the expansion members again
with that base, as Scala does, and an already entered expansion is walked
in its own scope.
Per the collections policy, PortInterface's port maps, the component
dictionaries and state machine instance map, an interface's import map
and the component instance map are insertion-ordered IndexMaps, matching
the iteration order of Scala's immutable Maps for up to four entries.
The port_order bookkeeping that emulated this for two walks goes away.

So check_dictionary_names reports a duplicate name at the later
definition, the passive-component, data-product and id-range checks
report the first offender in definition order, and instances with equal
base ids are compared in definition order.
LexedStr kept none of fpp_lexer's errors, so unclosed strings, malformed
numbers and bad line continuations parsed cleanly and fpp-format rewrote
them. Each lexer error is now attached to the token that reported it and
reported first, as 'invalid token: MSG'. A character that cannot begin a
token is reported with its unicode value (and the tab note), and the
parser skips it like whitespace, as the Scala lexer does. Port the Scala
lexer test inputs.
CheckExprTypes checks numeric operands with convert_node_to_numeric /
convert_to_numeric, reporting 'cannot convert T to a numeric type' as Scala
does. A unary minus has the numeric type its operand converts to (Integer
for an enum or alias operand), and an arithmetic expression has the type its
common type converts to (spec, Arithmetic and Concatenation). A shift
operand must itself be an integer or enum type, so an alias operand is
rejected. An expression that fails to type check gets no type, and
EvalConstantExprs does not evaluate it.

Type::is_int, is_float, is_primitive, is_numeric, is_convertible_to_numeric,
is_promotable_to_array and array_size no longer look through alias types,
as Scala's AliasType inherits the base answers; has_numeric_members keeps
the look-through because the spec defines it on the underlying type.
CheckTemplateUses visits only translation unit and module members, as in
Scala: it looks up each module by name in the current nested scope, pushes
the module scope, and resolves the template name of each expansion
specifier. It no longer walks every definition through BasicUseAnalyzer.
Component dictionary ids and opcodes stay 128-bit integers. Where the
next default id or opcode, or the end of an instance's id range, would
not fit, the analysis reports an error at the element or base id instead
of overflowing.

Parameter default opcodes follow the previous opcode as the spec says;
a test pins that there is no truncation to 32 bits.
Analysis::common_type, common_type_of_pair and common_type_of_types mirror
Scala's three commonType helpers: a list of elements reports at the element
before the one that fails, with the two types in Scala's order, and every
failure is a TypeMismatch 'cannot compute common type of T1 and T2'.
Analysis::convert_types reports 'cannot convert T1 to T2'; CheckExprTypes
uses it for array, enum, struct and parameter defaults, telemetry limits
(check_limit_expr), event throttle intervals (check_throttle) and template
constant arguments. FinalizeConstantExprs uses the same helpers and reports
its conversion failures as TypeMismatch.
CheckTopologyDefs is a tree visitor, as in Scala: it resolves each topology
where the tree reaches it (translation units in input order, members in
order, an included topology at its include site, an expanded topology at
its expansion site), resolving the topologies it imports first through
their symbols, and it handles direct and pattern connection graphs and the
telemetry packet set deployment check as it visits the members, in source
order. CheckTopologyInstances visits the topologies a topology imports right
after the topology itself.

Per the collections policy the topology direct-instance maps, the
transitive import set, the port map, the pattern map, the connection graph
maps, the matched-numbering instance connection map and the template
expansion map keep insertion order; connection lists keep the most recent
connection first, as Scala's do. Pattern targets keep every written target.
CheckTemplateInterfaceArgs checks the expansions in entry order and the
arguments in parameter order.
TemplateExpansion::params is now a map from TemplateExpansion::ParamKey,
a name group paired with a parameter name, to the bound argument, as in
Scala. It is an insertion-ordered map, so the arguments are still
analyzed in the order the parameters are declared.
TlmPacketSet::packet_map and the dictionary entry maps are insertion-ordered,
as Scala's immutable maps are for small dictionaries. Duplicate packet names
are reported in model order, the channels of a packet set are checked in the
order of the instance names and then of the component's channel map, and a
channel used in several packets is reported as used in the last one in model
order.
A special port whose implied use resolves to another kind of symbol (a
module Fw.Time, say) is reported as 'invalid use of symbol Time: not a
port symbol' with a note at the definition, like the general-port case.
A matched port that names no port of the component is reported through
PortInterface::lookup_port_instance as InvalidPortInstanceId, the error
Scala's getPortInstance produces.

The port_instance, port_matching, record and spec_init test lists follow
tests.sh, with the Rust-only tests after them.
fpp_lexer:
- Treat CR LF as a newline everywhere (a lone CR stays invalid).
- Lex signed exponents (1e-10, 1.5E+3) as in fetchFraction; an identifier
  character after a float literal is an 'invalid literal number' error that
  ends the literal instead of being swallowed into it.
- A digit must be below the base (15a, 0x1g end the literal), and 0x with no
  hex digit is an 'invalid literal number' error.
- Report every invalid character as a lexer error with the code point, the
  embedded-tab notes, the '$' hint and the '<' / '>' messages, one error per
  character.
- Report an unclosed string at its opening quote; form feed and an escaped
  newline in a single-line string stay rejected, as the spec requires.
- Keep the keywords in one sorted table so that keywords display with their
  FPP spelling (U32, F32, string), and display EOF as 'end of input'.
- Port util/Chars.scala as chars.rs.

fpp_parser cursor:
- Lex the whole file up front and emit its lexer errors before any syntax
  error of the file.
- Interpret string literals: \c becomes c, a multi-line string drops the
  newline after the opening quotes and at most the first line's indent of
  spaces on each line, and CR LF becomes LF.
- '->' absorbs the newlines after it; '<<' and '>>' do not; a line
  continuation ends a run of newlines.
- Normalize 0X to 0x, trim annotations like Java's String.trim, and remove
  the panics on a pre-annotation at end of input and on non-ASCII text in
  unclosed or indented strings.
- Locate 'expected' errors at the offending token (an end of line where it
  starts, end of input at the end of the file), keep the expected list at
  end of input, and give the initial span the include chain.
- LitString.inner_span covers the raw text between the quotes.

Tests: port the Scala LexerSpec (cursor/lexer_spec.rs with the Scala inputs
in fpp_parser/tests/lexer), the full illegal-character.fpp, numeric-shift and
a CRLF fixture, and lexer and cursor unit tests for each fix.
GeneralPortInstance construction gets its size the way Scala's private
PortInstance.getArraySize does: Analysis::get_array_size for a given
size, 1 when there is none.
Conflict in fpp_diagram/src/lower_sm.rs: both sides rewrote the SpanKey doc
that named cmp_span; kept the parity/f-tools wording, which also says what
Span::compare compares beyond the key.
…where their values are read

CheckExprTypes no longer converts the base id, queue size, stack size,
priority and CPU affinity of a component instance or the port numbers of a
connection to a numeric type. ComponentInstance::from_def_component_instance
and Endpoint::from_ast check the type right before reading each value, in
fpp-check's order, so a component definition error or a passive-component
attribute error comes first as it does there.
A type use is recorded in the use-def map under its type name, qualified
identifier and name, all with the same span. handle_references now sorts the
locations and drops repeats.
…omponent

A component instance exists only once its component is in the component map,
so the dictionary entry and used-symbol walks need no skip for a missing
component.
It creates its directory link with std::os::unix::fs::symlink.
…field names

The variants and the arms of From<SemanticError> for Diagnostic follow the
declaration order of SemanticError in Error.scala, with the state machine
errors grouped. The shared 'previous occurrence' and 'port matching' notes go
through prev_loc_note and matching_loc_note, as printPrevLoc and
printMatchingLoc do. Fields take the Scala names: UndefinedSymbol.symbol_kind,
InvalidSymbol.name, InvalidPortInstanceId.symbol_kind, InvalidPortNumber.size,
DuplicateSignal.sig_name, ChoiceTypeMismatch.{to_loc1,to1,to_loc2,to2},
CallSiteTypeMismatch.{te_ty,site_ty} and InterfaceImplements.err.
InvalidQualifier carries the qualifier name and the bare message, and renders
the headline and the definition note itself. No output changes.
The methods of Analysis follow the order of the Analysis case class, then
the functions of its companion object follow in their order, with
ComponentPrefix after them. The Rust-only helpers for scopes, template
expansions and the passes come last. Apart from a doc comment on
invalid_symbol, a pure move.
Each fpp_analysis/tests/<dir>/test.rs runs the tests ported from fpp-check in
tests.sh order, then the tests with no Scala counterpart after one marker
comment, each with a line saying what it covers. The mod blocks of
integration.rs follow the fpp-check/test directory listing, and template/test.rs
carries run.sh's note on the unconnected port report. The unused
disabled-tests feature is gone.
The directories are renamed from cycles, enums, structs and types, the mod
blocks use raw identifiers where the name is a keyword, and the tests named
after keywords are r#enum, r#struct and r#type. Only the paths in the refs
change.
The header comments of the overflow, opcode and implements fixtures say what
each test checks; the line counts are unchanged, so the refs are too.
…n from module_analyzer

Every use analysis runs on Analysis, so UseAnalysisPass and visit_template_arg
fix the state to it. The passes import visit_expansion from module_analyzer,
where it is defined, and analyzer no longer re-exports it.
…vate doc link

The test analyzes its model with the pipeline the other helpers use.
ImpliedUse::annotate_emitted_errors no longer links to the private
errors::annotate_emitted_errors, so cargo doc -p fpp_analysis is warning-free.
…mlinked-root LSP tests on Unix only

Endpoint::from_ast reports a port number that does not have a numeric type as
an error; it returns None only for an instance that is unresolved or failed to
check. The two workspace tests that open a project through a symbolic link
create it with std::os::unix::fs::symlink.
Brings in #101-#111 on top of the Scala parity work.

Conflict resolutions:
- fpp_analysis: the Scala-ordered errors.rs/analysis.rs and the split
  semantics files are kept. #106 is re-applied inside them:
  InvalidTlmPacketSet is back to Scala's (loc, name, msg) shape and the new
  TlmPacketSetChannelsNotCovered / TlmPacketSetChannelsUsedAndOmitted
  variants sit next to it. check_channel_usage keeps Scala's per-channel
  scan in dictionary order to decide which error comes first, then lists
  every channel failing that check. The packet and packet set errors stay
  at the specifier, where Scala reports them (#106 moved them to the name).
  get_uncovered_channels(_for_node) serve the LSP quick fix. #105 makes partial_topology_map crate-private. #103 and #107 were
  already ported from the same Scala tests; one copy of each is kept.
- fpp_lsp_parser: #102's expect() in place of bump() on top of the
  lookahead dispatch, so the parser neither asserts nor changes acceptance.
- fpp_lsp_server: the code action and its tests next to ours.
- fpp_parser/src/error.rs: ours (the include cycle diagnostic #111 touched
  is now IncludeError::Cycle).
- fpp_python: master's nested-union generator, macros and runtime, with
  IndexSet support and our comment fixes on top; all generated files are
  regenerated with `make -C fpp_python codegen` (the stubs now live in
  python/fpp/{__init__,ast}.pyi). Tests follow the nested names and our API
  shape (19 symbol variants, TemplateArgKind and ScopeOwner unions,
  Display-based type reprs).
- Cargo.lock: master's, updated by cargo for our dependencies.
SemanticError::UndefinedSymbol renders as Scala's
"symbol NAME is not defined" with the note "looking for a KIND here".
The state machine uses resolve through the same error, so they change
with it. The refs, the fpp_diagram check for a cascade of undefined names,
and the Python diagnostics tests and doc example follow.
The .gitignore note names the checked-in stubs under python/fpp/, and the
visitor module compares its traversal with Python's ast.NodeVisitor.
The packet and packet set diagnostics point at the specifier's name, as on
master, for a narrower span in the editor. The line is the one Scala reports.
# Conflicts:
#	Cargo.lock
#	fpp_analysis/src/analysis.rs
#	fpp_analysis/src/errors.rs
#	fpp_analysis/src/passes/check_expr_types.rs
#	fpp_analysis/src/passes/check_topology_defs.rs
#	fpp_analysis/src/passes/finalize_constant_exprs.rs
#	fpp_analysis/src/semantics/tlm_packet_set.rs
#	fpp_analysis/tests/connection_pattern/undef_source_and_target.ref.txt
#	fpp_analysis/tests/constant/absolute_undef.ref.txt
#	fpp_analysis/tests/dictionary_implied_uses/member_of_constant.ref.txt
#	fpp_analysis/tests/dictionary_implied_uses/missing_user_data_size.ref.txt
#	fpp_analysis/tests/enum/rep_type_string_size_scope.ref.txt
#	fpp_analysis/tests/enum/undef_constant_1.ref.txt
#	fpp_analysis/tests/enum/undef_constant_2.ref.txt
#	fpp_analysis/tests/expr/sizeof_string_fw_store_type_not_defined.ref.txt
#	fpp_analysis/tests/port_instance/implied_port_undef_member.ref.txt
#	fpp_analysis/tests/port_instance/shadowed_time_get.ref.txt
#	fpp_analysis/tests/port_instance/undef_command_recv.ref.txt
#	fpp_analysis/tests/port_instance/undef_command_reg.ref.txt
#	fpp_analysis/tests/port_instance/undef_command_resp.ref.txt
#	fpp_analysis/tests/port_instance/undef_event.ref.txt
#	fpp_analysis/tests/port_instance/undef_param_get.ref.txt
#	fpp_analysis/tests/port_instance/undef_param_set.ref.txt
#	fpp_analysis/tests/port_instance/undef_priority_and_implied_port.ref.txt
#	fpp_analysis/tests/port_instance/undef_product_recv.ref.txt
#	fpp_analysis/tests/port_instance/undef_product_request.ref.txt
#	fpp_analysis/tests/port_instance/undef_product_send.ref.txt
#	fpp_analysis/tests/port_instance/undef_size_and_port.ref.txt
#	fpp_analysis/tests/port_instance/undef_telemetry.ref.txt
#	fpp_analysis/tests/port_instance/undef_text_event.ref.txt
#	fpp_analysis/tests/port_instance/undef_time_get.ref.txt
#	fpp_analysis/tests/redef/port.ref.txt
#	fpp_analysis/tests/state_machine/types/action_undef_type.ref.txt
#	fpp_analysis/tests/state_machine/types/array_undef_type.ref.txt
#	fpp_analysis/tests/state_machine/types/guard_undef_type.ref.txt
#	fpp_analysis/tests/state_machine/types/signal_undef_type.ref.txt
#	fpp_analysis/tests/state_machine/types/struct_undef_type.ref.txt
#	fpp_analysis/tests/state_machine/undef/absolute_state_error.ref.txt
#	fpp_analysis/tests/state_machine_defs/nested_type_undef.ref.txt
#	fpp_analysis/tests/template/expand_absolute_global_template.ref.txt
#	fpp_analysis/tests/template/expand_absolute_undefined.ref.txt
#	fpp_analysis/tests/template/expand_in_include.ref.txt
#	fpp_analysis/tests/template/type_arg_string.ref.txt
#	fpp_analysis/tests/template/type_param_implied_use.ref.txt
#	fpp_analysis/tests/tlm_packets/channel_order_ids.ref.txt
#	fpp_analysis/tests/tlm_packets/channel_order_instances.ref.txt
#	fpp_analysis/tests/tlm_packets/duplicate_packet_name_explicit_ids.ref.txt
#	fpp_analysis/tests/tlm_packets/test.rs
#	fpp_analysis/tests/tlm_packets/topology_order_a.ref.txt
#	fpp_analysis/tests/tlm_packets/topology_order_include.ref.txt
#	fpp_analysis/tests/tlm_packets/topology_order_template.ref.txt
#	fpp_analysis/tests/tlm_packets/used_channel_order.ref.txt
#	fpp_analysis/tests/type/string_length_shadowed.ref.txt
#	fpp_analysis/tests/type/string_missing_default_size_constant.ref.txt
#	fpp_analysis/tests/type/string_missing_fw_size_store_type.ref.txt
#	fpp_analysis/tests/type/string_size_type_shadowed.ref.txt
#	fpp_lsp_parser/src/grammar/topology.rs
#	fpp_lsp_parser/src/grammar/types.rs
#	fpp_lsp_server/src/workspace_tests.rs
#	fpp_python/fpp.pyi
#	fpp_python/tests/test_api_contracts.py
#	fpp_python/tests/test_templates.py
@Kronos3
Kronos3 merged commit eb22567 into master Oct 11, 2026
30 checks passed
@Kronos3
Kronos3 deleted the feature/templates branch October 11, 2026 19:21
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.

1 participant