Use nested deref patterns to simplify compiler code
9875993b24336e99dfecf83f56db50e87e73f277493e711f0f4e6e68dc536dae
Now that the seed supports auto-deref patterns, use them throughout the
compiler to collapse sequential let-case/if-let-case chains that
destructure through pointers.
Resolver:
- getRecord(): Type::Nominal(NominalType::Record(recInfo))
- isVoidUnion(): Type::Nominal(NominalType::Union(unionType))
- resolveMatch(): Type::Nominal(NominalType::Union(u))
- resolveFnDeclBody(): SymbolData::Value { type: Type::Fn(fnType), .. }
- resolveInstanceMethodBodies(): same nested record pattern
Lowerer:
- recordInfoFromType()/unionInfoFromType(): single nested pattern
- isAggregateType(): Type::Optional(Type::Pointer { .. })
- optionalNilReg(): match with nested Optional patterns
- buildNilOptional(): match with nested Optional patterns
- lowerConstUnionVariantInto(): Type::Nominal(NominalType::Record(..))
- lowerAggregateEq(): Type::Optional(Type::Slice { .. })
Resolver tests:
- Collapsed SymbolData::Value + Type::Fn patterns (~10 instances)
- Collapsed Type::Nominal + NominalType::Record patterns
1 parent
97607e7a
lib/std/lang/lower.rad
+37 -48
| 1509 | 1509 | }); |
|
| 1510 | 1510 | } |
|
| 1511 | 1511 | return; |
|
| 1512 | 1512 | } |
|
| 1513 | 1513 | ||
| 1514 | - | let case resolver::Type::Nominal(payloadNominal) = payloadType else { |
|
| 1515 | - | throw LowerError::MissingMetadata; |
|
| 1516 | - | }; |
|
| 1517 | - | let case resolver::NominalType::Record(payloadRec) = *payloadNominal else { |
|
| 1514 | + | let case resolver::Type::Nominal(resolver::NominalType::Record(payloadRec)) = payloadType else { |
|
| 1518 | 1515 | throw LowerError::ExpectedRecord; |
|
| 1519 | 1516 | }; |
|
| 1520 | 1517 | let payloadLayout = payloadRec.layout; |
|
| 1521 | 1518 | try lowerConstRecordCtorInto(self, payloadArgs, payloadRec, dataPrefix, b); |
|
| 1522 | 1519 |
| 2980 | 2977 | /// Get the register to compare against `0` for optional `nil` checking. |
|
| 2981 | 2978 | /// For null-ptr-optimized types, loads the data pointer, or returns it |
|
| 2982 | 2979 | /// directly for scalar pointers. For aggregates, returns the tag register. |
|
| 2983 | 2980 | fn optionalNilReg(self: *mut FnLowerer, val: il::Val, typ: resolver::Type) -> il::Reg throws (LowerError) { |
|
| 2984 | 2981 | let reg = emitValToReg(self, val); |
|
| 2985 | - | let case resolver::Type::Optional(inner) = typ else return reg; |
|
| 2986 | 2982 | ||
| 2987 | - | if let case resolver::Type::Slice { .. } = *inner { |
|
| 2988 | - | let ptrReg = nextReg(self); |
|
| 2989 | - | emitLoadW64At(self, ptrReg, reg, SLICE_PTR_OFFSET); |
|
| 2990 | - | return ptrReg; |
|
| 2991 | - | } |
|
| 2992 | - | if let case resolver::Type::Pointer { .. } = *inner { |
|
| 2993 | - | return reg; |
|
| 2983 | + | match typ { |
|
| 2984 | + | case resolver::Type::Optional(resolver::Type::Slice { .. }) => { |
|
| 2985 | + | let ptrReg = nextReg(self); |
|
| 2986 | + | emitLoadW64At(self, ptrReg, reg, SLICE_PTR_OFFSET); |
|
| 2987 | + | return ptrReg; |
|
| 2988 | + | } |
|
| 2989 | + | case resolver::Type::Optional(resolver::Type::Pointer { .. }) => return reg, |
|
| 2990 | + | case resolver::Type::Optional(_) => return tvalTagReg(self, reg), |
|
| 2991 | + | else => return reg, |
|
| 2994 | 2992 | } |
|
| 2995 | - | return tvalTagReg(self, reg); |
|
| 2996 | 2993 | } |
|
| 2997 | 2994 | ||
| 2998 | 2995 | /// Lower an optional nil check (`opt == nil` or `opt != nil`). |
|
| 2999 | 2996 | fn lowerNilCheck(self: *mut FnLowerer, opt: *ast::Node, isEq: bool) -> il::Val throws (LowerError) { |
|
| 3000 | 2997 | let optTy = resolver::typeFor(self.low.resolver, opt) else { |
| 3808 | 3805 | // Record and Aggregate Type Helpers // |
|
| 3809 | 3806 | /////////////////////////////////////// |
|
| 3810 | 3807 | ||
| 3811 | 3808 | /// Extract the nominal record info from a resolver type. |
|
| 3812 | 3809 | fn recordInfoFromType(typ: resolver::Type) -> ?resolver::RecordType { |
|
| 3813 | - | let case resolver::Type::Nominal(info) = typ else { |
|
| 3814 | - | return nil; |
|
| 3815 | - | }; |
|
| 3816 | - | let case resolver::NominalType::Record(recInfo) = *info else { |
|
| 3817 | - | return nil; |
|
| 3818 | - | }; |
|
| 3810 | + | let case resolver::Type::Nominal(resolver::NominalType::Record(recInfo)) = typ |
|
| 3811 | + | else return nil; |
|
| 3819 | 3812 | return recInfo; |
|
| 3820 | 3813 | } |
|
| 3821 | 3814 | ||
| 3822 | 3815 | /// Extract the nominal union info from a resolver type. |
|
| 3823 | 3816 | fn unionInfoFromType(typ: resolver::Type) -> ?resolver::UnionType { |
|
| 3824 | - | let case resolver::Type::Nominal(info) = typ else { |
|
| 3825 | - | return nil; |
|
| 3826 | - | }; |
|
| 3827 | - | let case resolver::NominalType::Union(unionInfo) = *info else { |
|
| 3828 | - | return nil; |
|
| 3829 | - | }; |
|
| 3817 | + | let case resolver::Type::Nominal(resolver::NominalType::Union(unionInfo)) = typ |
|
| 3818 | + | else return nil; |
|
| 3830 | 3819 | return unionInfo; |
|
| 3831 | 3820 | } |
|
| 3832 | 3821 | ||
| 3833 | 3822 | /// Return the effective type of a node after any coercion applied by |
|
| 3834 | 3823 | /// the resolver. `lowerExpr` already materializes the coercion in the |
| 3855 | 3844 | } |
|
| 3856 | 3845 | case resolver::Type::Slice { .. } => return true, |
|
| 3857 | 3846 | case resolver::Type::TraitObject { .. } => return true, |
|
| 3858 | 3847 | case resolver::Type::Array(_) => return true, |
|
| 3859 | 3848 | case resolver::Type::Nil => return true, |
|
| 3860 | - | else => { |
|
| 3861 | - | // Optional pointers are scalar (single word). All other |
|
| 3862 | - | // optionals, including optional slices with NPO, are aggregates. |
|
| 3863 | - | if let case resolver::Type::Optional(inner) = typ { |
|
| 3864 | - | if let case resolver::Type::Pointer { .. } = *inner { |
|
| 3865 | - | return false; |
|
| 3866 | - | } |
|
| 3867 | - | return true; |
|
| 3868 | - | } |
|
| 3849 | + | case resolver::Type::Optional(resolver::Type::Pointer { .. }) => { |
|
| 3850 | + | // Optional pointers are scalar (single word) due to NPO. |
|
| 3869 | 3851 | return false; |
|
| 3870 | 3852 | } |
|
| 3853 | + | case resolver::Type::Optional(_) => { |
|
| 3854 | + | // All other optionals, including optional slices, are aggregates. |
|
| 3855 | + | return true; |
|
| 3856 | + | } |
|
| 3857 | + | else => return false, |
|
| 3871 | 3858 | } |
|
| 3872 | 3859 | } |
|
| 3873 | 3860 | ||
| 3874 | 3861 | /// Check if a resolver type is a small aggregate that can be |
|
| 3875 | 3862 | /// passed or returned by value in a register. |
| 4030 | 4017 | /// Build a `nil` value for an optional type. |
|
| 4031 | 4018 | /// |
|
| 4032 | 4019 | /// For optional pointers (`?*T`), returns an immediate `0` (null pointer). |
|
| 4033 | 4020 | /// For other optionals, builds a tagged aggregate with tag set to `0` (absent). |
|
| 4034 | 4021 | fn buildNilOptional(self: *mut FnLowerer, optType: resolver::Type) -> il::Val throws (LowerError) { |
|
| 4035 | - | let case resolver::Type::Optional(inner) = optType else { |
|
| 4036 | - | throw LowerError::ExpectedOptional; |
|
| 4037 | - | }; |
|
| 4038 | - | if let case resolver::Type::Pointer { .. } = *inner { |
|
| 4039 | - | return il::Val::Imm(0); |
|
| 4040 | - | } |
|
| 4041 | - | if let case resolver::Type::Slice { item, mutable } = *inner { |
|
| 4042 | - | return try buildSliceValue(self, item, mutable, il::Val::Imm(0), il::Val::Imm(0), il::Val::Imm(0)); |
|
| 4022 | + | match optType { |
|
| 4023 | + | case resolver::Type::Optional(resolver::Type::Pointer { .. }) => { |
|
| 4024 | + | return il::Val::Imm(0); |
|
| 4025 | + | } |
|
| 4026 | + | case resolver::Type::Optional(resolver::Type::Slice { item, mutable }) => { |
|
| 4027 | + | return try buildSliceValue(self, item, mutable, il::Val::Imm(0), il::Val::Imm(0), il::Val::Imm(0)); |
|
| 4028 | + | } |
|
| 4029 | + | case resolver::Type::Optional(inner) => { |
|
| 4030 | + | let valOffset = resolver::getOptionalValOffset(*inner) as i32; |
|
| 4031 | + | return try buildTagged(self, resolver::getTypeLayout(optType), 0, nil, *inner, 1, valOffset); |
|
| 4032 | + | } |
|
| 4033 | + | else => throw LowerError::ExpectedOptional, |
|
| 4043 | 4034 | } |
|
| 4044 | - | let valOffset = resolver::getOptionalValOffset(*inner) as i32; |
|
| 4045 | - | return try buildTagged(self, resolver::getTypeLayout(optType), 0, nil, *inner, 1, valOffset); |
|
| 4046 | 4035 | } |
|
| 4047 | 4036 | ||
| 4048 | 4037 | /// Build a result value for throwing functions. |
|
| 4049 | 4038 | fn buildResult( |
|
| 4050 | 4039 | self: *mut FnLowerer, |
| 4498 | 4487 | a: il::Reg, |
|
| 4499 | 4488 | b: il::Reg, |
|
| 4500 | 4489 | offset: i32 |
|
| 4501 | 4490 | ) -> il::Val throws (LowerError) { |
|
| 4502 | 4491 | match typ { |
|
| 4492 | + | case resolver::Type::Optional(resolver::Type::Slice { item, mutable }) => { |
|
| 4493 | + | // Optional slices use null pointer optimization. |
|
| 4494 | + | return try lowerSliceEq(self, item, mutable, a, b, offset); |
|
| 4495 | + | } |
|
| 4503 | 4496 | case resolver::Type::Optional(inner) => { |
|
| 4504 | - | if let case resolver::Type::Slice { item, mutable } = *inner { |
|
| 4505 | - | // Optional slices use null pointer optimization. |
|
| 4506 | - | return try lowerSliceEq(self, item, mutable, a, b, offset); |
|
| 4507 | - | } |
|
| 4508 | 4497 | return try lowerOptionalEq(self, *inner, a, b, offset); |
|
| 4509 | 4498 | } |
|
| 4510 | 4499 | case resolver::Type::Slice { item, mutable } => |
|
| 4511 | 4500 | return try lowerSliceEq(self, item, mutable, a, b, offset), |
|
| 4512 | 4501 | case resolver::Type::Array(arr) => |
lib/std/lang/resolver.rad
+6 -19
| 1512 | 1512 | return tag; |
|
| 1513 | 1513 | } |
|
| 1514 | 1514 | ||
| 1515 | 1515 | /// Check if a type is a union without payloads. |
|
| 1516 | 1516 | pub fn isVoidUnion(ty: Type) -> bool { |
|
| 1517 | - | let case Type::Nominal(info) = ty |
|
| 1518 | - | else return false; |
|
| 1519 | - | let case NominalType::Union(unionType) = *info |
|
| 1517 | + | let case Type::Nominal(NominalType::Union(unionType)) = ty |
|
| 1520 | 1518 | else return false; |
|
| 1521 | 1519 | return unionType.isAllVoid; |
|
| 1522 | 1520 | } |
|
| 1523 | 1521 | ||
| 1524 | 1522 | /// Check if a type should be treated as an address-like value. |
| 1869 | 1867 | } |
|
| 1870 | 1868 | } |
|
| 1871 | 1869 | ||
| 1872 | 1870 | /// Get the record info from a record type. |
|
| 1873 | 1871 | pub fn getRecord(ty: Type) -> ?RecordType { |
|
| 1874 | - | let case Type::Nominal(info) = ty else return nil; |
|
| 1875 | - | let case NominalType::Record(recInfo) = *info else return nil; |
|
| 1876 | - | ||
| 1872 | + | let case Type::Nominal(NominalType::Record(recInfo)) = ty else return nil; |
|
| 1877 | 1873 | return recInfo; |
|
| 1878 | 1874 | } |
|
| 1879 | 1875 | ||
| 1880 | 1876 | /// Auto-dereference a type: if it's a pointer, return the target type. |
|
| 1881 | 1877 | pub fn autoDeref(ty: Type) -> Type { |
| 3096 | 3092 | let sym = symbolFor(self, node) else { |
|
| 3097 | 3093 | // The function declaration failed to type check, therefore |
|
| 3098 | 3094 | // no symbol was associated with it. |
|
| 3099 | 3095 | return; |
|
| 3100 | 3096 | }; |
|
| 3101 | - | let case SymbolData::Value { type, .. } = sym.data else { |
|
| 3097 | + | let case SymbolData::Value { type: Type::Fn(fnType), .. } = sym.data else { |
|
| 3102 | 3098 | panic "resolveFnDeclBody: unexpected symbol data for function"; |
|
| 3103 | 3099 | }; |
|
| 3104 | - | let case Type::Fn(fnType) = type else { |
|
| 3105 | - | panic "resolveFnDeclBody: unexpected type for function"; |
|
| 3106 | - | }; |
|
| 3107 | 3100 | let retTy = *fnType.returnType; |
|
| 3108 | 3101 | let isExtern = ast::hasAttribute(sym.attrs, ast::Attribute::Extern); |
|
| 3109 | 3102 | let isIntrinsic = ast::hasAttribute(sym.attrs, ast::Attribute::Intrinsic); |
|
| 3110 | 3103 | ||
| 3111 | 3104 | if isIntrinsic and not isExtern { |
| 3616 | 3609 | ||
| 3617 | 3610 | // Symbol may be absent if [`resolveInstanceDecl`] reported an error |
|
| 3618 | 3611 | // for this method (eg. unknown method name). Skip gracefully. |
|
| 3619 | 3612 | let sym = symbolFor(self, methodNode) |
|
| 3620 | 3613 | else continue; |
|
| 3621 | - | let case SymbolData::Value { type, .. } = sym.data |
|
| 3614 | + | let case SymbolData::Value { type: Type::Fn(fnType), .. } = sym.data |
|
| 3622 | 3615 | else panic "resolveInstanceMethodBodies: expected value symbol"; |
|
| 3623 | - | let case Type::Fn(fnType) = type |
|
| 3624 | - | else panic "resolveInstanceMethodBodies: expected function type"; |
|
| 3625 | 3616 | ||
| 3626 | 3617 | // Enter function scope. |
|
| 3627 | 3618 | enterFn(self, methodNode, fnType); |
|
| 3628 | 3619 | ||
| 3629 | 3620 | // Bind the receiver parameter. |
| 4224 | 4215 | let subjectTy = try infer(self, sw.subject); |
|
| 4225 | 4216 | let subject = unwrapMatchSubject(subjectTy); |
|
| 4226 | 4217 | ||
| 4227 | 4218 | if let case Type::Optional(inner) = subject.effectiveTy { |
|
| 4228 | 4219 | try resolveMatchOptional(self, node, sw, inner); |
|
| 4229 | - | } else if let case Type::Nominal(info) = subject.effectiveTy { |
|
| 4230 | - | if let case NominalType::Union(u) = *info { |
|
| 4231 | - | try resolveMatchUnion(self, node, sw, subject.effectiveTy, u, subject.by); |
|
| 4232 | - | } else { |
|
| 4233 | - | try resolveMatchGeneric(self, node, sw, subject.effectiveTy); |
|
| 4234 | - | } |
|
| 4220 | + | } else if let case Type::Nominal(NominalType::Union(u)) = subject.effectiveTy { |
|
| 4221 | + | try resolveMatchUnion(self, node, sw, subject.effectiveTy, u, subject.by); |
|
| 4235 | 4222 | } else { |
|
| 4236 | 4223 | try resolveMatchGeneric(self, node, sw, subject.effectiveTy); |
|
| 4237 | 4224 | } |
|
| 4238 | 4225 | ||
| 4239 | 4226 | // Mark last non-guarded prong as exhaustive. |
lib/std/lang/resolver/tests.rad
+13 -37
| 353 | 353 | else panic "getUnionVariantPayload: not a union"; |
|
| 354 | 354 | for i in 0..unionType.variants.len { |
|
| 355 | 355 | if mem::eq(unionType.variants[i].name, variantName) { |
|
| 356 | 356 | let payloadType = unionType.variants[i].valueType; |
|
| 357 | 357 | // Unwrap single-field unlabeled records to get the inner type. |
|
| 358 | - | if let case super::Type::Nominal(nominalInfo) = payloadType { |
|
| 359 | - | if let case super::NominalType::Record(recInfo) = *nominalInfo { |
|
| 360 | - | if not recInfo.labeled and recInfo.fields.len == 1 { |
|
| 361 | - | return recInfo.fields[0].fieldType; |
|
| 362 | - | } |
|
| 358 | + | if let case super::Type::Nominal(super::NominalType::Record(recInfo)) = payloadType { |
|
| 359 | + | if not recInfo.labeled and recInfo.fields.len == 1 { |
|
| 360 | + | return recInfo.fields[0].fieldType; |
|
| 363 | 361 | } |
|
| 364 | 362 | } |
|
| 365 | 363 | return payloadType; |
|
| 366 | 364 | } |
|
| 367 | 365 | } |
| 1466 | 1464 | let callStmt = try getBlockStmt(blockNode, 1); |
|
| 1467 | 1465 | ||
| 1468 | 1466 | { // Verify the function symbol captures an empty parameter list and void return. |
|
| 1469 | 1467 | let sym = super::symbolFor(&a, fnNode) |
|
| 1470 | 1468 | else throw testing::TestError::Failed; |
|
| 1471 | - | let case super::SymbolData::Value { type: valType, .. } = sym.data |
|
| 1472 | - | else throw testing::TestError::Failed; |
|
| 1473 | - | ||
| 1474 | - | let case super::Type::Fn(fnTy) = valType |
|
| 1469 | + | let case super::SymbolData::Value { type: super::Type::Fn(fnTy), .. } = sym.data |
|
| 1475 | 1470 | else throw testing::TestError::Failed; |
|
| 1476 | 1471 | try testing::expect(fnTy.paramTypes.len == 0); |
|
| 1477 | 1472 | try testing::expect(*fnTy.returnType == super::Type::Void); |
|
| 1478 | 1473 | } |
|
| 1479 | 1474 | { // Checking that the type of the call matches the function return type. |
|
| 1480 | 1475 | let callExpr = try expectExprStmtType(&a, callStmt, super::Type::Void); |
|
| 1481 | 1476 | ||
| 1482 | 1477 | let fnSym = super::symbolFor(&a, fnNode) |
|
| 1483 | 1478 | else throw testing::TestError::Failed; |
|
| 1484 | - | let case super::SymbolData::Value { type: fnValType, .. } = fnSym.data |
|
| 1485 | - | else throw testing::TestError::Failed; |
|
| 1486 | - | let case super::Type::Fn(fnTy) = fnValType |
|
| 1479 | + | let case super::SymbolData::Value { type: super::Type::Fn(fnTy), .. } = fnSym.data |
|
| 1487 | 1480 | else throw testing::TestError::Failed; |
|
| 1488 | 1481 | try expectType(&a, callExpr, *fnTy.returnType); |
|
| 1489 | 1482 | } |
|
| 1490 | 1483 | } |
|
| 1491 | 1484 |
| 1502 | 1495 | let callStmt = try getBlockStmt(blockNode, 1); |
|
| 1503 | 1496 | ||
| 1504 | 1497 | { // Function returns i32 with no parameters. |
|
| 1505 | 1498 | let sym = super::symbolFor(&a, fnNode) |
|
| 1506 | 1499 | else throw testing::TestError::Failed; |
|
| 1507 | - | let case super::SymbolData::Value { type: valType, .. } = sym.data |
|
| 1508 | - | else throw testing::TestError::Failed; |
|
| 1509 | - | let case super::Type::Fn(fnTy) = valType |
|
| 1500 | + | let case super::SymbolData::Value { type: super::Type::Fn(fnTy), .. } = sym.data |
|
| 1510 | 1501 | else throw testing::TestError::Failed; |
|
| 1511 | 1502 | try testing::expect(fnTy.paramTypes.len == 0); |
|
| 1512 | 1503 | try testing::expect(*fnTy.returnType == super::Type::I32); |
|
| 1513 | 1504 | } |
|
| 1514 | 1505 | { // Call expression should inherit the function's return type. |
|
| 1515 | 1506 | let callExpr = try expectExprStmtType(&a, callStmt, super::Type::I32); |
|
| 1516 | 1507 | ||
| 1517 | 1508 | let fnSym = super::symbolFor(&a, fnNode) |
|
| 1518 | 1509 | else throw testing::TestError::Failed; |
|
| 1519 | - | let case super::SymbolData::Value { type: fnValType, .. } = fnSym.data |
|
| 1520 | - | else throw testing::TestError::Failed; |
|
| 1521 | - | let case super::Type::Fn(fnTy) = fnValType |
|
| 1510 | + | let case super::SymbolData::Value { type: super::Type::Fn(fnTy), .. } = fnSym.data |
|
| 1522 | 1511 | else throw testing::TestError::Failed; |
|
| 1523 | 1512 | try expectType(&a, callExpr, *fnTy.returnType); |
|
| 1524 | 1513 | } |
|
| 1525 | 1514 | } |
|
| 1526 | 1515 |
| 1537 | 1526 | let callStmt = try getBlockStmt(blockNode, 2); |
|
| 1538 | 1527 | ||
| 1539 | 1528 | { // Single parameter propagates nominal type onto the symbol and parameter node. |
|
| 1540 | 1529 | let sym = super::symbolFor(&a, fnNode) |
|
| 1541 | 1530 | else throw testing::TestError::Failed; |
|
| 1542 | - | let case super::SymbolData::Value { type: valType, .. } = sym.data |
|
| 1543 | - | else throw testing::TestError::Failed; |
|
| 1544 | - | let case super::Type::Fn(fnTy) = valType |
|
| 1531 | + | let case super::SymbolData::Value { type: super::Type::Fn(fnTy), .. } = sym.data |
|
| 1545 | 1532 | else throw testing::TestError::Failed; |
|
| 1546 | 1533 | try testing::expect(fnTy.paramTypes.len == 1); |
|
| 1547 | 1534 | try testing::expect(*fnTy.paramTypes[0] == super::Type::I8); |
|
| 1548 | 1535 | try testing::expect(*fnTy.returnType == super::Type::Void); |
|
| 1549 | 1536 |
| 1556 | 1543 | } |
|
| 1557 | 1544 | { // Call should resolve to void, matching the function's return type. |
|
| 1558 | 1545 | let callExpr = try expectExprStmtType(&a, callStmt, super::Type::Void); |
|
| 1559 | 1546 | let fnSym = super::symbolFor(&a, fnNode) |
|
| 1560 | 1547 | else throw testing::TestError::Failed; |
|
| 1561 | - | let case super::SymbolData::Value { type: fnValType, .. } = fnSym.data |
|
| 1562 | - | else throw testing::TestError::Failed; |
|
| 1563 | - | let case super::Type::Fn(fnTy) = fnValType |
|
| 1548 | + | let case super::SymbolData::Value { type: super::Type::Fn(fnTy), .. } = fnSym.data |
|
| 1564 | 1549 | else throw testing::TestError::Failed; |
|
| 1565 | 1550 | try expectType(&a, callExpr, *fnTy.returnType); |
|
| 1566 | 1551 | } |
|
| 1567 | 1552 | } |
|
| 1568 | 1553 |
| 1579 | 1564 | let callStmt = try getBlockStmt(blockNode, 3); |
|
| 1580 | 1565 | ||
| 1581 | 1566 | { // Ensure multi-parameter signatures record both argument types. |
|
| 1582 | 1567 | let sym = super::symbolFor(&a, fnNode) |
|
| 1583 | 1568 | else throw testing::TestError::Failed; |
|
| 1584 | - | let case super::SymbolData::Value { type: valType, .. } = sym.data |
|
| 1585 | - | else throw testing::TestError::Failed; |
|
| 1586 | - | let case super::Type::Fn(fnTy) = valType |
|
| 1569 | + | let case super::SymbolData::Value { type: super::Type::Fn(fnTy), .. } = sym.data |
|
| 1587 | 1570 | else throw testing::TestError::Failed; |
|
| 1588 | 1571 | try testing::expect(fnTy.paramTypes.len == 2); |
|
| 1589 | 1572 | try testing::expect(*fnTy.paramTypes[0] == super::Type::I8); |
|
| 1590 | 1573 | try testing::expect(*fnTy.paramTypes[1] == super::Type::I32); |
|
| 1591 | 1574 | try testing::expect(*fnTy.returnType == super::Type::Void); |
| 1601 | 1584 | } |
|
| 1602 | 1585 | { // Call expression should again mirror the function return type. |
|
| 1603 | 1586 | let callExpr = try expectExprStmtType(&a, callStmt, super::Type::Void); |
|
| 1604 | 1587 | let fnSym = super::symbolFor(&a, fnNode) |
|
| 1605 | 1588 | else throw testing::TestError::Failed; |
|
| 1606 | - | let case super::SymbolData::Value { type: fnValType, .. } = fnSym.data |
|
| 1607 | - | else throw testing::TestError::Failed; |
|
| 1608 | - | let case super::Type::Fn(fnTy) = fnValType |
|
| 1589 | + | let case super::SymbolData::Value { type: super::Type::Fn(fnTy), .. } = fnSym.data |
|
| 1609 | 1590 | else throw testing::TestError::Failed; |
|
| 1610 | 1591 | try expectType(&a, callExpr, *fnTy.returnType); |
|
| 1611 | 1592 | } |
|
| 1612 | 1593 | } |
|
| 1613 | 1594 |
| 1623 | 1604 | let fnNode = try getBlockStmt(blockNode, 0); |
|
| 1624 | 1605 | ||
| 1625 | 1606 | { // Function symbol should be visible for recursive calls within its own body. |
|
| 1626 | 1607 | let sym = super::symbolFor(&a, fnNode) |
|
| 1627 | 1608 | else throw testing::TestError::Failed; |
|
| 1628 | - | let case super::SymbolData::Value { type: valType, .. } = sym.data |
|
| 1629 | - | else throw testing::TestError::Failed; |
|
| 1630 | - | ||
| 1631 | - | let case super::Type::Fn(fnTy) = valType |
|
| 1609 | + | let case super::SymbolData::Value { type: super::Type::Fn(fnTy), .. } = sym.data |
|
| 1632 | 1610 | else throw testing::TestError::Failed; |
|
| 1633 | 1611 | try testing::expect(fnTy.paramTypes.len == 1); |
|
| 1634 | 1612 | try testing::expect(*fnTy.paramTypes[0] == super::Type::Bool); |
|
| 1635 | 1613 | try testing::expect(*fnTy.returnType == super::Type::Bool); |
|
| 1636 | 1614 | } |
| 3694 | 3672 | ||
| 3695 | 3673 | // Verify the array constant has the correct type with length 3. |
|
| 3696 | 3674 | let arrStmt = try getBlockStmt(result.root, 1); |
|
| 3697 | 3675 | let sym = super::symbolFor(&a, arrStmt) |
|
| 3698 | 3676 | else throw testing::TestError::Failed; |
|
| 3699 | - | let case super::SymbolData::Constant { type: constType, .. } = sym.data |
|
| 3700 | - | else throw testing::TestError::Failed; |
|
| 3701 | - | let case super::Type::Array(arrType) = constType |
|
| 3677 | + | let case super::SymbolData::Constant { type: super::Type::Array(arrType), .. } = sym.data |
|
| 3702 | 3678 | else throw testing::TestError::Failed; |
|
| 3703 | 3679 | try testing::expect(arrType.length == 3); |
|
| 3704 | 3680 | } |
|
| 3705 | 3681 | ||
| 3706 | 3682 | /// Test that a record field can use a constant as its array length. |