77import java .util .Arrays ;
88import java .util .List ;
99import software .amazon .smithy .rulesengine .language .evaluation .Scope ;
10- import software .amazon .smithy .rulesengine .language .evaluation .type .AnyType ;
1110import software .amazon .smithy .rulesengine .language .evaluation .type .OptionalType ;
1211import software .amazon .smithy .rulesengine .language .evaluation .type .Type ;
1312import software .amazon .smithy .rulesengine .language .evaluation .value .Value ;
1716import software .amazon .smithy .utils .SmithyUnstableApi ;
1817
1918/**
20- * A coalesce function that returns the first non-empty value, with type-safe fallback handling .
19+ * A coalesce function that returns the first non-empty value.
2120 * At runtime, returns the left value unless it's EmptyValue, in which case returns the right value.
2221 *
2322 * <p>Type checking rules:
2423 * <ul>
2524 * <li>{@code coalesce(T, T) => T} (same types)</li>
26- * <li>{@code coalesce(T, AnyType) => T} (AnyType adapts to concrete type)</li>
27- * <li>{@code coalesce(AnyType, T) => T} (AnyType adapts to concrete type)</li>
28- * <li>{@code coalesce(T, S) => S} (if T.isA(S), i.e., S is more general)</li>
29- * <li>{@code coalesce(T, S) => T} (if S.isA(T), i.e., T is more general)</li>
30- * <li>{@code coalesce(Optional<T>, S) => common_type(T, S)} (unwraps optional)</li>
31- * <li>{@code coalesce(T, Optional<S>) => common_type(T, S)} (unwraps optional)</li>
32- * <li>{@code coalesce(Optional<T>, Optional<S>) => Optional<common_type(T, S)>}</li>
25+ * <li>{@code coalesce(Optional<T>, T) => T} (unwraps optional)</li>
26+ * <li>{@code coalesce(T, Optional<T>) => T} (unwraps optional)</li>
27+ * <li>{@code coalesce(Optional<T>, Optional<T>) => Optional<T>}</li>
3328 * </ul>
3429 *
35- * <p>Special handling for AnyType: Since AnyType can masquerade as any type, when coalescing
36- * with a concrete type, the concrete type is used as the result type.
37- *
3830 * <p>Supports chaining:
3931 * {@code coalesce(opt1, coalesce(opt2, coalesce(opt3, default)))}
4032 *
@@ -80,29 +72,6 @@ public <R> R accept(ExpressionVisitor<R> visitor) {
8072 return visitor .visitCoalesce (args .get (0 ), args .get (1 ));
8173 }
8274
83- // Type checking rules for coalesce:
84- //
85- // This function returns the first non-empty value with type-safe fallback handling.
86- // The type resolution follows these rules:
87- //
88- // 1. If both types are identical, use that type
89- // 2. Special handling for AnyType: Since AnyType.isA() always returns true (it can masquerade as any type), we
90- // need to handle it specially. When coalescing AnyType with a concrete type, we use the concrete type as the
91- // result, since AnyType can adapt to it at runtime.
92- // 3. For other types, we use the isA relationship to find the more general type:
93- // - If left.isA(right), then right is more general, use right
94- // - If right.isA(left), then left is more general, use left
95- // 4. If no type relationship exists, throw a type mismatch error
96- //
97- // The result is wrapped in Optional only if BOTH inputs are Optional, since coalesce(optional, required)
98- // guarantees a non-empty result.
99- //
100- // Examples:
101- // - coalesce(String, String) => String
102- // - coalesce(Optional<String>, String) => String
103- // - coalesce(Optional<String>, Optional<String>) => Optional<String>
104- // - coalesce(String, AnyType) => String (AnyType adapts)
105- // - coalesce(SubType, SuperType) => SuperType (more general)
10675 @ Override
10776 public Type typeCheck (Scope <Type > scope ) {
10877 List <Expression > args = getArguments ();
@@ -113,38 +82,23 @@ public Type typeCheck(Scope<Type> scope) {
11382
11483 Type leftType = args .get (0 ).typeCheck (scope );
11584 Type rightType = args .get (1 ).typeCheck (scope );
116-
117- // Find the least upper bound (most specific common type)
118- Type resultType = lubForCoalesce (leftType , rightType );
119-
120- // Only return Optional if both sides can be empty
121- if (leftType instanceof OptionalType && rightType instanceof OptionalType ) {
122- return Type .optionalType (resultType );
85+ Type leftInner = getInnerType (leftType );
86+ Type rightInner = getInnerType (rightType );
87+
88+ // Both must be the same type (after unwrapping optionals)
89+ if (!leftInner .equals (rightInner )) {
90+ throw new IllegalArgumentException (String .format (
91+ "Type mismatch in coalesce: %s and %s must be the same type" ,
92+ leftType ,
93+ rightType ));
12394 }
12495
125- return resultType ;
126- }
127-
128- // Finds the least upper bound (LUB) for coalesce type checking.
129- // The LUB is the most specific type that both input types can be assigned to.
130- // Special handling for AnyType: it adapts to concrete types rather than dominating them.
131- private static Type lubForCoalesce (Type a , Type b ) {
132- Type ai = getInnerType (a );
133- Type bi = getInnerType (b );
134-
135- if (ai .equals (bi )) {
136- return ai ;
137- } else if (ai instanceof AnyType ) {
138- return bi ; // AnyType adapts to concrete type
139- } else if (bi instanceof AnyType ) {
140- return ai ; // AnyType adapts to concrete type
141- } else if (ai .isA (bi )) {
142- return bi ; // bi is more general
143- } else if (bi .isA (ai )) {
144- return ai ; // ai is more general
96+ // Only return Optional if both sides are optional
97+ if (leftType instanceof OptionalType && rightType instanceof OptionalType ) {
98+ return Type .optionalType (leftInner );
14599 }
146100
147- throw new IllegalArgumentException ( "Type mismatch in coalesce: " + a + " and " + b + " have no common type" ) ;
101+ return leftInner ;
148102 }
149103
150104 private static Type getInnerType (Type t ) {
0 commit comments