Fix tuple-to-tuple implicit conversion compatibility check#1283
Conversation
When generic type inference for operators like Equivalent(T, T) produces conflicting inferences from direct argument types, the system now also tries implicit conversion targets of each argument as potential type bindings. This allows FHIR.CodeableConcept ~ Code to resolve by finding the common type Concept (via FHIR.CodeableConcept→Concept model conversion and Code→Concept implicit conversion).
There was a problem hiding this comment.
Pull request overview
Fixes #788, where Equivalent(FHIR.CodeableConcept, Code) (and similar generic operator resolutions) failed because InvocationBuilder.MatchSignature only tried each argument's exact type as a generic binding. When neither argument type was directly compatible with the other, but both had implicit conversions to a common type (e.g. Concept), resolution gave up. The fix adds a fallback step that also tries each argument's implicit conversion targets — both system-level (Code → Concept, numeric widenings, Date → DateTime) and model-level (FHIRHelpers conversions) — as candidate generic bindings.
Changes:
- Add
CoercionProvider.GetImplicitConversionTargetsenumerating system- and model-level implicit conversion targets for a type. - Extend
InvocationBuilder.MatchSignaturewith a fallback inference pass over those targets when direct inference yields no compatible binding. - Un-ignore the
FhirCodeableConcept_EquivalentTo_Coderegression test now that the scenario resolves.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| Cql/Cql.CqlToElm/CoercionProvider.cs | Adds GetImplicitConversionTargets to enumerate implicit conversion targets for use in generic inference. |
| Cql/Cql.CqlToElm/InvocationBuilder.cs | Adds a fallback in MatchSignature that uses implicit conversion targets as candidate generic bindings. |
| Cql/CqlToElmTests/(tests)/EquivalentTest.cs | Removes [Ignore] from FhirCodeableConcept_EquivalentTo_Code to reactivate the previously failing test. |
|
@copilot Apply all review comments |
…version, handle List/Interval, drop ParameterTypeSpecifier guard
Applied both review comments in commit
All 2420 CqlToElmTests still pass. |
|
@copilot - This is duplication of existing functionality, i.e. we have now added a function for implicit conversions for simple types that already existed (internal bool HasImplicitConversion(TypeSpecifier from, TypeSpecifier to)) - the case that this PR tries to fix should already have been supported, so instead of adding new code, we should focus on fixing the bug and finding why the existing code does not handle this. |
… of truth for system-level conversions
The duplication is now removed. The system-level conversion list lives in a single private static Regarding "the case should already have been supported": the issue is structural — |
|
@copilot This is a smart solution, but I think it is uncommon in most programming languages that we need to bind generic arguments to a coercable type (so not polymorphic, but through conversion operators) to resolve an overload. Do we have any proof that CQL requires this kind of binding? |
The CQL specification doesn't define the generic type inference algorithm explicitly, so there's no direct "proof" text. However, the spec does support this interpretation: From the Developer's Guide (§ Function Resolution):
This explicitly lists generics alongside implicit conversion as mechanisms that allow invocation signatures to match. For The alternative interpretation (bind T only to a directly-supplied argument type) would make That said, the spec is silent on the exact algorithm, so you're right that this is an interpretation rather than a guaranteed requirement. If you have a preference for a different approach (for example, requiring the user to write an explicit conversion like |
alexzautke
left a comment
There was a problem hiding this comment.
@copilot Add a dedicated test that covers the Concept vs CodeableConcept mismatch, preferably as a Library test based on CQL
Added a dedicated CQL Library-based test |
|
@alexzautke - is there anything more I need to do on this PR? |
|
@ewoutkramer No, I will take this up either during DevDays with Bryn or at the next Connectathon |
|
Unassigned myself, since I don't think there is a concrete action for me to do here at thim moment. |
|
Some evidence from the dqm-content-qicore-2025 measure corpus arguing in favor of landing this PR: The pattern this fixes is pervasive in the CMS measure CQL. The reference translator resolves it exactly the way this PR proposes. In the shipped ELM, The internal translator fails on it today. Running the exact QICoreCommon construct through the current CqlToElm front-end (develop): codesystem "ConditionClinicalStatusCodes": 'http://terminology.hl7.org/CodeSystem/condition-clinical'
code "active": 'active' from "ConditionClinicalStatusCodes"
define fluent function isActive(condition Condition):
condition.clinicalStatus ~ "active"fails with: — the error this PR's un-ignored test ( Bottom line: the CMS measures only work today because they arrive as pre-translated reference ELM. Translating the dqm-content-qicore-2025 CQL sources with the SDK's own front-end (e.g. PackagerCLI Related: #1355 fixes the back-end/runtime half of the same theme (performing the implicit conversions the ELM implies, inside tuple unions). The two PRs touch disjoint projects and compose: once the front-end starts producing operands unified via implicit conversion targets, the back-end paths fixed there handle them. One observation worth carrying over: compatibility checks should be directional — Generated by Claude Code |
Uh oh!
There was an error while loading. Please reload this page.