Add support for standalone methods
29fdb962aefa472d7106707547edef9cd332b0e5791c0972ed5949eba3b35546
Eg. `fn (f *Fnord) f() -> i32 { ... }`
1 parent
d91f35a2
lib/std/lang/ast.rad
+6 -3
| 791 | 791 | InstanceDecl { |
|
| 792 | 792 | /// Trait name identifier. |
|
| 793 | 793 | traitName: *Node, |
|
| 794 | 794 | /// Target type identifier. |
|
| 795 | 795 | targetType: *Node, |
|
| 796 | - | /// Method definition nodes ([`InstanceMethodDecl`]). |
|
| 796 | + | /// Method definition nodes ([`MethodDecl`]). |
|
| 797 | 797 | methods: *mut [*Node], |
|
| 798 | 798 | }, |
|
| 799 | - | /// Method definition inside an instance block. |
|
| 800 | - | InstanceMethodDecl { |
|
| 799 | + | /// Method definition with a receiver. |
|
| 800 | + | /// Used both inside `instance` blocks and as standalone methods. |
|
| 801 | + | MethodDecl { |
|
| 801 | 802 | /// Method name identifier. |
|
| 802 | 803 | name: *Node, |
|
| 803 | 804 | /// Receiver binding name ([`Ident`] node). |
|
| 804 | 805 | receiverName: *Node, |
|
| 805 | 806 | /// Receiver type node (eg. `*mut Arena`). |
|
| 806 | 807 | receiverType: *Node, |
|
| 807 | 808 | /// Function signature. |
|
| 808 | 809 | sig: FnSig, |
|
| 809 | 810 | /// Method body. |
|
| 810 | 811 | body: *Node, |
|
| 812 | + | /// Optional attribute list. |
|
| 813 | + | attrs: ?Attributes, |
|
| 811 | 814 | }, |
|
| 812 | 815 | } |
|
| 813 | 816 | ||
| 814 | 817 | /// Full AST node with shared metadata and variant-specific payload. |
|
| 815 | 818 | pub record Node { |
lib/std/lang/ast/printer.rad
+1 -1
| 453 | 453 | } |
|
| 454 | 454 | case super::NodeValue::InstanceDecl { traitName, targetType, methods } => { |
|
| 455 | 455 | let children = nodeListToExprs(a, &methods[..]); |
|
| 456 | 456 | return sexpr::block(a, "instance", &[toExpr(a, traitName), toExpr(a, targetType)], children); |
|
| 457 | 457 | } |
|
| 458 | - | case super::NodeValue::InstanceMethodDecl { name, receiverName, receiverType, sig, body } => { |
|
| 458 | + | case super::NodeValue::MethodDecl { name, receiverName, receiverType, sig, body, .. } => { |
|
| 459 | 459 | let params = sexpr::list(a, "params", nodeListToExprs(a, &sig.params[..])); |
|
| 460 | 460 | let ret = toExprOrNull(a, sig.returnType); |
|
| 461 | 461 | return sexpr::block(a, "method", &[toExpr(a, receiverType), toExpr(a, receiverName), toExpr(a, name), params, ret], &[toExpr(a, body)]); |
|
| 462 | 462 | } |
|
| 463 | 463 | else => return sexpr::sym("?"), |
lib/std/lang/lower.rad
+168 -124
| 776 | 776 | try lowerDataDecl(low, node, decl.value, false); |
|
| 777 | 777 | } |
|
| 778 | 778 | case ast::NodeValue::InstanceDecl { traitName, targetType, methods } => { |
|
| 779 | 779 | try lowerInstanceDecl(low, node, traitName, targetType, methods); |
|
| 780 | 780 | } |
|
| 781 | + | case ast::NodeValue::MethodDecl { name, receiverName, receiverType, sig, body, .. } => { |
|
| 782 | + | if let f = try lowerMethodDecl(low, node, name, receiverName, sig, body) { |
|
| 783 | + | low.fns.append(f, low.allocator); |
|
| 784 | + | } |
|
| 785 | + | } |
|
| 781 | 786 | else => {}, |
|
| 782 | 787 | } |
|
| 783 | 788 | } |
|
| 784 | 789 | return defaultFnIdx; |
|
| 785 | 790 | } |
| 1025 | 1030 | // later from inherited supertrait methods. |
|
| 1026 | 1031 | let mut methodNames: [*[u8]; ast::MAX_TRAIT_METHODS] = undefined; |
|
| 1027 | 1032 | let mut methodNameSet: [bool; ast::MAX_TRAIT_METHODS] = [false; ast::MAX_TRAIT_METHODS]; |
|
| 1028 | 1033 | ||
| 1029 | 1034 | for methodNode in methods { |
|
| 1030 | - | let case ast::NodeValue::InstanceMethodDecl { |
|
| 1031 | - | name, receiverName, receiverType, sig, body |
|
| 1035 | + | let case ast::NodeValue::MethodDecl { |
|
| 1036 | + | name, receiverName, receiverType, sig, body, .. |
|
| 1032 | 1037 | } = methodNode.value else continue; |
|
| 1033 | 1038 | ||
| 1034 | 1039 | let case ast::NodeValue::Ident(mName) = name.value else { |
|
| 1035 | 1040 | throw LowerError::ExpectedIdentifier; |
|
| 1036 | 1041 | }; |
|
| 1037 | 1042 | let qualName = instanceMethodName(self, nil, typeName, mName); |
|
| 1038 | - | ||
| 1039 | - | // Get the function type from the resolver. |
|
| 1040 | - | let data = resolver::nodeData(self.resolver, methodNode); |
|
| 1041 | - | let case resolver::Type::Fn(fnType) = data.ty else { |
|
| 1042 | - | throw LowerError::ExpectedFunction; |
|
| 1043 | - | }; |
|
| 1044 | - | ||
| 1045 | - | // Register the method symbol. |
|
| 1046 | - | if let sym = data.sym { |
|
| 1047 | - | registerFnSym(self, sym, qualName); |
|
| 1048 | - | } |
|
| 1049 | - | ||
| 1050 | - | // Lower the method as a normal function. |
|
| 1051 | - | let mut fnLow = fnLowerer(self, methodNode, fnType, qualName); |
|
| 1052 | - | if requiresReturnParam(fnType) { |
|
| 1053 | - | fnLow.returnReg = nextReg(&mut fnLow); |
|
| 1054 | - | } |
|
| 1055 | - | let lowParams = try lowerParams(&mut fnLow, *fnType, sig.params, receiverName); |
|
| 1056 | - | let func = try! alloc::alloc(self.arena, @sizeOf(il::Fn), @alignOf(il::Fn)) as *mut il::Fn; |
|
| 1057 | - | ||
| 1058 | - | *func = il::Fn { |
|
| 1059 | - | name: qualName, |
|
| 1060 | - | params: lowParams, |
|
| 1061 | - | returnType: ilType(self, *fnType.returnType), |
|
| 1062 | - | isExtern: false, |
|
| 1063 | - | isLeaf: true, |
|
| 1064 | - | blocks: &[], |
|
| 1065 | - | }; |
|
| 1066 | - | if fnType.throwList.len > 0 { |
|
| 1067 | - | func.returnType = il::Type::W64; |
|
| 1068 | - | } |
|
| 1069 | - | func.blocks = try lowerFnBody(&mut fnLow, body); |
|
| 1070 | - | func.isLeaf = fnLow.isLeaf; |
|
| 1043 | + | let func = try lowerMethod(self, methodNode, qualName, receiverName, sig, body) |
|
| 1044 | + | else continue; |
|
| 1071 | 1045 | self.fns.append(func, self.allocator); |
|
| 1072 | 1046 | ||
| 1073 | 1047 | let method = resolver::findTraitMethod(traitInfo, mName) |
|
| 1074 | 1048 | else panic "lowerInstanceDecl: method not found in trait"; |
|
| 1075 | 1049 |
| 1106 | 1080 | isUndefined: false, |
|
| 1107 | 1081 | values: &values[..traitInfo.methods.len as u32], |
|
| 1108 | 1082 | }, self.allocator); |
|
| 1109 | 1083 | } |
|
| 1110 | 1084 | ||
| 1085 | + | /// Lower a method node into an IL function with the given qualified name. |
|
| 1086 | + | /// Shared by both instance methods and standalone methods. |
|
| 1087 | + | fn lowerMethod( |
|
| 1088 | + | self: *mut Lowerer, |
|
| 1089 | + | node: *ast::Node, |
|
| 1090 | + | qualName: *[u8], |
|
| 1091 | + | receiverName: *ast::Node, |
|
| 1092 | + | sig: ast::FnSig, |
|
| 1093 | + | body: *ast::Node, |
|
| 1094 | + | ) -> ?*il::Fn throws (LowerError) { |
|
| 1095 | + | let data = resolver::nodeData(self.resolver, node); |
|
| 1096 | + | let case resolver::Type::Fn(fnType) = data.ty else { |
|
| 1097 | + | throw LowerError::ExpectedFunction; |
|
| 1098 | + | }; |
|
| 1099 | + | let sym = data.sym else throw LowerError::MissingSymbol(node); |
|
| 1100 | + | registerFnSym(self, sym, qualName); |
|
| 1101 | + | ||
| 1102 | + | let mut fnLow = fnLowerer(self, node, fnType, qualName); |
|
| 1103 | + | if requiresReturnParam(fnType) { |
|
| 1104 | + | fnLow.returnReg = nextReg(&mut fnLow); |
|
| 1105 | + | } |
|
| 1106 | + | let lowParams = try lowerParams(&mut fnLow, *fnType, sig.params, receiverName); |
|
| 1107 | + | let func = try! alloc::alloc(self.arena, @sizeOf(il::Fn), @alignOf(il::Fn)) as *mut il::Fn; |
|
| 1108 | + | ||
| 1109 | + | *func = il::Fn { |
|
| 1110 | + | name: qualName, |
|
| 1111 | + | params: lowParams, |
|
| 1112 | + | returnType: ilType(self, *fnType.returnType), |
|
| 1113 | + | isExtern: false, |
|
| 1114 | + | isLeaf: true, |
|
| 1115 | + | blocks: &[], |
|
| 1116 | + | }; |
|
| 1117 | + | if fnType.throwList.len > 0 { |
|
| 1118 | + | func.returnType = il::Type::W64; |
|
| 1119 | + | } |
|
| 1120 | + | func.blocks = try lowerFnBody(&mut fnLow, body); |
|
| 1121 | + | func.isLeaf = fnLow.isLeaf; |
|
| 1122 | + | ||
| 1123 | + | return func; |
|
| 1124 | + | } |
|
| 1125 | + | ||
| 1126 | + | /// Lower a standalone method declaration. |
|
| 1127 | + | /// Produces a function with qualified name `Type::method`. |
|
| 1128 | + | fn lowerMethodDecl( |
|
| 1129 | + | self: *mut Lowerer, |
|
| 1130 | + | node: *ast::Node, |
|
| 1131 | + | name: *ast::Node, |
|
| 1132 | + | receiverName: *ast::Node, |
|
| 1133 | + | sig: ast::FnSig, |
|
| 1134 | + | body: *ast::Node, |
|
| 1135 | + | ) -> ?*il::Fn throws (LowerError) { |
|
| 1136 | + | let sym = resolver::nodeData(self.resolver, node).sym |
|
| 1137 | + | else throw LowerError::MissingSymbol(node); |
|
| 1138 | + | let case ast::NodeValue::Ident(mName) = name.value |
|
| 1139 | + | else throw LowerError::ExpectedIdentifier; |
|
| 1140 | + | let me = resolver::findMethodBySymbol(self.resolver, sym) |
|
| 1141 | + | else throw LowerError::MissingMetadata; |
|
| 1142 | + | let qualName = instanceMethodName(self, nil, me.concreteTypeName, mName); |
|
| 1143 | + | ||
| 1144 | + | return try lowerMethod(self, node, qualName, receiverName, sig, body); |
|
| 1145 | + | } |
|
| 1146 | + | ||
| 1111 | 1147 | /// Check if a function should be lowered. |
|
| 1112 | 1148 | fn shouldLowerFn(decl: *ast::FnDecl, buildTest: bool) -> bool { |
|
| 1113 | 1149 | if checkAttr(decl.attrs, ast::Attribute::Test) { |
|
| 1114 | 1150 | return buildTest; |
|
| 1115 | 1151 | } |
| 6025 | 6061 | // Type of the try expression, which is either the return type of the function |
|
| 6026 | 6062 | // if successful, or an optional of it, if using `try?`. |
|
| 6027 | 6063 | let tryExprTy = resolver::typeFor(self.low.resolver, node) else { |
|
| 6028 | 6064 | throw LowerError::MissingType(node); |
|
| 6029 | 6065 | }; |
|
| 6030 | - | // Check for trait method dispatch. |
|
| 6066 | + | // Check for trait method dispatch or standalone method call. |
|
| 6031 | 6067 | let mut resVal: il::Val = undefined; |
|
| 6068 | + | let callNodeExtra = resolver::nodeData(self.low.resolver, t.expr).extra; |
|
| 6032 | 6069 | if let case resolver::NodeExtra::TraitMethodCall { |
|
| 6033 | 6070 | traitInfo, methodIndex |
|
| 6034 | - | } = resolver::nodeData(self.low.resolver, t.expr).extra { |
|
| 6071 | + | } = callNodeExtra { |
|
| 6035 | 6072 | resVal = try lowerTraitMethodCall(self, t.expr, callExpr, traitInfo, methodIndex); |
|
| 6073 | + | } else if let case resolver::NodeExtra::MethodCall { method } = callNodeExtra { |
|
| 6074 | + | resVal = try lowerMethodCall(self, t.expr, callExpr, method); |
|
| 6036 | 6075 | } else { |
|
| 6037 | 6076 | resVal = try lowerCall(self, t.expr, callExpr); |
|
| 6038 | 6077 | } |
|
| 6039 | 6078 | let base = emitValToReg(self, resVal); // The result value. |
|
| 6040 | 6079 | let tagReg = resultTagReg(self, base); // The result tag. |
| 6443 | 6482 | } |
|
| 6444 | 6483 | // Check for trait method dispatch. |
|
| 6445 | 6484 | if let case resolver::NodeExtra::TraitMethodCall { traitInfo, methodIndex } = nodeData { |
|
| 6446 | 6485 | return try lowerTraitMethodCall(self, node, call, traitInfo, methodIndex); |
|
| 6447 | 6486 | } |
|
| 6487 | + | // Check for standalone method call. |
|
| 6488 | + | if let case resolver::NodeExtra::MethodCall { method } = nodeData { |
|
| 6489 | + | return try lowerMethodCall(self, node, call, method); |
|
| 6490 | + | } |
|
| 6448 | 6491 | if let sym = resolver::nodeData(self.low.resolver, call.callee).sym { |
|
| 6449 | 6492 | if let case resolver::SymbolData::Type(nominal) = sym.data { |
|
| 6450 | 6493 | let case resolver::NominalType::Record(_) = *nominal else { |
|
| 6451 | 6494 | throw LowerError::ExpectedRecord; |
|
| 6452 | 6495 | }; |
| 6509 | 6552 | typ: il::Type::W64, |
|
| 6510 | 6553 | dst: fnPtrReg, |
|
| 6511 | 6554 | src: vtableReg, |
|
| 6512 | 6555 | offset: slotOffset, |
|
| 6513 | 6556 | }); |
|
| 6514 | - | ||
| 6515 | - | // Check if the method needs a hidden return parameter. |
|
| 6516 | 6557 | let methodFnType = traitInfo.methods[methodIndex].fnType; |
|
| 6517 | - | let retTy = *methodFnType.returnType; |
|
| 6518 | - | let returnParam = requiresReturnParam(methodFnType); |
|
| 6519 | 6558 | ||
| 6520 | - | // Build args: data pointer (receiver) + user args. |
|
| 6521 | - | let argOffset: u32 = 1 if returnParam else 0; |
|
| 6559 | + | // Build args: optional return param slot + data pointer (receiver) + user args. |
|
| 6560 | + | let argOffset: u32 = 1 if requiresReturnParam(methodFnType) else 0; |
|
| 6522 | 6561 | let args = try allocVals(self, call.args.len + 1 + argOffset); |
|
| 6523 | - | ||
| 6524 | - | // Data pointer is the receiver (first argument after hidden return param). |
|
| 6525 | 6562 | args[argOffset] = il::Val::Reg(dataReg); |
|
| 6526 | 6563 | ||
| 6527 | - | // Lower user arguments. |
|
| 6528 | 6564 | for arg, i in call.args { |
|
| 6529 | 6565 | args[i + 1 + argOffset] = try lowerExpr(self, arg); |
|
| 6530 | 6566 | } |
|
| 6567 | + | return try emitCallValue(self, il::Val::Reg(fnPtrReg), methodFnType, args); |
|
| 6568 | + | } |
|
| 6531 | 6569 | ||
| 6532 | - | // Allocate the return buffer when needed. |
|
| 6533 | - | if returnParam { |
|
| 6534 | - | if methodFnType.throwList.len > 0 { |
|
| 6535 | - | let successType = *methodFnType.returnType; |
|
| 6536 | - | let layout = resolver::getResultLayout( |
|
| 6537 | - | successType, methodFnType.throwList); |
|
| 6570 | + | /// Emit a function call with return-parameter and small-aggregate handling. |
|
| 6571 | + | /// |
|
| 6572 | + | /// All call lowering paths (regular, trait method, standalone method) converge |
|
| 6573 | + | /// here after preparing the callee value, function type, and argument array. |
|
| 6574 | + | /// The `args` slice must already include a slot at index zero for the hidden |
|
| 6575 | + | /// return parameter; that slot is filled by this function. |
|
| 6576 | + | fn emitCallValue( |
|
| 6577 | + | self: *mut FnLowerer, |
|
| 6578 | + | callee: il::Val, |
|
| 6579 | + | fnInfo: *resolver::FnType, |
|
| 6580 | + | args: *mut [il::Val], |
|
| 6581 | + | ) -> il::Val throws (LowerError) { |
|
| 6582 | + | let retTy = *fnInfo.returnType; |
|
| 6538 | 6583 | ||
| 6584 | + | if requiresReturnParam(fnInfo) { |
|
| 6585 | + | if fnInfo.throwList.len > 0 { |
|
| 6586 | + | let layout = resolver::getResultLayout(retTy, fnInfo.throwList); |
|
| 6539 | 6587 | args[0] = il::Val::Reg(emitReserveLayout(self, layout)); |
|
| 6540 | 6588 | } else { |
|
| 6541 | 6589 | args[0] = il::Val::Reg(try emitReserve(self, retTy)); |
|
| 6542 | 6590 | } |
|
| 6543 | 6591 | let dst = nextReg(self); |
|
| 6544 | 6592 | ||
| 6545 | 6593 | emit(self, il::Instr::Call { |
|
| 6546 | 6594 | retTy: il::Type::W64, |
|
| 6547 | 6595 | dst, |
|
| 6548 | - | func: il::Val::Reg(fnPtrReg), |
|
| 6596 | + | func: callee, |
|
| 6549 | 6597 | args, |
|
| 6550 | 6598 | }); |
|
| 6551 | 6599 | return il::Val::Reg(dst); |
|
| 6552 | 6600 | } |
|
| 6553 | - | ||
| 6554 | - | // Scalar call: allocate destination register for non-void return types. |
|
| 6555 | 6601 | let mut dst: ?il::Reg = nil; |
|
| 6556 | 6602 | if retTy != resolver::Type::Void { |
|
| 6557 | 6603 | dst = nextReg(self); |
|
| 6558 | 6604 | } |
|
| 6559 | 6605 | emit(self, il::Instr::Call { |
|
| 6560 | 6606 | retTy: ilType(self.low, retTy), |
|
| 6561 | 6607 | dst, |
|
| 6562 | - | func: il::Val::Reg(fnPtrReg), |
|
| 6608 | + | func: callee, |
|
| 6563 | 6609 | args, |
|
| 6564 | 6610 | }); |
|
| 6565 | 6611 | ||
| 6566 | 6612 | if let d = dst { |
|
| 6567 | 6613 | if isSmallAggregate(retTy) { |
|
| 6568 | - | let slot = emitReserveLayout( |
|
| 6569 | - | self, |
|
| 6570 | - | resolver::Layout { |
|
| 6571 | - | size: resolver::PTR_SIZE, |
|
| 6572 | - | alignment: resolver::PTR_SIZE |
|
| 6573 | - | }); |
|
| 6574 | - | ||
| 6614 | + | let slot = emitReserveLayout(self, resolver::Layout { |
|
| 6615 | + | size: resolver::PTR_SIZE, |
|
| 6616 | + | alignment: resolver::PTR_SIZE, |
|
| 6617 | + | }); |
|
| 6575 | 6618 | emit(self, il::Instr::Store { |
|
| 6576 | 6619 | typ: il::Type::W64, |
|
| 6577 | 6620 | src: il::Val::Reg(d), |
|
| 6578 | 6621 | dst: slot, |
|
| 6579 | 6622 | offset: 0, |
| 6583 | 6626 | return il::Val::Reg(d); |
|
| 6584 | 6627 | } |
|
| 6585 | 6628 | return il::Val::Undef; |
|
| 6586 | 6629 | } |
|
| 6587 | 6630 | ||
| 6631 | + | /// Lower a method receiver expression to a pointer value. |
|
| 6632 | + | /// |
|
| 6633 | + | /// If the parent is already a pointer type, the value is used directly. |
|
| 6634 | + | /// If the parent is a value type (eg. a local record), its address is taken. |
|
| 6635 | + | fn lowerReceiver(self: *mut FnLowerer, parent: *ast::Node, parentTy: resolver::Type) -> il::Val |
|
| 6636 | + | throws (LowerError) |
|
| 6637 | + | { |
|
| 6638 | + | if let case resolver::Type::Pointer { .. } = parentTy { |
|
| 6639 | + | // Already a pointer: lower and use directly. |
|
| 6640 | + | return try lowerExpr(self, parent); |
|
| 6641 | + | } |
|
| 6642 | + | // Value type: take its address by lowering it and returning the slot pointer. |
|
| 6643 | + | // Aggregate types are already lowered as pointers to stack slots. |
|
| 6644 | + | let val = try lowerExpr(self, parent); |
|
| 6645 | + | if isAggregateType(parentTy) { |
|
| 6646 | + | return val; |
|
| 6647 | + | } |
|
| 6648 | + | // Scalar value: store to a stack slot and return the slot pointer. |
|
| 6649 | + | let layout = resolver::getLayout(self.low.resolver, parent, parentTy); |
|
| 6650 | + | let slot = emitReserveLayout(self, layout); |
|
| 6651 | + | try emitStore(self, slot, 0, parentTy, val); |
|
| 6652 | + | ||
| 6653 | + | return il::Val::Reg(slot); |
|
| 6654 | + | } |
|
| 6655 | + | ||
| 6656 | + | /// Lower a standalone method call via direct dispatch. |
|
| 6657 | + | /// |
|
| 6658 | + | /// Given `obj.method(args)` where `method` is a standalone method on a concrete type, |
|
| 6659 | + | /// emits a direct call with the receiver address as the first argument: |
|
| 6660 | + | /// |
|
| 6661 | + | /// call <retTy> %ret @Type::method(&obj, args...) |
|
| 6662 | + | /// |
|
| 6663 | + | fn lowerMethodCall( |
|
| 6664 | + | self: *mut FnLowerer, |
|
| 6665 | + | node: *ast::Node, |
|
| 6666 | + | call: ast::Call, |
|
| 6667 | + | method: *resolver::MethodEntry, |
|
| 6668 | + | ) -> il::Val throws (LowerError) { |
|
| 6669 | + | let case ast::NodeValue::FieldAccess(access) = call.callee.value |
|
| 6670 | + | else throw LowerError::MissingMetadata; |
|
| 6671 | + | ||
| 6672 | + | // Get the receiver as a pointer. |
|
| 6673 | + | let parentTy = resolver::typeFor(self.low.resolver, access.parent) |
|
| 6674 | + | else throw LowerError::MissingType(access.parent); |
|
| 6675 | + | let receiverVal = try lowerReceiver(self, access.parent, parentTy); |
|
| 6676 | + | ||
| 6677 | + | let qualName = instanceMethodName(self.low, nil, method.concreteTypeName, method.name); |
|
| 6678 | + | let case resolver::SymbolData::Value { type: resolver::Type::Fn(fnInfo), .. } = method.symbol.data |
|
| 6679 | + | else panic "lowerMethodCall: expected Fn type on method symbol"; |
|
| 6680 | + | ||
| 6681 | + | // Build args: optional return param slot + receiver + user args. |
|
| 6682 | + | let argOffset: u32 = 1 if requiresReturnParam(fnInfo) else 0; |
|
| 6683 | + | let args = try allocVals(self, call.args.len + 1 + argOffset); |
|
| 6684 | + | args[argOffset] = receiverVal; |
|
| 6685 | + | for arg, i in call.args { |
|
| 6686 | + | args[i + 1 + argOffset] = try lowerExpr(self, arg); |
|
| 6687 | + | } |
|
| 6688 | + | return try emitCallValue(self, il::Val::FnAddr(qualName), fnInfo, args); |
|
| 6689 | + | } |
|
| 6690 | + | ||
| 6588 | 6691 | /// Check if a call is to a compiler intrinsic and lower it directly. |
|
| 6589 | 6692 | fn lowerIntrinsicCall(self: *mut FnLowerer, call: ast::Call) -> ?il::Val throws (LowerError) { |
|
| 6590 | 6693 | // Get the callee symbol and check if it's marked as an intrinsic. |
|
| 6591 | 6694 | let sym = resolver::nodeData(self.low.resolver, call.callee).sym else { |
|
| 6592 | 6695 | // Expressions or function pointers may not have an associated symbol. |
| 6665 | 6768 | throw LowerError::MissingType(call.callee); |
|
| 6666 | 6769 | }; |
|
| 6667 | 6770 | let case resolver::Type::Fn(fnInfo) = calleeTy else { |
|
| 6668 | 6771 | throw LowerError::ExpectedFunction; |
|
| 6669 | 6772 | }; |
|
| 6670 | - | let retTy = resolver::typeFor(self.low.resolver, node) else { |
|
| 6671 | - | throw LowerError::MissingType(node); |
|
| 6672 | - | }; |
|
| 6673 | - | let returnParam = requiresReturnParam(fnInfo); |
|
| 6674 | - | ||
| 6675 | - | // Lower function value and arguments, reserving an extra slot for the |
|
| 6676 | - | // hidden return buffer when needed. |
|
| 6677 | 6773 | let callee = try lowerCallee(self, call.callee); |
|
| 6678 | - | let offset: u32 = 1 if returnParam else 0; |
|
| 6774 | + | let offset: u32 = 1 if requiresReturnParam(fnInfo) else 0; |
|
| 6679 | 6775 | let args = try allocVals(self, call.args.len + offset); |
|
| 6680 | 6776 | for arg, i in call.args { |
|
| 6681 | 6777 | args[i + offset] = try lowerExpr(self, arg); |
|
| 6682 | 6778 | } |
|
| 6683 | 6779 | ||
| 6684 | - | // Allocate the return buffer when needed. |
|
| 6685 | - | if returnParam { |
|
| 6686 | - | if fnInfo.throwList.len > 0 { |
|
| 6687 | - | let successType = *fnInfo.returnType; |
|
| 6688 | - | let layout = resolver::getResultLayout(successType, fnInfo.throwList); |
|
| 6689 | - | ||
| 6690 | - | args[0] = il::Val::Reg(emitReserveLayout(self, layout)); |
|
| 6691 | - | } else { |
|
| 6692 | - | args[0] = il::Val::Reg(try emitReserve(self, retTy)); |
|
| 6693 | - | } |
|
| 6694 | - | let dst = nextReg(self); |
|
| 6695 | - | emit(self, il::Instr::Call { |
|
| 6696 | - | retTy: il::Type::W64, |
|
| 6697 | - | dst, |
|
| 6698 | - | func: callee, |
|
| 6699 | - | args, |
|
| 6700 | - | }); |
|
| 6701 | - | return il::Val::Reg(dst); |
|
| 6702 | - | } |
|
| 6703 | - | ||
| 6704 | - | // Scalar call: allocate destination register for non-void return types. |
|
| 6705 | - | let mut dst: ?il::Reg = nil; |
|
| 6706 | - | if retTy != resolver::Type::Void { |
|
| 6707 | - | dst = nextReg(self); |
|
| 6708 | - | } |
|
| 6709 | - | emit(self, il::Instr::Call { |
|
| 6710 | - | retTy: ilType(self.low, retTy), |
|
| 6711 | - | dst, |
|
| 6712 | - | func: callee, |
|
| 6713 | - | args, |
|
| 6714 | - | }); |
|
| 6715 | - | ||
| 6716 | - | // Non-void functions produce a value in a register, while void functions |
|
| 6717 | - | // return an undefined value that shouldn't be used. |
|
| 6718 | - | if let d = dst { |
|
| 6719 | - | if isSmallAggregate(retTy) { |
|
| 6720 | - | let slot = emitReserveLayout( |
|
| 6721 | - | self, |
|
| 6722 | - | resolver::Layout { |
|
| 6723 | - | size: resolver::PTR_SIZE, |
|
| 6724 | - | alignment: resolver::PTR_SIZE |
|
| 6725 | - | }); |
|
| 6726 | - | emit(self, il::Instr::Store { |
|
| 6727 | - | typ: il::Type::W64, |
|
| 6728 | - | src: il::Val::Reg(d), |
|
| 6729 | - | dst: slot, |
|
| 6730 | - | offset: 0, |
|
| 6731 | - | }); |
|
| 6732 | - | return il::Val::Reg(slot); |
|
| 6733 | - | } |
|
| 6734 | - | return il::Val::Reg(d); |
|
| 6735 | - | } |
|
| 6736 | - | return il::Val::Undef; |
|
| 6780 | + | return try emitCallValue(self, callee, fnInfo, args); |
|
| 6737 | 6781 | } |
|
| 6738 | 6782 | ||
| 6739 | 6783 | /// Apply coercions requested by the resolver. |
|
| 6740 | 6784 | fn applyCoercion(self: *mut FnLowerer, node: *ast::Node, val: il::Val) -> il::Val throws (LowerError) { |
|
| 6741 | 6785 | let coerce = resolver::coercionFor(self.low.resolver, node) else { |
lib/std/lang/parser.rad
+16 -6
| 683 | 683 | case ast::NodeValue::FnDecl(_) => return false, |
|
| 684 | 684 | case ast::NodeValue::RecordDecl(_) => return false, |
|
| 685 | 685 | case ast::NodeValue::UnionDecl(_) => return false, |
|
| 686 | 686 | case ast::NodeValue::TraitDecl { .. } => return false, |
|
| 687 | 687 | case ast::NodeValue::InstanceDecl { .. } => return false, |
|
| 688 | + | case ast::NodeValue::MethodDecl { .. } => return false, |
|
| 688 | 689 | else => return true, |
|
| 689 | 690 | } |
|
| 690 | 691 | } |
|
| 691 | 692 | ||
| 692 | 693 | /// Parse a primary leaf expression without postfix operators. |
| 1965 | 1966 | /// Parse a function declaration. |
|
| 1966 | 1967 | fn parseFnDecl(p: *mut Parser, attrs: ?ast::Attributes) -> *ast::Node |
|
| 1967 | 1968 | throws (ParseError) |
|
| 1968 | 1969 | { |
|
| 1969 | 1970 | try expect(p, scanner::TokenKind::Fn, "expected `fn`"); |
|
| 1971 | + | ||
| 1972 | + | // Method syntax: `fn (recv: *Type) name(params) { body }`. |
|
| 1973 | + | if check(p, scanner::TokenKind::LParen) { |
|
| 1974 | + | return try parseMethodDecl(p, attrs); |
|
| 1975 | + | } |
|
| 1970 | 1976 | let name = try parseIdent(p, "expected function name"); |
|
| 1971 | 1977 | let sig = try parseFnTypeSig(p); |
|
| 1972 | 1978 | let mut body: ?*ast::Node = nil; |
|
| 1973 | 1979 | ||
| 1974 | 1980 | if let a = attrs; ast::attributesContains(&a, ast::Attribute::Extern) { |
| 2413 | 2419 | ||
| 2414 | 2420 | let mut methods = ast::nodeSlice(p.arena, ast::MAX_TRAIT_METHODS); |
|
| 2415 | 2421 | while not check(p, scanner::TokenKind::RBrace) and |
|
| 2416 | 2422 | not check(p, scanner::TokenKind::Eof) |
|
| 2417 | 2423 | { |
|
| 2418 | - | let method = try parseInstanceMethodDecl(p); |
|
| 2424 | + | try expect(p, scanner::TokenKind::Fn, "expected `fn`"); |
|
| 2425 | + | let method = try parseMethodDecl(p, nil); |
|
| 2426 | + | ||
| 2419 | 2427 | methods.append(method, p.allocator); |
|
| 2420 | 2428 | } |
|
| 2421 | 2429 | try expect(p, scanner::TokenKind::RBrace, "expected `}` after instance methods"); |
|
| 2422 | 2430 | ||
| 2423 | 2431 | return node(p, ast::NodeValue::InstanceDecl { traitName, targetType, methods }); |
|
| 2424 | 2432 | } |
|
| 2425 | 2433 | ||
| 2426 | - | /// Parse an instance method declaration. |
|
| 2434 | + | /// Parse a method declaration with a receiver. |
|
| 2427 | 2435 | /// Syntax: `fn (t: *mut Type) fnord(<params>) -> ReturnType { body }` |
|
| 2428 | - | fn parseInstanceMethodDecl(p: *mut Parser) -> *ast::Node |
|
| 2436 | + | /// |
|
| 2437 | + | /// Used both inside `instance` blocks and as standalone methods at the top level. |
|
| 2438 | + | /// Expects the `fn` token to have already been consumed. |
|
| 2439 | + | fn parseMethodDecl(p: *mut Parser, attrs: ?ast::Attributes) -> *ast::Node |
|
| 2429 | 2440 | throws (ParseError) |
|
| 2430 | 2441 | { |
|
| 2431 | - | try expect(p, scanner::TokenKind::Fn, "expected `fn`"); |
|
| 2432 | 2442 | try expect(p, scanner::TokenKind::LParen, "expected `(` before receiver"); |
|
| 2433 | 2443 | ||
| 2434 | 2444 | let receiverName = try parseIdent(p, "expected receiver name"); |
|
| 2435 | 2445 | try expect(p, scanner::TokenKind::Colon, "expected `:` after receiver name"); |
|
| 2436 | 2446 | let receiverType = try parseType(p); |
| 2439 | 2449 | ||
| 2440 | 2450 | let name = try parseIdent(p, "expected method name"); |
|
| 2441 | 2451 | let sig = try parseFnTypeSig(p); |
|
| 2442 | 2452 | let body = try parseBlock(p); |
|
| 2443 | 2453 | ||
| 2444 | - | return node(p, ast::NodeValue::InstanceMethodDecl { |
|
| 2445 | - | name, receiverName, receiverType, sig, body, |
|
| 2454 | + | return node(p, ast::NodeValue::MethodDecl { |
|
| 2455 | + | name, receiverName, receiverType, sig, body, attrs, |
|
| 2446 | 2456 | }); |
|
| 2447 | 2457 | } |
|
| 2448 | 2458 | ||
| 2449 | 2459 | /// Parse a comma-separated list enclosed by the given delimiters. |
|
| 2450 | 2460 | fn parseList( |
lib/std/lang/resolver.rad
+261 -36
| 42 | 42 | pub const MAX_UNION_VARIANTS: u32 = 128; |
|
| 43 | 43 | /// Maximum nesting of loops. |
|
| 44 | 44 | pub const MAX_LOOP_DEPTH: u32 = 16; |
|
| 45 | 45 | /// Maximum trait instances. |
|
| 46 | 46 | pub const MAX_INSTANCES: u32 = 128; |
|
| 47 | + | /// Maximum standalone methods (across all types). |
|
| 48 | + | pub const MAX_METHODS: u32 = 256; |
|
| 47 | 49 | ||
| 48 | 50 | /// Trait definition stored in the resolver. |
|
| 49 | 51 | pub record TraitType { |
|
| 50 | 52 | /// Trait name. |
|
| 51 | 53 | name: *[u8], |
| 79 | 81 | moduleId: u16, |
|
| 80 | 82 | /// Method symbols for each trait method, in declaration order. |
|
| 81 | 83 | methods: *mut [*mut Symbol], |
|
| 82 | 84 | } |
|
| 83 | 85 | ||
| 86 | + | /// An entry in the method registry. |
|
| 87 | + | pub record MethodEntry { |
|
| 88 | + | /// Concrete type that owns the method. |
|
| 89 | + | concreteType: Type, |
|
| 90 | + | /// Name of the concrete type. |
|
| 91 | + | concreteTypeName: *[u8], |
|
| 92 | + | /// Method name. |
|
| 93 | + | name: *[u8], |
|
| 94 | + | /// Function type excluding the receiver. |
|
| 95 | + | fnType: *FnType, |
|
| 96 | + | /// Whether the receiver is mutable. |
|
| 97 | + | mutable: bool, |
|
| 98 | + | /// Symbol for the method. |
|
| 99 | + | symbol: *mut Symbol, |
|
| 100 | + | } |
|
| 101 | + | ||
| 84 | 102 | /// Identifier for the synthetic `len` field. |
|
| 85 | 103 | pub const LEN_FIELD: *[u8] = "len"; |
|
| 86 | 104 | /// Identifier for the synthetic `ptr` field. |
|
| 87 | 105 | pub const PTR_FIELD: *[u8] = "ptr"; |
|
| 88 | 106 | /// Identifier for the synthetic `cap` field. |
| 618 | 636 | /// Trait definition. |
|
| 619 | 637 | traitInfo: *TraitType, |
|
| 620 | 638 | /// Method index in the v-table. |
|
| 621 | 639 | methodIndex: u32, |
|
| 622 | 640 | }, |
|
| 641 | + | /// Standalone method call metadata. |
|
| 642 | + | MethodCall { method: *MethodEntry }, |
|
| 623 | 643 | /// Slice `.append(val, allocator)` method call. |
|
| 624 | 644 | SliceAppend { elemType: *Type }, |
|
| 625 | 645 | /// Slice `.delete(index)` method call. |
|
| 626 | 646 | SliceDelete { elemType: *Type }, |
|
| 627 | 647 | } |
| 755 | 775 | moduleScopes: [?*mut Scope; module::MAX_MODULES], |
|
| 756 | 776 | /// Trait instance registry. |
|
| 757 | 777 | instances: [InstanceEntry; MAX_INSTANCES], |
|
| 758 | 778 | /// Number of registered instances. |
|
| 759 | 779 | instancesLen: u32, |
|
| 780 | + | /// Standalone method registry. |
|
| 781 | + | methods: [MethodEntry; MAX_METHODS], |
|
| 782 | + | /// Number of registered standalone methods. |
|
| 783 | + | methodsLen: u32, |
|
| 760 | 784 | } |
|
| 761 | 785 | ||
| 762 | 786 | /// Internal error sentinel thrown when analysis cannot proceed. |
|
| 763 | 787 | pub union ResolveError { |
|
| 764 | 788 | Failure, |
| 900 | 924 | // TODO: Shouldn't be undefined. |
|
| 901 | 925 | moduleGraph: undefined, |
|
| 902 | 926 | moduleScopes, |
|
| 903 | 927 | instances: undefined, |
|
| 904 | 928 | instancesLen: 0, |
|
| 929 | + | methods: undefined, |
|
| 930 | + | methodsLen: 0, |
|
| 905 | 931 | }; |
|
| 906 | 932 | } |
|
| 907 | 933 | ||
| 908 | 934 | /// Return `true` if there are no errors in the diagnostics. |
|
| 909 | 935 | pub fn success(diag: *Diagnostics) -> bool { |
| 2452 | 2478 | // Handled in previous pass. |
|
| 2453 | 2479 | } |
|
| 2454 | 2480 | case ast::NodeValue::InstanceDecl { traitName, targetType, methods } => { |
|
| 2455 | 2481 | try resolveInstanceDecl(self, node, traitName, targetType, methods); |
|
| 2456 | 2482 | } |
|
| 2483 | + | case ast::NodeValue::MethodDecl { name, receiverName, receiverType, sig, body, attrs } => { |
|
| 2484 | + | try resolveMethodDecl(self, node, name, receiverName, receiverType, sig, attrs); |
|
| 2485 | + | } |
|
| 2457 | 2486 | else => { |
|
| 2458 | 2487 | // Ignore non-declaration nodes. |
|
| 2459 | 2488 | } |
|
| 2460 | 2489 | } |
|
| 2461 | 2490 | } |
| 2492 | 2521 | // Skip: already analyzed in declaration phase. |
|
| 2493 | 2522 | } |
|
| 2494 | 2523 | case ast::NodeValue::InstanceDecl { methods, .. } => { |
|
| 2495 | 2524 | try resolveInstanceMethodBodies(self, methods); |
|
| 2496 | 2525 | } |
|
| 2526 | + | case ast::NodeValue::MethodDecl { receiverName, sig, body, .. } => { |
|
| 2527 | + | try resolveMethodBody(self, node, receiverName, sig, body); |
|
| 2528 | + | } |
|
| 2497 | 2529 | else => { |
|
| 2498 | 2530 | // FIXME: This allows module-level statements that should |
|
| 2499 | 2531 | // normally only be valid inside function bodies. We currently |
|
| 2500 | 2532 | // need this because of how tests are written, but it should |
|
| 2501 | 2533 | // be eventually removed. |
| 3442 | 3474 | // Track which trait methods are covered by the instance. |
|
| 3443 | 3475 | let mut covered: [bool; ast::MAX_TRAIT_METHODS] = [false; ast::MAX_TRAIT_METHODS]; |
|
| 3444 | 3476 | ||
| 3445 | 3477 | // Match each instance method to a trait method. |
|
| 3446 | 3478 | for methodNode in methods { |
|
| 3447 | - | let case ast::NodeValue::InstanceMethodDecl { |
|
| 3448 | - | name, receiverName, receiverType, sig, body |
|
| 3479 | + | let case ast::NodeValue::MethodDecl { |
|
| 3480 | + | name, receiverName, receiverType, sig, body, .. |
|
| 3449 | 3481 | } = methodNode.value else continue; |
|
| 3450 | 3482 | ||
| 3451 | 3483 | let methodName = try nodeName(self, name); |
|
| 3452 | 3484 | ||
| 3453 | 3485 | // Find the matching trait method. |
| 3594 | 3626 | /// Resolve instance method bodies. |
|
| 3595 | 3627 | fn resolveInstanceMethodBodies(self: *mut Resolver, methods: *mut [*ast::Node]) |
|
| 3596 | 3628 | throws (ResolveError) |
|
| 3597 | 3629 | { |
|
| 3598 | 3630 | for methodNode in methods { |
|
| 3599 | - | let case ast::NodeValue::InstanceMethodDecl { |
|
| 3600 | - | name, receiverName, receiverType, sig, body |
|
| 3631 | + | let case ast::NodeValue::MethodDecl { |
|
| 3632 | + | name, receiverName, receiverType, sig, body, .. |
|
| 3601 | 3633 | } = methodNode.value else continue; |
|
| 3602 | 3634 | ||
| 3603 | 3635 | // Symbol may be absent if [`resolveInstanceDecl`] reported an error |
|
| 3604 | 3636 | // for this method (eg. unknown method name). Skip gracefully. |
|
| 3605 | 3637 | let sym = symbolFor(self, methodNode) |
|
| 3606 | 3638 | else continue; |
|
| 3607 | - | let case SymbolData::Value { type: Type::Fn(fnType), .. } = sym.data |
|
| 3608 | - | else panic "resolveInstanceMethodBodies: expected value symbol"; |
|
| 3609 | 3639 | ||
| 3610 | - | // Enter function scope. |
|
| 3611 | - | enterFn(self, methodNode, fnType); |
|
| 3640 | + | try resolveMethodBody(self, methodNode, receiverName, sig, body); |
|
| 3641 | + | } |
|
| 3642 | + | } |
|
| 3643 | + | ||
| 3644 | + | /// Resolve a method body shared by instance methods and standalone methods. |
|
| 3645 | + | /// Binds the receiver and parameters, then type-checks the body. |
|
| 3646 | + | fn resolveMethodBody( |
|
| 3647 | + | self: *mut Resolver, |
|
| 3648 | + | node: *ast::Node, |
|
| 3649 | + | receiverName: *ast::Node, |
|
| 3650 | + | sig: ast::FnSig, |
|
| 3651 | + | body: *ast::Node, |
|
| 3652 | + | ) throws (ResolveError) { |
|
| 3653 | + | let sym = symbolFor(self, node) |
|
| 3654 | + | else throw emitError(self, node, ErrorKind::Internal); |
|
| 3655 | + | let case SymbolData::Value { type: Type::Fn(fnType), .. } = sym.data |
|
| 3656 | + | else panic "resolveMethodBody: expected value symbol"; |
|
| 3612 | 3657 | ||
| 3613 | - | // Bind the receiver parameter. |
|
| 3614 | - | let receiverTy = *fnType.paramTypes[0]; |
|
| 3615 | - | try bindValueIdent(self, receiverName, receiverName, receiverTy, false, 0, 0) catch { |
|
| 3616 | - | exitFn(self); |
|
| 3617 | - | throw ResolveError::Failure; |
|
| 3618 | - | }; |
|
| 3619 | - | // Bind the remaining parameters from the signature. |
|
| 3620 | - | for paramNode in sig.params { |
|
| 3621 | - | let paramTy = try infer(self, paramNode) catch { |
|
| 3622 | - | exitFn(self); |
|
| 3623 | - | throw ResolveError::Failure; |
|
| 3624 | - | }; |
|
| 3625 | - | } |
|
| 3658 | + | // Enter function scope. |
|
| 3659 | + | enterFn(self, node, fnType); |
|
| 3626 | 3660 | ||
| 3627 | - | // Resolve the body. |
|
| 3628 | - | let retTy = *fnType.returnType; |
|
| 3629 | - | let bodyTy = try checkAssignable(self, body, Type::Void) catch { |
|
| 3661 | + | // Bind the receiver parameter. |
|
| 3662 | + | let receiverTy = *fnType.paramTypes[0]; |
|
| 3663 | + | try bindValueIdent(self, receiverName, receiverName, receiverTy, false, 0, 0) catch { |
|
| 3664 | + | exitFn(self); |
|
| 3665 | + | throw ResolveError::Failure; |
|
| 3666 | + | }; |
|
| 3667 | + | // Bind the remaining parameters from the signature. |
|
| 3668 | + | for paramNode in sig.params { |
|
| 3669 | + | let paramTy = try infer(self, paramNode) catch { |
|
| 3630 | 3670 | exitFn(self); |
|
| 3631 | 3671 | throw ResolveError::Failure; |
|
| 3632 | 3672 | }; |
|
| 3633 | - | if retTy != Type::Void and bodyTy != Type::Never { |
|
| 3634 | - | exitFn(self); |
|
| 3635 | - | throw emitError(self, body, ErrorKind::FnMissingReturn); |
|
| 3636 | - | } |
|
| 3673 | + | } |
|
| 3674 | + | ||
| 3675 | + | // Resolve the body. |
|
| 3676 | + | let retTy = *fnType.returnType; |
|
| 3677 | + | let bodyTy = try checkAssignable(self, body, Type::Void) catch { |
|
| 3678 | + | exitFn(self); |
|
| 3679 | + | throw ResolveError::Failure; |
|
| 3680 | + | }; |
|
| 3681 | + | if retTy != Type::Void and bodyTy != Type::Never { |
|
| 3637 | 3682 | exitFn(self); |
|
| 3683 | + | throw emitError(self, body, ErrorKind::FnMissingReturn); |
|
| 3638 | 3684 | } |
|
| 3685 | + | exitFn(self); |
|
| 3686 | + | } |
|
| 3687 | + | ||
| 3688 | + | /// Resolve a standalone method declaration (signature only). |
|
| 3689 | + | /// Validates the receiver type and registers the method in the method table. |
|
| 3690 | + | /// Extract the type name from a resolved receiver type node (`*T` or `*mut T`). |
|
| 3691 | + | fn receiverTypeName(self: *mut Resolver, receiverType: *ast::Node) -> *[u8] throws (ResolveError) { |
|
| 3692 | + | let case ast::NodeValue::TypeSig(ast::TypeSig::Pointer { valueType, .. }) = receiverType.value |
|
| 3693 | + | else throw emitError(self, receiverType, ErrorKind::TraitReceiverMismatch); |
|
| 3694 | + | let case ast::NodeValue::TypeSig(ast::TypeSig::Nominal(nameNode)) = valueType.value |
|
| 3695 | + | else throw emitError(self, receiverType, ErrorKind::Internal); |
|
| 3696 | + | let sym = symbolFor(self, nameNode) |
|
| 3697 | + | else throw emitError(self, receiverType, ErrorKind::Internal); |
|
| 3698 | + | ||
| 3699 | + | return sym.name; |
|
| 3700 | + | } |
|
| 3701 | + | ||
| 3702 | + | fn resolveMethodDecl( |
|
| 3703 | + | self: *mut Resolver, |
|
| 3704 | + | node: *ast::Node, |
|
| 3705 | + | name: *ast::Node, |
|
| 3706 | + | receiverName: *ast::Node, |
|
| 3707 | + | receiverType: *ast::Node, |
|
| 3708 | + | sig: ast::FnSig, |
|
| 3709 | + | attrs: ?ast::Attributes, |
|
| 3710 | + | ) throws (ResolveError) { |
|
| 3711 | + | // Resolve the receiver type: must be `*Type` or `*mut Type` pointing to a |
|
| 3712 | + | // nominal type. |
|
| 3713 | + | let fullReceiverTy = try infer(self, receiverType); |
|
| 3714 | + | let case Type::Pointer { target, mutable: receiverMut } = fullReceiverTy |
|
| 3715 | + | else throw emitError(self, receiverType, ErrorKind::TraitReceiverMismatch); |
|
| 3716 | + | let concreteType = *target; |
|
| 3717 | + | let case Type::Nominal(nominalTy) = concreteType |
|
| 3718 | + | else throw emitError(self, receiverType, ErrorKind::ExpectedRecord); |
|
| 3719 | + | try ensureNominalResolved(self, nominalTy, receiverType); |
|
| 3720 | + | ||
| 3721 | + | // Get the type name from the inner type node's symbol. |
|
| 3722 | + | let typeName = try receiverTypeName(self, receiverType); |
|
| 3723 | + | let methodName = try nodeName(self, name); |
|
| 3724 | + | ||
| 3725 | + | // Reject duplicate method for the same (type, name). |
|
| 3726 | + | if let _ = findMethod(self, concreteType, methodName) { |
|
| 3727 | + | throw emitError(self, name, ErrorKind::DuplicateBinding(methodName)); |
|
| 3728 | + | } |
|
| 3729 | + | ||
| 3730 | + | // Resolve parameter types. |
|
| 3731 | + | let a = alloc::arenaAllocator(&mut self.arena); |
|
| 3732 | + | let mut paramTypes: *mut [*Type] = &mut []; |
|
| 3733 | + | ||
| 3734 | + | // Receiver is the first parameter. |
|
| 3735 | + | let receiverPtrType = Type::Pointer { |
|
| 3736 | + | target: allocType(self, concreteType), |
|
| 3737 | + | mutable: receiverMut, |
|
| 3738 | + | }; |
|
| 3739 | + | paramTypes.append(allocType(self, receiverPtrType), a); |
|
| 3740 | + | ||
| 3741 | + | for paramNode in sig.params { |
|
| 3742 | + | let case ast::NodeValue::FnParam(param) = paramNode.value |
|
| 3743 | + | else throw emitError(self, paramNode, ErrorKind::ExpectedIdentifier); |
|
| 3744 | + | let paramTy = try resolveValueType(self, param.type); |
|
| 3745 | + | paramTypes.append(allocType(self, paramTy), a); |
|
| 3746 | + | } |
|
| 3747 | + | ||
| 3748 | + | // Resolve return type. |
|
| 3749 | + | let mut returnType = Type::Void; |
|
| 3750 | + | if let retNode = sig.returnType { |
|
| 3751 | + | returnType = try resolveValueType(self, retNode); |
|
| 3752 | + | } |
|
| 3753 | + | ||
| 3754 | + | // Resolve throw list. |
|
| 3755 | + | let mut throwTypes: *mut [*Type] = &mut []; |
|
| 3756 | + | for throwNode in sig.throwList { |
|
| 3757 | + | let throwTy = try resolveValueType(self, throwNode); |
|
| 3758 | + | throwTypes.append(allocType(self, throwTy), a); |
|
| 3759 | + | } |
|
| 3760 | + | ||
| 3761 | + | let retTypePtr = allocType(self, returnType); |
|
| 3762 | + | let throwList = &throwTypes[..]; |
|
| 3763 | + | ||
| 3764 | + | // Full function type (receiver + params) for lowering. |
|
| 3765 | + | let fullFnType = FnType { |
|
| 3766 | + | paramTypes: ¶mTypes[..], returnType: retTypePtr, throwList, localCount: 0, |
|
| 3767 | + | }; |
|
| 3768 | + | let fnTy = Type::Fn(allocFnType(self, fullFnType)); |
|
| 3769 | + | ||
| 3770 | + | // Function type excluding receiver, for call arg checking. |
|
| 3771 | + | let checkFnType = FnType { |
|
| 3772 | + | paramTypes: ¶mTypes[1..], returnType: retTypePtr, throwList, localCount: 0, |
|
| 3773 | + | }; |
|
| 3774 | + | ||
| 3775 | + | // Compute attribute mask. |
|
| 3776 | + | let mut attrMask: u32 = 0; |
|
| 3777 | + | if let a = attrs { |
|
| 3778 | + | for attrNode in a.list { |
|
| 3779 | + | if let case ast::NodeValue::Attribute(attr) = attrNode.value { |
|
| 3780 | + | attrMask = attrMask | (attr as u32); |
|
| 3781 | + | } |
|
| 3782 | + | } |
|
| 3783 | + | } |
|
| 3784 | + | ||
| 3785 | + | // Create a symbol for the method without binding it into the module scope. |
|
| 3786 | + | let sym = allocSymbol(self, SymbolData::Value { |
|
| 3787 | + | mutable: false, alignment: 0, type: fnTy, addressTaken: false, |
|
| 3788 | + | }, methodName, node, attrMask); |
|
| 3789 | + | ||
| 3790 | + | setNodeSymbol(self, node, sym); |
|
| 3791 | + | setNodeType(self, node, fnTy); |
|
| 3792 | + | setNodeType(self, name, fnTy); |
|
| 3793 | + | ||
| 3794 | + | // Register in the method table. |
|
| 3795 | + | if self.methodsLen >= MAX_METHODS { |
|
| 3796 | + | throw emitError(self, node, ErrorKind::Internal); |
|
| 3797 | + | } |
|
| 3798 | + | self.methods[self.methodsLen] = MethodEntry { |
|
| 3799 | + | concreteType, |
|
| 3800 | + | concreteTypeName: typeName, |
|
| 3801 | + | name: methodName, |
|
| 3802 | + | fnType: allocFnType(self, checkFnType), |
|
| 3803 | + | mutable: receiverMut, |
|
| 3804 | + | symbol: sym, |
|
| 3805 | + | }; |
|
| 3806 | + | self.methodsLen += 1; |
|
| 3639 | 3807 | } |
|
| 3640 | 3808 | ||
| 3641 | 3809 | /// Look up an instance entry by trait and concrete type. |
|
| 3642 | 3810 | fn findInstance(self: *Resolver, traitInfo: *TraitType, concreteType: Type) -> ?*InstanceEntry { |
|
| 3643 | 3811 | for i in 0..self.instancesLen { |
| 3647 | 3815 | } |
|
| 3648 | 3816 | } |
|
| 3649 | 3817 | return nil; |
|
| 3650 | 3818 | } |
|
| 3651 | 3819 | ||
| 3820 | + | /// Look up a standalone method by concrete type and name. |
|
| 3821 | + | pub fn findMethod(self: *Resolver, concreteType: Type, name: *[u8]) -> ?*MethodEntry { |
|
| 3822 | + | for i in 0..self.methodsLen { |
|
| 3823 | + | let entry = &self.methods[i]; |
|
| 3824 | + | if typesEqual(entry.concreteType, concreteType) and entry.name == name { |
|
| 3825 | + | return entry; |
|
| 3826 | + | } |
|
| 3827 | + | } |
|
| 3828 | + | return nil; |
|
| 3829 | + | } |
|
| 3830 | + | ||
| 3831 | + | /// Look up a standalone method entry by its symbol. |
|
| 3832 | + | pub fn findMethodBySymbol(self: *Resolver, sym: *mut Symbol) -> ?*MethodEntry { |
|
| 3833 | + | for i in 0..self.methodsLen { |
|
| 3834 | + | let entry = &self.methods[i]; |
|
| 3835 | + | if entry.symbol == sym { |
|
| 3836 | + | return entry; |
|
| 3837 | + | } |
|
| 3838 | + | } |
|
| 3839 | + | return nil; |
|
| 3840 | + | } |
|
| 3841 | + | ||
| 3652 | 3842 | /// Resolve union variant types after all type names are bound (Phase 2 of type resolution). |
|
| 3653 | 3843 | fn resolveUnionBody(self: *mut Resolver, node: *ast::Node, decl: ast::UnionDecl) |
|
| 3654 | 3844 | throws (ResolveError) |
|
| 3655 | 3845 | { |
|
| 3656 | 3846 | // Get the type symbol that was bound to this declaration node. |
| 4855 | 5045 | try checkCallArgs(self, node, call, method.fnType, ctx); |
|
| 4856 | 5046 | setTraitMethodCall(self, node, traitInfo, method.index); |
|
| 4857 | 5047 | ||
| 4858 | 5048 | return setNodeType(self, node, *method.fnType.returnType); |
|
| 4859 | 5049 | } |
|
| 5050 | + | ||
| 5051 | + | // Check for a standalone method call on a concrete type. |
|
| 5052 | + | if let case Type::Nominal(_) = subjectTy { |
|
| 5053 | + | let methodName = try nodeName(self, access.child); |
|
| 5054 | + | if let method = findMethod(self, subjectTy, methodName) { |
|
| 5055 | + | // Reject mutable-receiver methods on immutable bindings. |
|
| 5056 | + | // If the parent is already a mutable pointer, the receiver is fine. |
|
| 5057 | + | // Otherwise, check that the parent can yield a mutable borrow. |
|
| 5058 | + | if method.mutable { |
|
| 5059 | + | let mut isMutPtr = false; |
|
| 5060 | + | if let case Type::Pointer { mutable, .. } = parentTy { |
|
| 5061 | + | isMutPtr = mutable; |
|
| 5062 | + | } |
|
| 5063 | + | if not isMutPtr and not (try canBorrowMutFrom(self, access.parent)) { |
|
| 5064 | + | throw emitError(self, access.parent, ErrorKind::ImmutableBinding); |
|
| 5065 | + | } |
|
| 5066 | + | } |
|
| 5067 | + | // Check arguments (excluding receiver). |
|
| 5068 | + | try checkCallArgs(self, node, call, method.fnType, ctx); |
|
| 5069 | + | self.nodeData.entries[node.id].extra = NodeExtra::MethodCall { method }; |
|
| 5070 | + | ||
| 5071 | + | return setNodeType(self, node, *method.fnType.returnType); |
|
| 5072 | + | } |
|
| 5073 | + | } |
|
| 4860 | 5074 | } |
|
| 4861 | 5075 | let case Type::Fn(info) = calleeTy else { |
|
| 4862 | 5076 | // TODO: Emit type error. |
|
| 4863 | 5077 | panic; |
|
| 4864 | 5078 | }; |
| 5389 | 5603 | ||
| 5390 | 5604 | match subjectTy { |
|
| 5391 | 5605 | case Type::Nominal(NominalType::Record(recordType)) => { |
|
| 5392 | 5606 | let fieldNode = access.child; |
|
| 5393 | 5607 | let fieldName = try nodeName(self, fieldNode); |
|
| 5394 | - | let fieldIndex = findRecordField(&recordType, fieldName) |
|
| 5395 | - | else throw emitError(self, node, ErrorKind::RecordFieldUnknown(fieldName)); |
|
| 5396 | - | let fieldTy = recordType.fields[fieldIndex].fieldType; |
|
| 5397 | - | ||
| 5398 | - | setRecordFieldIndex(self, fieldNode, fieldIndex); |
|
| 5399 | - | ||
| 5400 | - | return setNodeType(self, node, fieldTy); |
|
| 5608 | + | if let fieldIndex = findRecordField(&recordType, fieldName) { |
|
| 5609 | + | let fieldTy = recordType.fields[fieldIndex].fieldType; |
|
| 5610 | + | setRecordFieldIndex(self, fieldNode, fieldIndex); |
|
| 5611 | + | return setNodeType(self, node, fieldTy); |
|
| 5612 | + | } |
|
| 5613 | + | // Not a field: check for a standalone method. |
|
| 5614 | + | if let method = findMethod(self, subjectTy, fieldName) { |
|
| 5615 | + | return setNodeType(self, node, Type::Fn(method.fnType)); |
|
| 5616 | + | } |
|
| 5617 | + | throw emitError(self, node, ErrorKind::RecordFieldUnknown(fieldName)); |
|
| 5401 | 5618 | } |
|
| 5402 | 5619 | case Type::Array(arrayInfo) => { |
|
| 5403 | 5620 | let fieldNode = access.child; |
|
| 5404 | 5621 | let fieldName = try nodeName(self, fieldNode); |
|
| 5405 | 5622 |
| 5438 | 5655 | let method = findTraitMethod(traitInfo, fieldName) |
|
| 5439 | 5656 | else throw emitError(self, node, ErrorKind::RecordFieldUnknown(fieldName)); |
|
| 5440 | 5657 | ||
| 5441 | 5658 | return setNodeType(self, node, Type::Fn(method.fnType)); |
|
| 5442 | 5659 | } |
|
| 5443 | - | else => {} |
|
| 5660 | + | else => { |
|
| 5661 | + | // Check for standalone methods on any nominal type (e.g. unions). |
|
| 5662 | + | if let case Type::Nominal(_) = subjectTy { |
|
| 5663 | + | let fieldName = try nodeName(self, access.child); |
|
| 5664 | + | if let method = findMethod(self, subjectTy, fieldName) { |
|
| 5665 | + | return setNodeType(self, node, Type::Fn(method.fnType)); |
|
| 5666 | + | } |
|
| 5667 | + | } |
|
| 5668 | + | } |
|
| 5444 | 5669 | } |
|
| 5445 | 5670 | // FIXME: We can't move this to the `else` branch due to a resolver bug. |
|
| 5446 | 5671 | throw emitError(self, access.parent, ErrorKind::ExpectedRecord); |
|
| 5447 | 5672 | } |
|
| 5448 | 5673 |
test/tests/method.basic.rad
added
+34 -0
| 1 | + | //! returns: 0 |
|
| 2 | + | //! Basic standalone method test. |
|
| 3 | + | ||
| 4 | + | record Point { |
|
| 5 | + | x: i32, |
|
| 6 | + | y: i32, |
|
| 7 | + | } |
|
| 8 | + | ||
| 9 | + | fn (p: *Point) sum() -> i32 { |
|
| 10 | + | return p.x + p.y; |
|
| 11 | + | } |
|
| 12 | + | ||
| 13 | + | fn (p: *mut Point) translate(dx: i32, dy: i32) { |
|
| 14 | + | p.x = p.x + dx; |
|
| 15 | + | p.y = p.y + dy; |
|
| 16 | + | } |
|
| 17 | + | ||
| 18 | + | @default fn main() -> i32 { |
|
| 19 | + | let mut pt = Point { x: 3, y: 4 }; |
|
| 20 | + | ||
| 21 | + | // Call immutable method. |
|
| 22 | + | assert pt.sum() == 7; |
|
| 23 | + | ||
| 24 | + | // Call mutable method. |
|
| 25 | + | pt.translate(10, 20); |
|
| 26 | + | assert pt.x == 13; |
|
| 27 | + | assert pt.y == 24; |
|
| 28 | + | ||
| 29 | + | // Call via pointer. |
|
| 30 | + | let ptr = &pt; |
|
| 31 | + | assert ptr.sum() == 37; |
|
| 32 | + | ||
| 33 | + | return 0; |
|
| 34 | + | } |
test/tests/method.chain.rad
added
+34 -0
| 1 | + | //! returns: 0 |
|
| 2 | + | //! Method chaining via mutable pointer returns. |
|
| 3 | + | ||
| 4 | + | record Builder { |
|
| 5 | + | x: i32, |
|
| 6 | + | y: i32, |
|
| 7 | + | } |
|
| 8 | + | ||
| 9 | + | fn (b: *mut Builder) setX(x: i32) -> *mut Builder { |
|
| 10 | + | b.x = x; |
|
| 11 | + | return b; |
|
| 12 | + | } |
|
| 13 | + | ||
| 14 | + | fn (b: *mut Builder) setY(y: i32) -> *mut Builder { |
|
| 15 | + | b.y = y; |
|
| 16 | + | return b; |
|
| 17 | + | } |
|
| 18 | + | ||
| 19 | + | fn (b: *Builder) sum() -> i32 { |
|
| 20 | + | return b.x + b.y; |
|
| 21 | + | } |
|
| 22 | + | ||
| 23 | + | @default fn main() -> i32 { |
|
| 24 | + | let mut b = Builder { x: 0, y: 0 }; |
|
| 25 | + | ||
| 26 | + | // Chain method calls. |
|
| 27 | + | let p = b.setX(10).setY(20); |
|
| 28 | + | assert p.sum() == 30; |
|
| 29 | + | ||
| 30 | + | // Original also modified. |
|
| 31 | + | assert b.sum() == 30; |
|
| 32 | + | ||
| 33 | + | return 0; |
|
| 34 | + | } |
test/tests/method.multiple.rad
added
+48 -0
| 1 | + | //! returns: 0 |
|
| 2 | + | //! Multiple methods on the same type. |
|
| 3 | + | ||
| 4 | + | record Vec2 { |
|
| 5 | + | x: i32, |
|
| 6 | + | y: i32, |
|
| 7 | + | } |
|
| 8 | + | ||
| 9 | + | fn (v: *Vec2) magnitudeSq() -> i32 { |
|
| 10 | + | return v.x * v.x + v.y * v.y; |
|
| 11 | + | } |
|
| 12 | + | ||
| 13 | + | fn (v: *Vec2) dot(other: *Vec2) -> i32 { |
|
| 14 | + | return v.x * other.x + v.y * other.y; |
|
| 15 | + | } |
|
| 16 | + | ||
| 17 | + | fn (v: *mut Vec2) add(other: *Vec2) { |
|
| 18 | + | v.x = v.x + other.x; |
|
| 19 | + | v.y = v.y + other.y; |
|
| 20 | + | } |
|
| 21 | + | ||
| 22 | + | fn (v: *mut Vec2) scale(factor: i32) { |
|
| 23 | + | v.x = v.x * factor; |
|
| 24 | + | v.y = v.y * factor; |
|
| 25 | + | } |
|
| 26 | + | ||
| 27 | + | @default fn main() -> i32 { |
|
| 28 | + | let mut a = Vec2 { x: 3, y: 4 }; |
|
| 29 | + | let b = Vec2 { x: 1, y: 2 }; |
|
| 30 | + | ||
| 31 | + | // Immutable method. |
|
| 32 | + | assert a.magnitudeSq() == 25; |
|
| 33 | + | ||
| 34 | + | // Method with pointer parameter. |
|
| 35 | + | assert a.dot(&b) == 11; |
|
| 36 | + | ||
| 37 | + | // Mutable method with pointer parameter. |
|
| 38 | + | a.add(&b); |
|
| 39 | + | assert a.x == 4; |
|
| 40 | + | assert a.y == 6; |
|
| 41 | + | ||
| 42 | + | // Mutable method with scalar parameter. |
|
| 43 | + | a.scale(2); |
|
| 44 | + | assert a.x == 8; |
|
| 45 | + | assert a.y == 12; |
|
| 46 | + | ||
| 47 | + | return 0; |
|
| 48 | + | } |
test/tests/method.ptr.rad
added
+39 -0
| 1 | + | //! returns: 0 |
|
| 2 | + | //! Methods called via various pointer indirections. |
|
| 3 | + | ||
| 4 | + | record Counter { |
|
| 5 | + | value: i32, |
|
| 6 | + | } |
|
| 7 | + | ||
| 8 | + | fn (c: *Counter) get() -> i32 { |
|
| 9 | + | return c.value; |
|
| 10 | + | } |
|
| 11 | + | ||
| 12 | + | fn (c: *mut Counter) inc() { |
|
| 13 | + | c.value = c.value + 1; |
|
| 14 | + | } |
|
| 15 | + | ||
| 16 | + | @default fn main() -> i32 { |
|
| 17 | + | let mut c = Counter { value: 0 }; |
|
| 18 | + | ||
| 19 | + | // Direct call on value. |
|
| 20 | + | assert c.get() == 0; |
|
| 21 | + | ||
| 22 | + | // Mutable method on value. |
|
| 23 | + | c.inc(); |
|
| 24 | + | assert c.get() == 1; |
|
| 25 | + | ||
| 26 | + | // Call via immutable pointer. |
|
| 27 | + | let p = &c; |
|
| 28 | + | assert p.get() == 1; |
|
| 29 | + | ||
| 30 | + | // Call via mutable pointer. |
|
| 31 | + | let mp = &mut c; |
|
| 32 | + | mp.inc(); |
|
| 33 | + | assert mp.get() == 2; |
|
| 34 | + | ||
| 35 | + | // Original value also updated. |
|
| 36 | + | assert c.get() == 2; |
|
| 37 | + | ||
| 38 | + | return 0; |
|
| 39 | + | } |
test/tests/method.pub.rad
added
+16 -0
| 1 | + | //! returns: 0 |
|
| 2 | + | //! Public attribute on standalone methods. |
|
| 3 | + | ||
| 4 | + | record Foo { |
|
| 5 | + | x: i32, |
|
| 6 | + | } |
|
| 7 | + | ||
| 8 | + | pub fn (f: *Foo) getX() -> i32 { |
|
| 9 | + | return f.x; |
|
| 10 | + | } |
|
| 11 | + | ||
| 12 | + | @default fn main() -> i32 { |
|
| 13 | + | let f = Foo { x: 42 }; |
|
| 14 | + | assert f.getX() == 42; |
|
| 15 | + | return 0; |
|
| 16 | + | } |
test/tests/method.return.rad
added
+37 -0
| 1 | + | //! returns: 0 |
|
| 2 | + | //! Methods returning various types and records. |
|
| 3 | + | ||
| 4 | + | record Pair { |
|
| 5 | + | a: i32, |
|
| 6 | + | b: i32, |
|
| 7 | + | } |
|
| 8 | + | ||
| 9 | + | fn (p: *Pair) sum() -> i32 { |
|
| 10 | + | return p.a + p.b; |
|
| 11 | + | } |
|
| 12 | + | ||
| 13 | + | fn (p: *Pair) swapped() -> Pair { |
|
| 14 | + | return Pair { a: p.b, b: p.a }; |
|
| 15 | + | } |
|
| 16 | + | ||
| 17 | + | fn (p: *Pair) firstPtr() -> *i32 { |
|
| 18 | + | return &p.a; |
|
| 19 | + | } |
|
| 20 | + | ||
| 21 | + | @default fn main() -> i32 { |
|
| 22 | + | let pair = Pair { a: 10, b: 20 }; |
|
| 23 | + | ||
| 24 | + | // Scalar return. |
|
| 25 | + | assert pair.sum() == 30; |
|
| 26 | + | ||
| 27 | + | // Aggregate return. |
|
| 28 | + | let s = pair.swapped(); |
|
| 29 | + | assert s.a == 20; |
|
| 30 | + | assert s.b == 10; |
|
| 31 | + | ||
| 32 | + | // Pointer return. |
|
| 33 | + | let ptr = pair.firstPtr(); |
|
| 34 | + | assert *ptr == 10; |
|
| 35 | + | ||
| 36 | + | return 0; |
|
| 37 | + | } |
test/tests/method.throws.rad
added
+47 -0
| 1 | + | //! returns: 0 |
|
| 2 | + | //! Methods that throw errors. |
|
| 3 | + | ||
| 4 | + | union ParseError { |
|
| 5 | + | Invalid, |
|
| 6 | + | } |
|
| 7 | + | ||
| 8 | + | record Parser { |
|
| 9 | + | pos: i32, |
|
| 10 | + | len: i32, |
|
| 11 | + | } |
|
| 12 | + | ||
| 13 | + | fn (p: *mut Parser) advance() throws (ParseError) { |
|
| 14 | + | if p.pos >= p.len { |
|
| 15 | + | throw ParseError::Invalid; |
|
| 16 | + | } |
|
| 17 | + | p.pos = p.pos + 1; |
|
| 18 | + | } |
|
| 19 | + | ||
| 20 | + | fn (p: *Parser) remaining() -> i32 { |
|
| 21 | + | return p.len - p.pos; |
|
| 22 | + | } |
|
| 23 | + | ||
| 24 | + | @default fn main() -> i32 { |
|
| 25 | + | let mut parser = Parser { pos: 0, len: 2 }; |
|
| 26 | + | ||
| 27 | + | // Advance successfully. |
|
| 28 | + | try parser.advance() catch { |
|
| 29 | + | assert false; |
|
| 30 | + | }; |
|
| 31 | + | assert parser.remaining() == 1; |
|
| 32 | + | ||
| 33 | + | // Advance again. |
|
| 34 | + | try parser.advance() catch { |
|
| 35 | + | assert false; |
|
| 36 | + | }; |
|
| 37 | + | assert parser.remaining() == 0; |
|
| 38 | + | ||
| 39 | + | // Advance past end -- should throw. |
|
| 40 | + | let mut threw = false; |
|
| 41 | + | try parser.advance() catch { |
|
| 42 | + | threw = true; |
|
| 43 | + | }; |
|
| 44 | + | assert threw; |
|
| 45 | + | ||
| 46 | + | return 0; |
|
| 47 | + | } |
test/tests/method.union.rad
added
+24 -0
| 1 | + | //! returns: 0 |
|
| 2 | + | //! Standalone methods on union types. |
|
| 3 | + | ||
| 4 | + | union Shape { |
|
| 5 | + | Circle(i32), |
|
| 6 | + | Rect { w: i32, h: i32 }, |
|
| 7 | + | } |
|
| 8 | + | ||
| 9 | + | fn (s: *Shape) isCircle() -> bool { |
|
| 10 | + | match *s { |
|
| 11 | + | case Shape::Circle(_) => return true, |
|
| 12 | + | else => return false, |
|
| 13 | + | } |
|
| 14 | + | } |
|
| 15 | + | ||
| 16 | + | @default fn main() -> i32 { |
|
| 17 | + | let c = Shape::Circle(5); |
|
| 18 | + | let r = Shape::Rect { w: 3, h: 4 }; |
|
| 19 | + | ||
| 20 | + | assert c.isCircle(); |
|
| 21 | + | assert not r.isCircle(); |
|
| 22 | + | ||
| 23 | + | return 0; |
|
| 24 | + | } |
test/tests/method.with.trait.rad
added
+36 -0
| 1 | + | //! returns: 0 |
|
| 2 | + | //! Standalone methods coexisting with trait instances on the same type. |
|
| 3 | + | ||
| 4 | + | record Widget { |
|
| 5 | + | x: i32, |
|
| 6 | + | y: i32, |
|
| 7 | + | } |
|
| 8 | + | ||
| 9 | + | // Standalone method. |
|
| 10 | + | fn (w: *Widget) area() -> i32 { |
|
| 11 | + | return w.x * w.y; |
|
| 12 | + | } |
|
| 13 | + | ||
| 14 | + | // Trait with its own method. |
|
| 15 | + | trait Printable { |
|
| 16 | + | fn (*Printable) code() -> i32; |
|
| 17 | + | } |
|
| 18 | + | ||
| 19 | + | instance Printable for Widget { |
|
| 20 | + | fn (w: *Widget) code() -> i32 { |
|
| 21 | + | return w.x + w.y; |
|
| 22 | + | } |
|
| 23 | + | } |
|
| 24 | + | ||
| 25 | + | @default fn main() -> i32 { |
|
| 26 | + | let w = Widget { x: 3, y: 5 }; |
|
| 27 | + | ||
| 28 | + | // Standalone method call. |
|
| 29 | + | assert w.area() == 15; |
|
| 30 | + | ||
| 31 | + | // Trait method call via trait object. |
|
| 32 | + | let p: *opaque Printable = &w; |
|
| 33 | + | assert p.code() == 8; |
|
| 34 | + | ||
| 35 | + | return 0; |
|
| 36 | + | } |