| 1 | // Copyright (c) 2019-2024 Alexander Medvednikov. All rights reserved. |
| 2 | // Use of this source code is governed by an MIT license |
| 3 | // that can be found in the LICENSE file. |
| 4 | module checker |
| 5 | |
| 6 | import v.ast |
| 7 | import v.token |
| 8 | |
| 9 | fn (mut c Checker) interface_decl(mut node ast.InterfaceDecl) { |
| 10 | c.check_valid_pascal_case(node.name, 'interface name', node.pos) |
| 11 | mut decl_sym := c.table.sym(node.typ) |
| 12 | is_js := node.language == .js |
| 13 | if mut decl_sym.info is ast.Interface { |
| 14 | mut has_generic_types := false |
| 15 | if node.embeds.len > 0 { |
| 16 | all_embeds := c.expand_iface_embeds(node, 0, node.embeds) |
| 17 | // eprintln('> node.name: ${node.name} | node.embeds.len: ${node.embeds.len} | all_embeds: ${all_embeds.len}') |
| 18 | node.embeds = all_embeds |
| 19 | mut emnames := map[string]int{} |
| 20 | mut emnames_ds := map[string]bool{} |
| 21 | mut emnames_ds_info := map[string]bool{} |
| 22 | mut efnames := map[string]int{} |
| 23 | mut efnames_ds_info := map[string]bool{} |
| 24 | for i, m in node.methods { |
| 25 | emnames[m.name] = i |
| 26 | emnames_ds[m.name] = true |
| 27 | emnames_ds_info[m.name] = true |
| 28 | } |
| 29 | for i, f in node.fields { |
| 30 | efnames[f.name] = i |
| 31 | efnames_ds_info[f.name] = true |
| 32 | } |
| 33 | for embed in all_embeds { |
| 34 | isym := c.table.sym(embed.typ) |
| 35 | if embed.typ.has_flag(.generic) { |
| 36 | has_generic_types = true |
| 37 | } |
| 38 | if isym.kind != .interface { |
| 39 | c.error('interface `${node.name}` tries to embed `${isym.name}`, but `${isym.name}` is not an interface, but `${isym.kind}`', |
| 40 | embed.pos) |
| 41 | continue |
| 42 | } |
| 43 | // Ensure each generic type of the embed was declared in the interface's definition |
| 44 | if node.generic_types.len > 0 && embed.typ.has_flag(.generic) { |
| 45 | embed_generic_names := c.table.generic_type_names(embed.typ) |
| 46 | node_generic_names := node.generic_types.map(c.table.type_to_str(it)) |
| 47 | for name in embed_generic_names { |
| 48 | if name !in node_generic_names { |
| 49 | interface_generic_names := node_generic_names.join(', ') |
| 50 | c.error('generic type name `${name}` is not mentioned in interface `${node.name}<${interface_generic_names}>`', |
| 51 | embed.pos) |
| 52 | } |
| 53 | } |
| 54 | } |
| 55 | isym_info := isym.info as ast.Interface |
| 56 | for f in isym_info.fields { |
| 57 | if !efnames_ds_info[f.name] { |
| 58 | efnames_ds_info[f.name] = true |
| 59 | decl_sym.info.fields << f |
| 60 | } |
| 61 | } |
| 62 | for m in isym_info.methods { |
| 63 | if !emnames_ds_info[m.name] { |
| 64 | emnames_ds_info[m.name] = true |
| 65 | decl_sym.info.methods << m.new_method_with_receiver_type(node.typ) |
| 66 | } |
| 67 | } |
| 68 | for m in isym.methods { |
| 69 | if !emnames_ds[m.name] { |
| 70 | emnames_ds[m.name] = true |
| 71 | decl_sym.methods << m.new_method_with_receiver_type(node.typ) |
| 72 | } |
| 73 | } |
| 74 | if embed_decl := c.table.interfaces[embed.typ] { |
| 75 | for f in embed_decl.fields { |
| 76 | if f.name in efnames { |
| 77 | // already existing field name, check for conflicts |
| 78 | ifield := node.fields[efnames[f.name]] |
| 79 | if field := c.table.find_field_with_embeds(isym, f.name) { |
| 80 | if ifield.typ != field.typ { |
| 81 | exp := c.table.type_to_str(ifield.typ) |
| 82 | got := c.table.type_to_str(field.typ) |
| 83 | c.error('embedded interface `${embed_decl.name}` conflicts existing field: `${ifield.name}`, expecting type: `${exp}`, got type: `${got}`', |
| 84 | ifield.pos) |
| 85 | } |
| 86 | } |
| 87 | } else { |
| 88 | efnames[f.name] = node.fields.len |
| 89 | node.fields << f |
| 90 | } |
| 91 | } |
| 92 | for m in embed_decl.methods { |
| 93 | if m.name in emnames { |
| 94 | // already existing method name, check for conflicts |
| 95 | imethod := node.methods[emnames[m.name]] |
| 96 | if em_fn := decl_sym.find_method(imethod.name) { |
| 97 | if m_fn := isym.find_method(m.name) { |
| 98 | msg := c.table.is_same_method(m_fn, em_fn) |
| 99 | if msg.len > 0 { |
| 100 | em_sig := c.table.fn_signature(em_fn, skip_receiver: true) |
| 101 | m_sig := c.table.fn_signature(m_fn, skip_receiver: true) |
| 102 | c.error('embedded interface `${embed_decl.name}` causes conflict: ${msg}, for interface method `${em_sig}` vs `${m_sig}`', |
| 103 | imethod.pos) |
| 104 | } |
| 105 | } |
| 106 | } |
| 107 | } else { |
| 108 | emnames[m.name] = node.methods.len |
| 109 | mut new_method := m.new_method_with_receiver_type(node.typ) |
| 110 | new_method.pos = embed.pos |
| 111 | node.methods << new_method |
| 112 | } |
| 113 | } |
| 114 | } |
| 115 | } |
| 116 | } |
| 117 | for i, method in node.methods { |
| 118 | if node.language == .v { |
| 119 | c.check_valid_snake_case(method.name, 'method name', method.pos) |
| 120 | } |
| 121 | if !c.ensure_type_exists(method.return_type, method.return_type_pos) { |
| 122 | continue |
| 123 | } |
| 124 | if is_js { |
| 125 | mrsym := c.table.sym(method.return_type) |
| 126 | if !mrsym.is_js_compatible() { |
| 127 | c.error('method ${method.name} returns non JS type', method.pos) |
| 128 | } |
| 129 | } |
| 130 | if method.return_type.has_flag(.generic) { |
| 131 | has_generic_types = true |
| 132 | // Ensure each generic type of the method was declared in the interface's definition |
| 133 | if node.generic_types.len > 0 { |
| 134 | method_generic_names := c.table.generic_type_names(method.return_type) |
| 135 | node_generic_names := node.generic_types.map(c.table.type_to_str(it)) |
| 136 | for name in method_generic_names { |
| 137 | if name !in node_generic_names { |
| 138 | interface_generic_names := node_generic_names.join(', ') |
| 139 | c.error('generic type name `${name}` is not mentioned in interface `${node.name}<${interface_generic_names}>`', |
| 140 | method.return_type_pos) |
| 141 | } |
| 142 | } |
| 143 | } |
| 144 | } else if !method.return_type.has_option_or_result() { |
| 145 | ret_sym := c.table.sym(method.return_type) |
| 146 | if ret_sym.info is ast.ArrayFixed && !ret_sym.info.is_fn_ret { |
| 147 | c.cast_to_fixed_array_ret(method.return_type, ret_sym) |
| 148 | } else if ret_sym.info is ast.Alias { |
| 149 | parent_sym := c.table.sym(ret_sym.info.parent_type) |
| 150 | if parent_sym.info is ast.ArrayFixed && !parent_sym.info.is_fn_ret { |
| 151 | c.cast_to_fixed_array_ret(ret_sym.info.parent_type, parent_sym) |
| 152 | } |
| 153 | } |
| 154 | } |
| 155 | for j, param in method.params { |
| 156 | if j == 0 && is_js { |
| 157 | continue // no need to check first param |
| 158 | } |
| 159 | if param.typ.has_flag(.generic) { |
| 160 | has_generic_types = true |
| 161 | } |
| 162 | if !c.ensure_type_exists(param.typ, param.pos) { |
| 163 | continue |
| 164 | } |
| 165 | if reserved_type_names_chk.matches(param.name) { |
| 166 | c.error('invalid use of reserved type `${param.name}` as a parameter name', |
| 167 | param.pos) |
| 168 | } |
| 169 | // Ensure each generic type of the method was declared in the interface's definition |
| 170 | if node.generic_types.len > 0 && param.typ.has_flag(.generic) { |
| 171 | method_generic_names := c.table.generic_type_names(param.typ) |
| 172 | node_generic_names := node.generic_types.map(c.table.type_to_str(it)) |
| 173 | for name in method_generic_names { |
| 174 | if name !in node_generic_names { |
| 175 | interface_generic_names := node_generic_names.join(', ') |
| 176 | c.error('generic type name `${name}` is not mentioned in interface `${node.name}<${interface_generic_names}>`', |
| 177 | param.type_pos) |
| 178 | } |
| 179 | } |
| 180 | } |
| 181 | if is_js { |
| 182 | psym := c.table.sym(param.typ) |
| 183 | if !psym.is_js_compatible() && !(j == method.params.len - 1 |
| 184 | && method.is_variadic) { |
| 185 | c.error('method `${method.name}` accepts non JS type as parameter', |
| 186 | method.pos) |
| 187 | } |
| 188 | } |
| 189 | } |
| 190 | for field in node.fields { |
| 191 | field_sym := c.table.sym(field.typ) |
| 192 | if field.name == method.name && field_sym.kind == .function { |
| 193 | c.error('type `${decl_sym.name}` has both field and method named `${method.name}`', |
| 194 | method.pos) |
| 195 | } |
| 196 | } |
| 197 | for j in 0 .. i { |
| 198 | if method.name == node.methods[j].name { |
| 199 | c.error('duplicate method name `${method.name}`', method.pos) |
| 200 | } |
| 201 | } |
| 202 | } |
| 203 | for i, field in node.fields { |
| 204 | if node.language == .v { |
| 205 | c.check_valid_snake_case(field.name, 'field name', field.pos) |
| 206 | } |
| 207 | if !c.ensure_type_exists(field.typ, field.pos) { |
| 208 | continue |
| 209 | } |
| 210 | if field.typ.has_flag(.generic) { |
| 211 | has_generic_types = true |
| 212 | } |
| 213 | if is_js { |
| 214 | tsym := c.table.sym(field.typ) |
| 215 | if !tsym.is_js_compatible() { |
| 216 | c.error('field `${field.name}` uses non JS type', field.pos) |
| 217 | } |
| 218 | } |
| 219 | if field.typ == node.typ && node.language != .js { |
| 220 | c.error('recursive interface fields are not allowed because they cannot be initialised', |
| 221 | field.type_pos) |
| 222 | } |
| 223 | for j in 0 .. i { |
| 224 | if field.name == node.fields[j].name { |
| 225 | c.error('field name `${field.name}` duplicate', field.pos) |
| 226 | } |
| 227 | } |
| 228 | } |
| 229 | if node.generic_types.len == 0 && has_generic_types { |
| 230 | c.error('generic interface `${node.name}` declaration must specify the generic type names, e.g. ${node.name}[T]', |
| 231 | node.pos) |
| 232 | } |
| 233 | } |
| 234 | } |
| 235 | |
| 236 | fn (mut c Checker) unwrap_generic_interface(typ ast.Type, interface_type ast.Type, pos token.Pos) ast.Type { |
| 237 | utyp := c.unwrap_generic(typ) |
| 238 | resolved_interface_type := c.unwrap_generic(interface_type) |
| 239 | if resolved_interface_type != interface_type && !resolved_interface_type.has_flag(.generic) { |
| 240 | mut resolved_sym := c.table.sym(resolved_interface_type) |
| 241 | if mut resolved_sym.info is ast.Interface { |
| 242 | if resolved_sym.info.concrete_types.len == 0 |
| 243 | && resolved_sym.generic_types.len == resolved_sym.info.generic_types.len { |
| 244 | resolved_sym.info.concrete_types = resolved_sym.generic_types.clone() |
| 245 | } |
| 246 | } |
| 247 | return resolved_interface_type |
| 248 | } |
| 249 | typ_sym := c.table.sym(utyp) |
| 250 | mut inter_sym := c.table.sym(interface_type) |
| 251 | |
| 252 | if mut inter_sym.info is ast.Interface { |
| 253 | if inter_sym.info.is_generic { |
| 254 | mut inferred_types := []ast.Type{} |
| 255 | generic_names := inter_sym.info.generic_types.map(c.table.get_type_name(it)) |
| 256 | mut interface_concrete_types := inter_sym.generic_types.clone() |
| 257 | if interface_concrete_types.len == 0 |
| 258 | && inter_sym.info.generic_types.len == inter_sym.info.concrete_types.len { |
| 259 | interface_concrete_types = inter_sym.info.concrete_types.clone() |
| 260 | } |
| 261 | // inferring interface generic types |
| 262 | for gt_name in generic_names { |
| 263 | mut inferred_type := ast.void_type |
| 264 | for ifield in inter_sym.info.fields { |
| 265 | if field := c.table.find_field_with_embeds(typ_sym, ifield.name) { |
| 266 | mut ifield_typ := ifield.typ |
| 267 | if interface_concrete_types.len == generic_names.len { |
| 268 | if resolved_field_type := c.table.convert_generic_type(ifield.typ, |
| 269 | generic_names, interface_concrete_types) |
| 270 | { |
| 271 | ifield_typ = resolved_field_type |
| 272 | } |
| 273 | } |
| 274 | inferred_field_type := c.infer_composite_generic_type(gt_name, ifield_typ, |
| 275 | field.typ) |
| 276 | if inferred_field_type != ast.void_type { |
| 277 | inferred_type = inferred_field_type |
| 278 | } |
| 279 | } |
| 280 | } |
| 281 | for imethod in inter_sym.info.methods { |
| 282 | mut method := typ_sym.find_method_with_generic_parent(imethod.name) or { |
| 283 | if pos.file_idx != -1 { |
| 284 | c.error('can not find method `${imethod.name}` on `${typ_sym.name}`, needed for interface: `${inter_sym.name}`', pos) |
| 285 | } |
| 286 | return 0 |
| 287 | } |
| 288 | method = c.resolve_method_for_concrete_type(method, typ_sym) |
| 289 | mut imethod_return_type := imethod.return_type |
| 290 | if interface_concrete_types.len == generic_names.len { |
| 291 | if resolved_return_type := c.table.convert_generic_type(imethod.return_type, |
| 292 | generic_names, interface_concrete_types) |
| 293 | { |
| 294 | imethod_return_type = resolved_return_type |
| 295 | } |
| 296 | } |
| 297 | if imethod_return_type.has_flag(.generic) |
| 298 | || c.type_has_unresolved_generic_parts(imethod_return_type) { |
| 299 | imret_sym := c.table.sym(imethod_return_type) |
| 300 | mret_sym := c.table.sym(method.return_type) |
| 301 | if method.return_type == ast.void_type |
| 302 | && imethod_return_type != method.return_type { |
| 303 | if pos.file_idx != -1 { |
| 304 | c.error('interface method `${imethod.name}` returns `${imret_sym.name}`, but implementation method `${method.name}` returns no value', |
| 305 | pos) |
| 306 | } |
| 307 | return 0 |
| 308 | } |
| 309 | if imethod_return_type == ast.void_type |
| 310 | && imethod_return_type != method.return_type { |
| 311 | if pos.file_idx != -1 { |
| 312 | c.error('interface method `${imethod.name}` returns no value, but implementation method `${method.name}` returns `${mret_sym.name}`', |
| 313 | pos) |
| 314 | } |
| 315 | return 0 |
| 316 | } |
| 317 | inferred_return_type := c.infer_composite_generic_type(gt_name, |
| 318 | imethod_return_type, method.return_type) |
| 319 | if inferred_return_type != ast.void_type { |
| 320 | inferred_type = inferred_return_type |
| 321 | } |
| 322 | } |
| 323 | for i, iparam in imethod.params { |
| 324 | if i == 0 { |
| 325 | continue |
| 326 | } |
| 327 | param := method.params[i] or { ast.Param{} } |
| 328 | mut iparam_typ := iparam.typ |
| 329 | if interface_concrete_types.len == generic_names.len { |
| 330 | if resolved_param_type := c.table.convert_generic_type(iparam.typ, |
| 331 | generic_names, interface_concrete_types) |
| 332 | { |
| 333 | iparam_typ = resolved_param_type |
| 334 | } |
| 335 | } |
| 336 | mut need_inferred_type := false |
| 337 | if !iparam.typ.has_flag(.generic) && iparam.typ == param.typ |
| 338 | && imethod.return_type == method.return_type |
| 339 | && imethod.name == method.name { |
| 340 | need_inferred_type = true |
| 341 | } |
| 342 | if iparam_typ.has_flag(.generic) |
| 343 | || c.type_has_unresolved_generic_parts(iparam_typ) || need_inferred_type { |
| 344 | inferred_param_type := c.infer_composite_generic_type(gt_name, |
| 345 | iparam_typ, param.typ) |
| 346 | if inferred_param_type != ast.void_type { |
| 347 | inferred_type = inferred_param_type |
| 348 | } else if need_inferred_type && inferred_type == ast.void_type { |
| 349 | inferred_type = param.typ |
| 350 | } |
| 351 | } |
| 352 | } |
| 353 | } |
| 354 | if inferred_type == ast.void_type { |
| 355 | if interface_concrete_types.any(c.type_has_unresolved_generic_parts(it)) { |
| 356 | return interface_type |
| 357 | } |
| 358 | if pos.file_idx != -1 { |
| 359 | c.error('could not infer generic type `${gt_name}` in interface `${inter_sym.name}`', |
| 360 | pos) |
| 361 | } |
| 362 | return interface_type |
| 363 | } |
| 364 | inferred_types << inferred_type |
| 365 | } |
| 366 | // add concrete types to method, but only if at least one |
| 367 | // inferred type is actually concrete (not still generic). |
| 368 | // When all inferred types are generic (e.g. T -> T from |
| 369 | // `implements`), registering them would cause a circular |
| 370 | // recheck that fails to resolve field types. |
| 371 | if !inferred_types.all(it.has_flag(.generic)) { |
| 372 | for imethod in inter_sym.info.methods { |
| 373 | im_fkey := imethod.fkey() |
| 374 | if c.table.register_fn_concrete_types(im_fkey, inferred_types) { |
| 375 | c.need_recheck_generic_fns = true |
| 376 | } |
| 377 | if method := typ_sym.find_method_with_generic_parent(imethod.name) { |
| 378 | method_concrete_types := c.concrete_types_for_type_symbol(typ_sym) |
| 379 | if method_concrete_types.len > 0 |
| 380 | && c.table.register_fn_concrete_types(method.fkey(), method_concrete_types) { |
| 381 | c.need_recheck_generic_fns = true |
| 382 | } |
| 383 | } |
| 384 | } |
| 385 | } |
| 386 | result_type := c.table.unwrap_generic_type(interface_type, generic_names, |
| 387 | inferred_types) |
| 388 | // Set concrete types on the instantiated interface symbol |
| 389 | mut result_sym := c.table.sym(result_type) |
| 390 | if mut result_sym.info is ast.Interface { |
| 391 | result_sym.info.concrete_types = inferred_types |
| 392 | } |
| 393 | return result_type |
| 394 | } |
| 395 | } |
| 396 | return interface_type |
| 397 | } |
| 398 | |