|
1 | 1 | /*
|
2 |
| - * Copyright (c) 2023, NCQA and contributors |
| 2 | + * Copyright (c) 2023, Firely, NCQA and contributors |
3 | 3 | * See the file CONTRIBUTORS for details.
|
4 | 4 | *
|
5 | 5 | * This file is licensed under the BSD 3-Clause license
|
@@ -127,6 +127,20 @@ private Expression TranslateElement(Element element) =>
|
127 | 127 | {
|
128 | 128 | using (PushElement(element))
|
129 | 129 | {
|
| 130 | + /* |
| 131 | + This code is useful for setting breakpoints to inspect the expression tree at a specific element. |
| 132 | + The ELM json must be modified to add an annotation tags with a debug counter first. |
| 133 | +
|
| 134 | + var debugCounter = element.annotation |
| 135 | + ?.OfType<Annotation>() |
| 136 | + .FirstOrDefault()?.t.FirstOrDefault(t => t.name == "debug") |
| 137 | + ?.value; |
| 138 | + if (debugCounter == "42") // Identify the correct debug counter from the ELM file |
| 139 | + { |
| 140 | + ; // Set a breakpoint here |
| 141 | + } |
| 142 | + */ |
| 143 | + |
130 | 144 | Expression? expression = element switch
|
131 | 145 | {
|
132 | 146 | //@formatter:off
|
@@ -325,12 +339,12 @@ Expression ConvertToResultType()
|
325 | 339 | {
|
326 | 340 | if (_typeResolver.GetListElementType(left.Type, throwError: false) is { } leftListElemType
|
327 | 341 | && _typeResolver.GetListElementType(right.Type, throwError: false) is { } rightListElemType
|
328 |
| - && leftListElemType == rightListElemType) |
| 342 | + && ElmTupleTypeUtility.AreCompatibleForUnionOperation(leftListElemType, rightListElemType)) |
329 | 343 | return [left, right];
|
330 | 344 |
|
331 | 345 | if (left.Type.IsCqlInterval(out var leftPointType)
|
332 | 346 | && right.Type.IsCqlInterval(out var rightPointType)
|
333 |
| - && leftPointType == rightPointType) |
| 347 | + && ElmTupleTypeUtility.AreCompatibleForUnionOperation(leftPointType, rightPointType)) |
334 | 348 | return [left, right];
|
335 | 349 | }
|
336 | 350 |
|
@@ -442,8 +456,7 @@ protected Expression List(List list)
|
442 | 456 | array = Expression.NewArrayBounds(elementType, Expression.Constant(0));
|
443 | 457 | }
|
444 | 458 |
|
445 |
| - var asEnumerable = array.NewTypeAsExpression(typeof(IEnumerable<>).MakeGenericType(elementType)); |
446 |
| - return asEnumerable; |
| 459 | + return array; |
447 | 460 | }
|
448 | 461 |
|
449 | 462 | throw this.NewExpressionBuildingException($"List is the wrong type");
|
@@ -1918,7 +1931,7 @@ private void QueryDumpDebugInfoToLog(Query query)
|
1918 | 1931 | Type valueTupleType = _typeResolver.GetListElementType(funcResultType, true)!;
|
1919 | 1932 | FieldInfo[] valueTupleFields = valueTupleType.GetFields(bfPublicInstance | BindingFlags.GetField);
|
1920 | 1933 |
|
1921 |
| - Type cqlTupleType = _tupleBuilderCache.CreateOrGetTupleTypeFor(sourceListElementTypes.Zip(aliases)); |
| 1934 | + Type cqlTupleType = _tupleBuilderCache.CreateOrGetTupleTypeFor(sourceListElementTypes.Zip(aliases).ToList()); |
1922 | 1935 | PropertyInfo[] cqlTupleProperties = cqlTupleType.GetProperties(bfPublicInstance | BindingFlags.SetProperty);
|
1923 | 1936 |
|
1924 | 1937 | Debug.Assert(valueTupleFields.Length > 0);
|
@@ -2354,6 +2367,55 @@ void throwCannotCastIfNoMatch(TypeConversion result)
|
2354 | 2367 | throw this.NewExpressionBuildingException($"Cannot convert {input.Type} to {outputType}.");
|
2355 | 2368 | }
|
2356 | 2369 | }
|
| 2370 | + |
| 2371 | + /// <summary> |
| 2372 | + /// Pre-processes an expression tree to fix missing resultTypeSpecifier on AliasRef elements |
| 2373 | + /// by copying the type information from the source elements that define the aliases. |
| 2374 | + /// </summary> |
| 2375 | + /// <param name="elmExpression">The root ELM expression to process</param> |
| 2376 | + private void FixMissingAliasRefTypeSpecifiers(Elm.Expression elmExpression) |
| 2377 | + { |
| 2378 | + // First pass: Build dictionary of alias names to their source elements (with resultTypeSpecifier) |
| 2379 | + var aliasSources = new Dictionary<string, (Element sourceElement, TypeSpecifier sourceResultTypeSpecifier)>(); |
| 2380 | + |
| 2381 | + var aliasCollector = new ElmTreeWalker(node => |
| 2382 | + { |
| 2383 | + switch (node) |
| 2384 | + { |
| 2385 | + case AliasedQuerySource aqs when !string.IsNullOrEmpty(aqs.alias) && aqs.expression?.resultTypeSpecifier != null: |
| 2386 | + aliasSources[aqs.alias] = (aqs, aqs.expression.resultTypeSpecifier); |
| 2387 | + break; |
| 2388 | + |
| 2389 | + case LetClause let when !string.IsNullOrEmpty(let.identifier) && let.expression?.resultTypeSpecifier != null: |
| 2390 | + aliasSources[let.identifier] = (let, let.expression.resultTypeSpecifier); |
| 2391 | + break; |
| 2392 | + } |
| 2393 | + return true; // Continue walking children |
| 2394 | + }); |
| 2395 | + |
| 2396 | + aliasCollector.Start(elmExpression); |
| 2397 | + |
| 2398 | + // Second pass: Find AliasRef elements without resultTypeSpecifier and copy it from the dictionary |
| 2399 | + var aliasRefFixer = new ElmTreeWalker(node => |
| 2400 | + { |
| 2401 | + if (node is AliasRef aliasRef |
| 2402 | + && !string.IsNullOrEmpty(aliasRef.name) |
| 2403 | + && aliasRef.resultTypeSpecifier == null |
| 2404 | + && aliasSources.TryGetValue(aliasRef.name, out var source)) |
| 2405 | + { |
| 2406 | + _logger.LogDebug( |
| 2407 | + "Fixing missing resultTypeSpecifier for AliasRef named '{alias}' @ {aliasLocator}, originating from {sourceType} @ {sourceLocator}. {expressionBuilderContext}", |
| 2408 | + aliasRef.name, aliasRef.locator, |
| 2409 | + source.sourceElement.GetType().Name, |
| 2410 | + source.sourceElement.locator, |
| 2411 | + DebuggerView); |
| 2412 | + aliasRef.resultTypeSpecifier = source.sourceResultTypeSpecifier; |
| 2413 | + } |
| 2414 | + return true; // Continue walking children |
| 2415 | + }); |
| 2416 | + |
| 2417 | + aliasRefFixer.Start(elmExpression); |
| 2418 | + } |
2357 | 2419 | }
|
2358 | 2420 |
|
2359 | 2421 | #endregion
|
|
0 commit comments