Lower instance methods
e3e79cd294dd8e81174c7ea4a1f67a2da0b1d43e231a3092b8c3b6b6ef9b4f01
Creates v-tables in the data section.
1 parent
37fa6115
lib/std/lang/lower.rad
+231 -43
| 223 | 223 | // Slice Layout |
|
| 224 | 224 | // |
|
| 225 | 225 | // A slice is a fat pointer consisting of a data pointer and length. |
|
| 226 | 226 | // `{ ptr: u32, len: u32 }`. |
|
| 227 | 227 | ||
| 228 | - | /// Offset of slice pointer in slice data structure. |
|
| 228 | + | /// Slice data pointer offset. |
|
| 229 | 229 | const SLICE_PTR_OFFSET: i32 = 0; |
|
| 230 | 230 | /// Offset of slice length in slice data structure. |
|
| 231 | 231 | const SLICE_LEN_OFFSET: i32 = 8; |
|
| 232 | 232 | ||
| 233 | + | // Trait Object Layout |
|
| 234 | + | // |
|
| 235 | + | // A trait object is a fat pointer consisting of a data pointer and a |
|
| 236 | + | // v-table pointer. `{ data: *T, vtable: *VTable }`. |
|
| 237 | + | ||
| 238 | + | /// Trait object data pointer offset. |
|
| 239 | + | const TRAIT_OBJ_DATA_OFFSET: i32 = 0; |
|
| 240 | + | /// Trait object v-table pointer offset. |
|
| 241 | + | const TRAIT_OBJ_VTABLE_OFFSET: i32 = 8; |
|
| 242 | + | ||
| 233 | 243 | // Tagged Value Layout (optionals, tagged unions) |
|
| 234 | 244 | // |
|
| 235 | 245 | // Optionals and unions use 1-byte tags. Results use 8-byte tags. |
|
| 236 | 246 | // |
|
| 237 | 247 | // `{ tag: u8, [padding], payload: T }` |
| 846 | 856 | try lowerDataDecl(low, node, decl.value, true); |
|
| 847 | 857 | } |
|
| 848 | 858 | case ast::NodeValue::StaticDecl(decl) => { |
|
| 849 | 859 | try lowerDataDecl(low, node, decl.value, false); |
|
| 850 | 860 | } |
|
| 861 | + | case ast::NodeValue::InstanceDecl { traitName, targetType, methods } => { |
|
| 862 | + | try lowerInstanceDecl(low, node, traitName, targetType, &methods); |
|
| 863 | + | } |
|
| 851 | 864 | else => {}, |
|
| 852 | 865 | } |
|
| 853 | 866 | } |
|
| 854 | 867 | return defaultFnIdx; |
|
| 855 | 868 | } |
| 927 | 940 | self.moduleGraph = graph; |
|
| 928 | 941 | self.pkgName = pkgName; |
|
| 929 | 942 | self.currentMod = nil; |
|
| 930 | 943 | } |
|
| 931 | 944 | ||
| 932 | - | /// Lower a function declaration to an IL function. |
|
| 933 | - | /// |
|
| 934 | - | /// This sets up the per-function lowering state, processes parameters, |
|
| 935 | - | /// then lowers the function body into a CFG of basic blocks. |
|
| 936 | - | /// |
|
| 937 | - | /// For throwing functions, the return type is a result aggregate (tagged union) |
|
| 938 | - | /// rather than the declared return type. |
|
| 939 | - | fn lowerFnDecl(self: *mut Lowerer, node: *ast::Node, decl: ast::FnDecl) -> ?*il::Fn throws (LowerError) { |
|
| 940 | - | if not shouldLowerFn(&decl, self.options.buildTest) { |
|
| 941 | - | return nil; |
|
| 942 | - | } |
|
| 943 | - | let case ast::NodeValue::Ident(name) = decl.name.value else { |
|
| 944 | - | throw LowerError::ExpectedIdentifier; |
|
| 945 | - | }; |
|
| 946 | - | let data = resolver::nodeData(self.resolver, node); |
|
| 947 | - | let case resolver::Type::Fn(fnType) = data.ty else { |
|
| 948 | - | throw LowerError::ExpectedFunction; |
|
| 949 | - | }; |
|
| 950 | - | let isExtern = checkAttr(decl.attrs, ast::Attribute::Extern); |
|
| 951 | - | ||
| 952 | - | // Build qualified function name for multi-module compilation. |
|
| 953 | - | let qualName = qualifyName(self, nil, name); |
|
| 954 | - | ||
| 955 | - | // Register function symbol for cross-package call resolution. |
|
| 956 | - | if let sym = data.sym { |
|
| 957 | - | registerFnSym(self, sym, qualName); |
|
| 958 | - | } |
|
| 945 | + | /// Create a new function lowerer for a given function type and name. |
|
| 946 | + | fn fnLowerer( |
|
| 947 | + | self: *mut Lowerer, |
|
| 948 | + | node: *ast::Node, |
|
| 949 | + | fnType: *resolver::FnType, |
|
| 950 | + | qualName: *[u8] |
|
| 951 | + | ) -> FnLowerer { |
|
| 959 | 952 | let vars = try! alloc::allocSlice(self.arena, @sizeOf(VarData), @alignOf(VarData), fnType.localCount) as *mut [VarData]; |
|
| 960 | 953 | let params = try! alloc::allocSlice(self.arena, @sizeOf(FnParamBinding), @alignOf(FnParamBinding), fnType.paramTypesLen) as *mut [FnParamBinding]; |
|
| 961 | 954 | let blockData = try! alloc::allocSlice(self.arena, @sizeOf(BlockData), @alignOf(BlockData), INITIAL_BLOCKS) as *mut [BlockData]; |
|
| 962 | 955 | let loopStack = try! alloc::allocSlice(self.arena, @sizeOf(LoopCtx), @alignOf(LoopCtx), MAX_LOOP_DEPTH) as *mut [LoopCtx]; |
|
| 963 | 956 |
| 981 | 974 | returnReg: nil, |
|
| 982 | 975 | srcLoc: undefined, |
|
| 983 | 976 | }; |
|
| 984 | 977 | if self.options.debug { |
|
| 985 | 978 | let modId = self.currentMod else { |
|
| 986 | - | panic "lowerFnDecl: debug enabled but no current module"; |
|
| 979 | + | panic "fnLowerer: debug enabled but no current module"; |
|
| 987 | 980 | }; |
|
| 988 | 981 | fnLow.srcLoc = il::SrcLoc { |
|
| 989 | 982 | moduleId: modId, |
|
| 990 | 983 | offset: node.span.offset, |
|
| 991 | 984 | }; |
|
| 992 | 985 | } |
|
| 986 | + | return fnLow; |
|
| 987 | + | } |
|
| 988 | + | ||
| 989 | + | /// Lower a function declaration. |
|
| 990 | + | /// |
|
| 991 | + | /// This sets up the per-function lowering state, processes parameters, |
|
| 992 | + | /// then lowers the function body into a CFG of basic blocks. |
|
| 993 | + | /// |
|
| 994 | + | /// For throwing functions, the return type is a result aggregate |
|
| 995 | + | /// rather than the declared return type. |
|
| 996 | + | fn lowerFnDecl(self: *mut Lowerer, node: *ast::Node, decl: ast::FnDecl) -> ?*il::Fn throws (LowerError) { |
|
| 997 | + | if not shouldLowerFn(&decl, self.options.buildTest) { |
|
| 998 | + | return nil; |
|
| 999 | + | } |
|
| 1000 | + | let case ast::NodeValue::Ident(name) = decl.name.value else { |
|
| 1001 | + | throw LowerError::ExpectedIdentifier; |
|
| 1002 | + | }; |
|
| 1003 | + | let data = resolver::nodeData(self.resolver, node); |
|
| 1004 | + | let case resolver::Type::Fn(fnType) = data.ty else { |
|
| 1005 | + | throw LowerError::ExpectedFunction; |
|
| 1006 | + | }; |
|
| 1007 | + | let isExtern = checkAttr(decl.attrs, ast::Attribute::Extern); |
|
| 1008 | + | ||
| 1009 | + | // Build qualified function name for multi-module compilation. |
|
| 1010 | + | let qualName = qualifyName(self, nil, name); |
|
| 1011 | + | ||
| 1012 | + | // Register function symbol for cross-package call resolution. |
|
| 1013 | + | if let sym = data.sym { |
|
| 1014 | + | registerFnSym(self, sym, qualName); |
|
| 1015 | + | } |
|
| 1016 | + | let mut fnLow = fnLowerer(self, node, fnType, qualName); |
|
| 1017 | + | ||
| 993 | 1018 | // If the function returns an aggregate or is throwing, prepend a hidden |
|
| 994 | 1019 | // return parameter. The caller allocates the buffer and passes it |
|
| 995 | 1020 | // as the first argument; the callee writes the return value into it. |
|
| 996 | - | let needsRetReg = fnType.throwListLen > 0 |
|
| 997 | - | or (isAggregateType(*fnType.returnType) and not isSmallAggregate(*fnType.returnType)); |
|
| 998 | - | if needsRetReg and not isExtern { |
|
| 1021 | + | if requiresReturnParam(fnType) and not isExtern { |
|
| 999 | 1022 | fnLow.returnReg = nextReg(&mut fnLow); |
|
| 1000 | 1023 | } |
|
| 1001 | - | let lowParams = try lowerParams(&mut fnLow, *fnType, decl.sig.params); |
|
| 1024 | + | let lowParams = try lowerParams(&mut fnLow, *fnType, decl.sig.params, nil); |
|
| 1002 | 1025 | let func = try! alloc::alloc(self.arena, @sizeOf(il::Fn), @alignOf(il::Fn)) as *mut il::Fn; |
|
| 1003 | 1026 | ||
| 1004 | 1027 | *func = il::Fn { |
|
| 1005 | 1028 | name: qualName, |
|
| 1006 | 1029 | params: lowParams, |
| 1025 | 1048 | func.blocks = try lowerFnBody(&mut fnLow, body); |
|
| 1026 | 1049 | ||
| 1027 | 1050 | return func; |
|
| 1028 | 1051 | } |
|
| 1029 | 1052 | ||
| 1053 | + | /// Build a qualified name of the form "Type::method". |
|
| 1054 | + | fn instanceMethodName(self: *mut Lowerer, modId: ?u16, typeName: *[u8], methodName: *[u8]) -> *[u8] { |
|
| 1055 | + | let sepLen: u32 = 2; // "::" |
|
| 1056 | + | let totalLen = typeName.len + sepLen + methodName.len; |
|
| 1057 | + | let buf = try! alloc::allocSlice(self.arena, 1, 1, totalLen) as *mut [u8]; |
|
| 1058 | + | let mut pos: u32 = 0; |
|
| 1059 | + | ||
| 1060 | + | pos += try! mem::copy(&mut buf[pos..], typeName); |
|
| 1061 | + | pos += try! mem::copy(&mut buf[pos..], "::"); |
|
| 1062 | + | pos += try! mem::copy(&mut buf[pos..], methodName); |
|
| 1063 | + | ||
| 1064 | + | // TODO: Assert that `pos` equals `totalLen`. |
|
| 1065 | + | ||
| 1066 | + | return qualifyName(self, modId, &buf[..totalLen]); |
|
| 1067 | + | } |
|
| 1068 | + | ||
| 1069 | + | /// Build a v-table data name of the form "vtable::Type::Trait". |
|
| 1070 | + | fn vtableName(self: *mut Lowerer, modId: ?u16, typeName: *[u8], traitName: *[u8]) -> *[u8] { |
|
| 1071 | + | let prefix = "vtable::"; |
|
| 1072 | + | let sepLen: u32 = 2; // "::" |
|
| 1073 | + | let totalLen = prefix.len + typeName.len + sepLen + traitName.len; |
|
| 1074 | + | let buf = try! alloc::allocSlice(self.arena, 1, 1, totalLen) as *mut [u8]; |
|
| 1075 | + | let mut pos: u32 = 0; |
|
| 1076 | + | ||
| 1077 | + | pos += try! mem::copy(&mut buf[pos..], prefix); |
|
| 1078 | + | pos += try! mem::copy(&mut buf[pos..], typeName); |
|
| 1079 | + | pos += try! mem::copy(&mut buf[pos..], "::"); |
|
| 1080 | + | pos += try! mem::copy(&mut buf[pos..], traitName); |
|
| 1081 | + | ||
| 1082 | + | // TODO: Assert that `pos` equals `totalLen`. |
|
| 1083 | + | ||
| 1084 | + | return qualifyName(self, modId, &buf[..totalLen]); |
|
| 1085 | + | } |
|
| 1086 | + | ||
| 1087 | + | /// Lower an instance declaration (`instance Trait for Type { ... }`). |
|
| 1088 | + | /// |
|
| 1089 | + | /// Each method in the instance block is lowered as a standalone function |
|
| 1090 | + | /// with a qualified name of the form `Type::method`. A read-only v-table |
|
| 1091 | + | /// data record is emitted containing pointers to these functions, ordered |
|
| 1092 | + | /// by the trait's method indices. The v-table is later referenced when |
|
| 1093 | + | /// constructing trait objects for dynamic dispatch. |
|
| 1094 | + | fn lowerInstanceDecl( |
|
| 1095 | + | self: *mut Lowerer, |
|
| 1096 | + | node: *ast::Node, |
|
| 1097 | + | traitNameNode: *ast::Node, |
|
| 1098 | + | targetTypeNode: *ast::Node, |
|
| 1099 | + | methods: *ast::NodeList |
|
| 1100 | + | ) throws (LowerError) { |
|
| 1101 | + | // Look up the trait and type from the resolver. |
|
| 1102 | + | let traitSym = resolver::nodeData(self.resolver, traitNameNode).sym |
|
| 1103 | + | else throw LowerError::MissingSymbol(traitNameNode); |
|
| 1104 | + | let case resolver::SymbolData::Trait(traitInfo) = traitSym.data |
|
| 1105 | + | else throw LowerError::MissingMetadata; |
|
| 1106 | + | let typeSym = resolver::nodeData(self.resolver, targetTypeNode).sym |
|
| 1107 | + | else throw LowerError::MissingSymbol(targetTypeNode); |
|
| 1108 | + | ||
| 1109 | + | let tName = traitSym.name; |
|
| 1110 | + | let typeName = typeSym.name; |
|
| 1111 | + | ||
| 1112 | + | // Lower each instance method as a regular function. |
|
| 1113 | + | // Collect qualified names for the v-table. |
|
| 1114 | + | let mut methodNames: [*[u8]; ast::MAX_TRAIT_METHODS] = undefined; |
|
| 1115 | + | ||
| 1116 | + | for i in 0..methods.len { |
|
| 1117 | + | let methodNode = methods.list[i]; |
|
| 1118 | + | let case ast::NodeValue::InstanceMethodDecl { |
|
| 1119 | + | name, receiverName, receiverType, sig, body |
|
| 1120 | + | } = methodNode.value else continue; |
|
| 1121 | + | ||
| 1122 | + | let case ast::NodeValue::Ident(mName) = name.value else { |
|
| 1123 | + | throw LowerError::ExpectedIdentifier; |
|
| 1124 | + | }; |
|
| 1125 | + | let qualName = instanceMethodName(self, nil, typeName, mName); |
|
| 1126 | + | ||
| 1127 | + | // Get the function type from the resolver. |
|
| 1128 | + | let data = resolver::nodeData(self.resolver, methodNode); |
|
| 1129 | + | let case resolver::Type::Fn(fnType) = data.ty else { |
|
| 1130 | + | throw LowerError::ExpectedFunction; |
|
| 1131 | + | }; |
|
| 1132 | + | ||
| 1133 | + | // Register the method symbol. |
|
| 1134 | + | if let sym = data.sym { |
|
| 1135 | + | registerFnSym(self, sym, qualName); |
|
| 1136 | + | } |
|
| 1137 | + | ||
| 1138 | + | // Lower the method as a normal function. |
|
| 1139 | + | let mut fnLow = fnLowerer(self, methodNode, fnType, qualName); |
|
| 1140 | + | if requiresReturnParam(fnType) { |
|
| 1141 | + | fnLow.returnReg = nextReg(&mut fnLow); |
|
| 1142 | + | } |
|
| 1143 | + | let lowParams = try lowerParams(&mut fnLow, *fnType, sig.params, receiverName); |
|
| 1144 | + | let func = try! alloc::alloc(self.arena, @sizeOf(il::Fn), @alignOf(il::Fn)) as *mut il::Fn; |
|
| 1145 | + | ||
| 1146 | + | *func = il::Fn { |
|
| 1147 | + | name: qualName, |
|
| 1148 | + | params: lowParams, |
|
| 1149 | + | returnType: ilType(self, *fnType.returnType), |
|
| 1150 | + | isExtern: false, |
|
| 1151 | + | blocks: &[], |
|
| 1152 | + | }; |
|
| 1153 | + | if fnType.throwListLen > 0 { |
|
| 1154 | + | func.returnType = il::Type::W64; |
|
| 1155 | + | } |
|
| 1156 | + | func.blocks = try lowerFnBody(&mut fnLow, body); |
|
| 1157 | + | try pushFn(self, func); |
|
| 1158 | + | ||
| 1159 | + | let method = resolver::findTraitMethod(traitInfo, mName) |
|
| 1160 | + | else panic "lowerInstanceDecl: method not found in trait"; |
|
| 1161 | + | ||
| 1162 | + | methodNames[method.index] = qualName; |
|
| 1163 | + | } |
|
| 1164 | + | ||
| 1165 | + | // Create v-table in data section, used for dynamic dispatch. |
|
| 1166 | + | let vName = vtableName(self, nil, typeName, tName); |
|
| 1167 | + | let values = try! alloc::allocSlice( |
|
| 1168 | + | self.arena, @sizeOf(il::DataValue), @alignOf(il::DataValue), traitInfo.methodsLen |
|
| 1169 | + | ) as *mut [il::DataValue]; |
|
| 1170 | + | ||
| 1171 | + | for i in 0..traitInfo.methodsLen { |
|
| 1172 | + | values[i] = il::DataValue { |
|
| 1173 | + | item: il::DataItem::Fn(methodNames[i]), |
|
| 1174 | + | count: 1, |
|
| 1175 | + | }; |
|
| 1176 | + | } |
|
| 1177 | + | try pushData(self, il::Data { |
|
| 1178 | + | name: vName, |
|
| 1179 | + | size: traitInfo.methodsLen * resolver::PTR_SIZE, |
|
| 1180 | + | alignment: resolver::PTR_SIZE, |
|
| 1181 | + | readOnly: true, |
|
| 1182 | + | isUndefined: false, |
|
| 1183 | + | values: &values[..traitInfo.methodsLen], |
|
| 1184 | + | }); |
|
| 1185 | + | } |
|
| 1186 | + | ||
| 1030 | 1187 | /// Check if a function should be lowered. |
|
| 1031 | 1188 | fn shouldLowerFn(decl: *ast::FnDecl, buildTest: bool) -> bool { |
|
| 1032 | 1189 | if checkAttr(decl.attrs, ast::Attribute::Test) { |
|
| 1033 | 1190 | return buildTest; |
|
| 1034 | 1191 | } |
| 2857 | 3014 | newArgs[i] = il::Val::Undef; |
|
| 2858 | 3015 | } |
|
| 2859 | 3016 | return newArgs; |
|
| 2860 | 3017 | } |
|
| 2861 | 3018 | ||
| 3019 | + | /// Extract the parameter name from an [`FnParam`] AST node value. |
|
| 3020 | + | fn paramName(value: *ast::NodeValue) -> *[u8] throws (LowerError) { |
|
| 3021 | + | let case ast::NodeValue::FnParam(param) = *value else { |
|
| 3022 | + | throw LowerError::ExpectedFunctionParam; |
|
| 3023 | + | }; |
|
| 3024 | + | let case ast::NodeValue::Ident(name) = param.name.value else { |
|
| 3025 | + | throw LowerError::ExpectedIdentifier; |
|
| 3026 | + | }; |
|
| 3027 | + | return name; |
|
| 3028 | + | } |
|
| 3029 | + | ||
| 2862 | 3030 | /// Lower function parameters. Declares variables for each parameter. |
|
| 3031 | + | /// When a receiver name is passed, we're handling a trait method. |
|
| 2863 | 3032 | fn lowerParams( |
|
| 2864 | 3033 | self: *mut FnLowerer, |
|
| 2865 | 3034 | fnType: resolver::FnType, |
|
| 2866 | - | astParams: ast::NodeList |
|
| 3035 | + | astParams: ast::NodeList, |
|
| 3036 | + | receiverName: ?*ast::Node |
|
| 2867 | 3037 | ) -> *[il::Param] throws (LowerError) { |
|
| 2868 | 3038 | let offset: u32 = 1 if self.returnReg != nil else 0; |
|
| 2869 | 3039 | let totalLen = fnType.paramTypesLen + offset; |
|
| 2870 | - | ||
| 2871 | 3040 | if totalLen == 0 { |
|
| 2872 | 3041 | return &[]; |
|
| 2873 | 3042 | } |
|
| 2874 | 3043 | debug::check(fnType.paramTypesLen <= resolver::MAX_FN_PARAMS); |
|
| 2875 | 3044 |
| 2884 | 3053 | let type = ilType(self.low, *fnType.paramTypes[i]); |
|
| 2885 | 3054 | let reg = nextReg(self); |
|
| 2886 | 3055 | ||
| 2887 | 3056 | params[i + offset] = il::Param { value: reg, type }; |
|
| 2888 | 3057 | ||
| 2889 | - | // Declare the parameter variable. |
|
| 2890 | - | let paramNode = astParams.list[i]; |
|
| 2891 | - | let case ast::NodeValue::FnParam(param) = paramNode.value else { |
|
| 2892 | - | throw LowerError::ExpectedFunctionParam; |
|
| 2893 | - | }; |
|
| 2894 | - | let case ast::NodeValue::Ident(name) = param.name.value else { |
|
| 2895 | - | throw LowerError::ExpectedIdentifier; |
|
| 2896 | - | }; |
|
| 3058 | + | // Declare the parameter variable. For the receiver, the name comes |
|
| 3059 | + | // from the receiver node. |
|
| 3060 | + | // For all other parameters, the name comes from the AST params. |
|
| 3061 | + | let mut name: *[u8] = undefined; |
|
| 3062 | + | if let recNode = receiverName { |
|
| 3063 | + | if i == 0 { |
|
| 3064 | + | let case ast::NodeValue::Ident(recName) = recNode.value else { |
|
| 3065 | + | throw LowerError::ExpectedIdentifier; |
|
| 3066 | + | }; |
|
| 3067 | + | name = recName; |
|
| 3068 | + | } else { |
|
| 3069 | + | name = try paramName(&astParams.list[i - 1].value); |
|
| 3070 | + | } |
|
| 3071 | + | } else { |
|
| 3072 | + | name = try paramName(&astParams.list[i].value); |
|
| 3073 | + | } |
|
| 2897 | 3074 | let v = newVar(self, name, type, false, il::Val::Undef); |
|
| 2898 | 3075 | ||
| 2899 | 3076 | self.params[self.paramsLen] = FnParamBinding { var: v, reg }; |
|
| 2900 | 3077 | self.paramsLen += 1; |
|
| 2901 | 3078 | } |
| 3713 | 3890 | } |
|
| 3714 | 3891 | else => return false, |
|
| 3715 | 3892 | } |
|
| 3716 | 3893 | } |
|
| 3717 | 3894 | ||
| 3895 | + | /// Whether a function needs a hidden return parameter. |
|
| 3896 | + | /// |
|
| 3897 | + | /// This is the case for throwing functions, which return a result aggregate, |
|
| 3898 | + | /// and for functions returning large aggregates that cannot be passed in |
|
| 3899 | + | /// registers. |
|
| 3900 | + | fn requiresReturnParam(fnType: *resolver::FnType) -> bool { |
|
| 3901 | + | return fnType.throwListLen > 0 |
|
| 3902 | + | or (isAggregateType(*fnType.returnType) |
|
| 3903 | + | and not isSmallAggregate(*fnType.returnType)); |
|
| 3904 | + | } |
|
| 3905 | + | ||
| 3718 | 3906 | /// Check if a node is a void union variant literal (e.g. `Color::Red`). |
|
| 3719 | 3907 | /// If so, returns the variant's tag index. This enables optimized comparisons |
|
| 3720 | 3908 | /// that only check the tag instead of doing full aggregate comparison. |
|
| 3721 | 3909 | fn voidVariantIndex(res: *resolver::Resolver, node: *ast::Node) -> ?i64 { |
|
| 3722 | 3910 | let data = resolver::nodeData(res, node); |
lib/std/lang/resolver.rad
+1 -1
| 3277 | 3277 | ||
| 3278 | 3278 | return sym; |
|
| 3279 | 3279 | } |
|
| 3280 | 3280 | ||
| 3281 | 3281 | /// Find a trait method by name. |
|
| 3282 | - | fn findTraitMethod(traitType: *TraitType, name: *[u8]) -> ?*TraitMethod { |
|
| 3282 | + | pub fn findTraitMethod(traitType: *TraitType, name: *[u8]) -> ?*TraitMethod { |
|
| 3283 | 3283 | for i in 0..traitType.methodsLen { |
|
| 3284 | 3284 | if traitType.methods[i].name == name { |
|
| 3285 | 3285 | return &traitType.methods[i]; |
|
| 3286 | 3286 | } |
|
| 3287 | 3287 | } |