@@ -457,6 +457,229 @@ gb_internal bool check_builtin_objc_procedure(CheckerContext *c, Operand *operan
457457 return true ;
458458
459459 } break ;
460+
461+ case BuiltinProc_objc_block:
462+ {
463+ // NOTE(harold): The last argument specified in the call is the handler proc,
464+ // any other arguments before it are capture by-copy arguments.
465+ auto param_operands = slice_make<Operand>(permanent_allocator (), ce->args .count );
466+
467+ isize capture_arg_count = ce->args .count - 1 ;
468+
469+ // NOTE(harold): The first parameter is already checked at check_builtin_procedure().
470+ // Checking again would invalidate the Entity -> Value map for direct parameters if it's the handler proc.
471+ param_operands[0 ] = *operand;
472+
473+ for (isize i = 0 ; i < ce->args .count -1 ; i++) {
474+ Operand x = {};
475+ check_expr (c, &x, ce->args [i]);
476+
477+ switch (x.mode ) {
478+ case Addressing_Value:
479+ case Addressing_Context:
480+ case Addressing_Variable:
481+ case Addressing_Constant:
482+ param_operands[i] = x;
483+ break ;
484+
485+ default :
486+ gbString e = expr_to_string (x.expr );
487+ gbString t = type_to_string (x.type );
488+ error (x.expr , " '%.*s' capture arguments must be values, but got %s of type %s" , LIT (builtin_name), e, t);
489+ gb_string_free (t);
490+ gb_string_free (e);
491+ return false ;
492+ }
493+ }
494+
495+ // Validate handler proc
496+ Operand handler = {};
497+
498+ if (capture_arg_count == 0 ) {
499+ // It's already been checked and assigned
500+ handler = param_operands[0 ];
501+ } else {
502+ check_expr_or_type (c, &handler, ce->args [capture_arg_count]);
503+ param_operands[capture_arg_count] = handler;
504+ }
505+
506+ if (!is_operand_value (handler) || handler.type ->kind != Type_Proc) {
507+ gbString e = expr_to_string (handler.expr );
508+ gbString t = type_to_string (handler.type );
509+ error (handler.expr , " '%.*s' expected a procedure, but got '%s' of type %s" , LIT (builtin_name), e, t);
510+ gb_string_free (t);
511+ gb_string_free (e);
512+ return false ;
513+ }
514+
515+ Ast *handler_node = unparen_expr (handler.expr );
516+
517+ // Only direct reference to procs are allowed
518+ switch (handler_node->kind ) {
519+ case Ast_ProcLit: break ; // ok
520+ case Ast_Ident: {
521+ auto & ident = handler_node->Ident ;
522+
523+ if (ident.entity == nullptr ) {
524+ error (handler.expr , " '%.*s' failed to resolve entity from expression" , LIT (builtin_name));
525+ return false ;
526+ }
527+
528+ if (ident.entity ->kind != Entity_Procedure) {
529+ gbString e = expr_to_string (handler_node);
530+
531+ ERROR_BLOCK ();
532+ error (handler.expr , " '%.*s' expected a direct reference to a procedure" , LIT (builtin_name));
533+ if (ident.entity ->kind == Entity_Variable) {
534+ error_line (" \t Suggestion: Variables referencing a procedure are not allowed, they are not a direct procedure reference." );
535+ } else {
536+ error_line (" \t Suggestion: Ensure '%s' is not a runtime-evaluated expression." , e); // NOTE(harold): Is this case possible to hit?
537+ }
538+ error_line (" \n\t Refer to a procedure directly by its name or declare it anonymously: %.*s(proc(){})" , LIT (builtin_name));
539+
540+ gb_string_free (e);
541+ return false ;
542+ }
543+ } break ;
544+
545+ default : {
546+ gbString e = expr_to_string (handler_node);
547+ ERROR_BLOCK ();
548+ error (handler.expr , " '%.*s' expected a direct reference to a procedure" , LIT (builtin_name));
549+ if ( handler_node->kind == Ast_CallExpr) {
550+ error_line (" \t Suggestion: Do not use a procedure returned from another procedure." );
551+ } else {
552+ error_line (" \t Suggestion: Ensure '%s' is not a runtime-evaluated expression." , e);
553+ }
554+ error_line (" \n\t Refer to a procedure directly by its name or declare it anonymously: %.*s(proc(){})" , LIT (builtin_name));
555+
556+ gb_string_free (e);
557+ } return false ;
558+ } // End switch
559+
560+ auto & handler_type_proc = handler.type ->Proc ;
561+
562+ if (capture_arg_count > handler_type_proc.param_count ) {
563+ error (handler.expr , " '%.*s' captured arguments exceeded the handler's parameter count" , LIT (builtin_name));
564+ return false ;
565+ }
566+
567+ // If the handler proc is odin calling convention, but there must be a context defined in this scope.
568+ if (handler_type_proc.calling_convention == ProcCC_Odin) {
569+ if ((c->scope ->flags & ScopeFlag_ContextDefined) == 0 ) {
570+ ERROR_BLOCK ();
571+ error (handler.expr , " The handler procedure for '%.*s' requires a context, but no context is defined in the current scope" , LIT (builtin_name));
572+ error_line (" \t Suggestion: 'context = runtime.default_context()', or use the \" c\" calling convention for the handler procedure" );
573+ return false ;
574+ }
575+ }
576+
577+ // At most a single return value is supported
578+ if (handler_type_proc.result_count > 1 ) {
579+ error (handler_type_proc.node ->ProcType .results , " Handler procedures for '%.*s' cannot have multiple return values" , LIT (builtin_name));
580+ return false ;
581+ }
582+
583+ // Ensure that captured args are assignable to the handler's corresponding capture params
584+ if (handler_type_proc.param_count > 0 ) {
585+ auto & handler_param_types = handler.type ->Proc .params ->Tuple .variables ;
586+ Slice<Entity *> handler_capture_param_types = slice (handler_param_types, handler_param_types.count - capture_arg_count, handler_param_types.count );
587+
588+ for (isize i = 0 ; i < capture_arg_count; i++) {
589+ Operand op = param_operands[i];
590+ if (!check_is_assignable_to (c, &op, handler_capture_param_types[i]->type )) {
591+ gbString e = expr_to_string (op.expr );
592+ gbString src = type_to_string (op.type );
593+ gbString dst = type_to_string (handler_capture_param_types[i]->type );
594+ error (op.expr , " '%.*s' captured value '%s' of type '%s' is not assignable to type '%s'" , LIT (builtin_name), e, src, dst);
595+ gb_string_free (e);
596+ gb_string_free (src);
597+ gb_string_free (dst);
598+ return false ;
599+ }
600+ }
601+ }
602+
603+ ProcCallingConvention cc = handler_type_proc.calling_convention ;
604+ switch (cc) {
605+ case ProcCC_Odin:
606+ case ProcCC_Contextless:
607+ case ProcCC_CDecl:
608+ break ; // ok
609+ default :
610+ ERROR_BLOCK ();
611+
612+ error (handler.expr , " '%.*s' Invalid calling convention for block procedure." , LIT (builtin_name));
613+ error_line (" \t Suggestion: Do not specify a calling convention ot else use \" c\" or \" cotextless\" " );
614+ return false ;
615+ }
616+
617+ if (handler_type_proc.is_polymorphic ) {
618+ error (handler.expr , " '%.*s' Unspecialized polymorphic procedures are not allowed." , LIT (builtin_name));
619+ return false ;
620+ }
621+
622+ // Create the specialized Objc_Block type that this intrinsic will return
623+ Token ident = {};
624+ ident.kind = Token_Ident;
625+ ident.string = str_lit (" Objc_Block" );
626+ ident.pos = ast_token (call).pos ;
627+
628+ Token l_paren = {};
629+ l_paren.kind = Token_OpenParen;
630+ l_paren.string = str_lit (" (" );
631+ l_paren.pos = ident.pos ;
632+
633+ Token r_paren = {};
634+ r_paren.kind = Token_CloseParen;
635+ l_paren.string = str_lit (" )" );
636+ r_paren.pos = ident.pos ;
637+
638+ // Remove the capture args from the resulting Objc_Block type signature
639+ Ast* handler_proc_type_copy = clone_ast (handler_type_proc.node );
640+ handler_proc_type_copy->ProcType .params ->FieldList .list .count -= capture_arg_count;
641+
642+ // Make sure the Objc_Block's specialized proc is always "c" calling conv,
643+ // even if we have a context, as the invoker is always "c".
644+ // This allows us to have compatibility with the target block types with either calling convention used.
645+ handler_proc_type_copy->ProcType .calling_convention = ProcCC_CDecl;
646+
647+ Array<Ast *> poly_args = {};
648+ array_init (&poly_args, permanent_allocator (), 1 , 1 );
649+ poly_args[0 ] = handler_proc_type_copy;
650+
651+
652+ Type *t_Objc_Block = find_core_type (c->checker , str_lit (" Objc_Block" ));
653+ Operand poly_op = {};
654+ poly_op.type = t_Objc_Block;
655+ poly_op.mode = Addressing_Type;
656+
657+ Ast *poly_call = ast_call_expr (nullptr , ast_ident (nullptr , ident), poly_args, l_paren, r_paren, {});
658+
659+ auto err = check_polymorphic_record_type (c, &poly_op, poly_call);
660+
661+ if (err != 0 ) {
662+ operand->mode = Addressing_Invalid;
663+ operand->type = t_invalid;
664+ error (handler.expr , " '%.*s' failed to determine resulting Objc_Block handler procedure" , LIT (builtin_name));
665+ return false ;
666+ }
667+
668+ GB_ASSERT (poly_op.type != t_Objc_Block);
669+ GB_ASSERT (poly_op.mode == Addressing_Type);
670+
671+ bool is_global_block = capture_arg_count == 0 && handler_type_proc.calling_convention != ProcCC_Odin;
672+ if (is_global_block) {
673+ try_to_add_package_dependency (c, " runtime" , " _NSConcreteGlobalBlock" );
674+ } else {
675+ try_to_add_package_dependency (c, " runtime" , " _NSConcreteStackBlock" );
676+ }
677+
678+ *operand = poly_op;
679+ operand->type = alloc_type_pointer (operand->type );
680+ operand->mode = Addressing_Value;
681+ return true ;
682+ } break ;
460683 }
461684}
462685
@@ -2291,6 +2514,7 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As
22912514 case BuiltinProc_objc_register_selector:
22922515 case BuiltinProc_objc_register_class:
22932516 case BuiltinProc_objc_ivar_get:
2517+ case BuiltinProc_objc_block:
22942518 return check_builtin_objc_procedure (c, operand, call, id, type_hint);
22952519
22962520 case BuiltinProc___entry_point:
0 commit comments