Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Nov 4, 2025

Changing unit-of-measure annotations in types (e.g., int<meters> to int<seconds>) doesn't trigger rebuild in dependent projects, causing stale binaries and incorrect type checking.

Root Causes

1. Signature hash excluded from normal assembly MVIDs
F# signature hash (containing UoM metadata) was only used for reference assemblies, not implementation assemblies. Since int<a> and int<b> compile to identical IL (int32), MVID remained unchanged.

2. Signature hash didn't differentiate concrete measure types
hashMeasure only hashed measure type parameters (Measure.Var) but ignored concrete measure tycons (Measure.Const), making int<a> and int<b> hash identically.

Changes

  • fsc.fs: Always calculate and pass refAssemblySignatureHash to MVID calculation
  • ilwrite.fs: Include signature hash in deterministic MVID when available (removed referenceAssemblyOnly check)
  • TypeHashing.fs: Hash both Measure.Var and Measure.Const in hashMeasure using ListMeasureConOccsWithNonZeroExponents
  • ImpliedSignatureHashTests.fs: Add test coverage for UoM changes in record fields, function parameters, and return types

Example

// Project A
[<Measure>] type meters
[<Measure>] type seconds
type Distance = { Value: float<meters> }

// Changing to float<seconds> now triggers rebuild in dependent Project B
type Distance = { Value: float<seconds> }

Fixes #11234

Original prompt

This section details on the original issue you should resolve

<issue_title>Units-of-measure changes between F# projects aren't always noticed by Build, requiring Rebuild to fix errors</issue_title>
<issue_description>This issue has been moved from a ticket on Developer Community.


Repro case is in https://github.com/marklam/UnitsMismatch - the README is copied below:

This repository demonstrates a bug in the F# build system related to type providers and units of measure across different projects.
The issue is that changing the unit of measure in a record type is not always noticed by the build system, causing errors to not appear, or not go away
(depending on whether your edit is causing or fixing a units-of-measure mismatch).

Steps to Reproduce

  1. **Initial State (build error) **

    • In ClassLibrary1/TypeWIthUnits.fs, the record is defined as:
      type RecordWithUnits =
          {
              Value: int&lt;a&gt;
          }
    • In UnitsMismatch/Usage.fs), a value of type int&lt;b&gt; is supplied to RecordWithUnits.
    • Result: The project will not build due to a units-of-measure mismatch error.
  2. Change the UOM (to fix the error)

    • Change the declaration of RecordWithUnits to:
      type RecordWithUnits =
          {
              Value: int&lt;b&gt;
          }
    • Action: Perform a Build (not Rebuild).
    • Result: The build fails with the same error as before, even though the code should now be correct.
  3. Perform a Rebuild

    • Now perform a Rebuild operation.
    • Result: The build succeeds as expected.
  4. Change Back to Original UOM (to cause a build error)

    • Change the declaration of RecordWithUnits back to:
      type RecordWithUnits =
          {
              Value: int&lt;a&gt;
          }
    • Action: Perform a Build.
    • Result: The build succeeds, even though it should fail due to the units-of-measure mismatch.
  5. Rebuild Again

    • Perform a Rebuild.
    • Result: The expected build error appears.

Summary

  • Build does not always pick up changes to units of measure in record types, leading to stale or incorrect build results.
  • Rebuild is required to get the correct build status after such changes.

Expected Behavior

Changing the unit of measure in a type definition should always result in the correct build errors or success, even with an incremental Build.

Actual Behavior

Build does not always detect changes to units of measure, leading to confusing or incorrect build results. Only a full Rebuild produces the correct outcome.


Original Comments

Feedback Bot on 9/17/2025, 05:08 AM:

We have directed your feedback to the appropriate engineering team for further evaluation. The team will review the feedback and notify you about the next steps.

<agent_instructions>This issue reports a problem with build (via dotnet build) not being invalidated when unit of measure changes.

I assume this is related to the MVID calculation feature (it is written in ilwrite) and how we hash contents. Reason is, unit of measure causes the IL to be the same. It is only the unit of measure that changes, and that is only recorded in the F# signature blod (.NET resource) as result of SignatureHash.fs hashing. Find the place where types are hashed and how units of measure are hashed - I believe this is the problem.

Try to test this by creating 2 different on-disk projects, calling dotnet build, then just changing the unit of measure, and calling dotnet build again. Just like in the repro steps.

Since this involves msbuild machinery, if we want to keep it around as an automated test, we should use the "mvid" tests which are part of ComponentTests suite. i.e. a piece of code, after changing unit of measure, should get different mvid. The tests will be called something like .mvid., or possibly "deterministic".</agent_instructions>

Comments on the Issue (you are @copilot in this section)

Note

Custom agent used: F# agent
Generic agent for F# coding following the coding guidelines of F# from MsLearn


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

Copilot AI and others added 2 commits November 4, 2025 12:50
Always calculate and include F# signature hash in deterministic MVID calculation.
Previously, signature hash was only used for reference assemblies, causing
unit-of-measure changes (like int<a> to int<b>) to be missed since they
compile to identical IL but differ in F# signature metadata.

- Calculate signature hash for all assemblies (not just reference assemblies)
- Include signature hash in MVID calculation when available (not just for reference assemblies)
- Add tests to verify unit-of-measure changes affect signature hash

Co-authored-by: T-Gro <[email protected]>
The hashMeasure function was only hashing measure type parameters (Measure.Var)
but not concrete measure tycons (Measure.Const like 'a' in int<a>).
This caused int<a> and int<b> to have the same signature hash.

Now hash both measure variables and measure constants with their exponents.

Co-authored-by: T-Gro <[email protected]>
Copilot AI changed the title [WIP] Fix units-of-measure changes not detected by build system Fix units-of-measure changes not triggering rebuild in cross-project dependencies Nov 4, 2025
Copilot AI requested a review from T-Gro November 4, 2025 13:15
Copilot finished work on behalf of T-Gro November 4, 2025 13:15
@T-Gro T-Gro closed this Nov 5, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Archived in project

2 participants