# This file automatically generated by /friends/.rakubrew/versions/moar-main/tools/build/gen-cat.nqp #line 1 src/Perl6/Metamodel/Configuration.nqp # Keeps track of various special types or other things that the MOP may be # configured with. class Perl6::Metamodel::Configuration { my $stash_type := nqp::null(); my $stash_attr_type := nqp::null(); method set_stash_type($type, $attr_type) { $stash_type := $type; $stash_attr_type := $attr_type; } method stash_type() { $stash_type } method stash_attr_type() { $stash_attr_type } my $submethod_type := nqp::null(); method set_submethod_type($type) { $submethod_type := $type; } method submethod_type() { $submethod_type } my $multi_sig_comparator; method set_multi_sig_comparator($comp) { $multi_sig_comparator := $comp; } method compare_multi_sigs($a, $b) { nqp::isconcrete($multi_sig_comparator) ?? $multi_sig_comparator($a, $b) !! 0 } my $role_to_class_applier_type := nqp::null(); method set_role_to_class_applier_type($rtca_type) { $role_to_class_applier_type := $rtca_type; } method role_to_class_applier_type() { $role_to_class_applier_type } my $role_to_role_applier_type := nqp::null(); method set_role_to_role_applier_type($rtra_type) { $role_to_role_applier_type := $rtra_type; } method role_to_role_applier_type() { $role_to_role_applier_type } my &sym_lookup := nqp::null(); method set_sym_lookup_routine(&slr) { &sym_lookup := &slr; } method throw_or_die($exception, $die_message, *@pos, *%named) { if nqp::isnull(&sym_lookup) { nqp::die($die_message) } else { # When &sym_lookup is registered we do have all core exception classes declared. Therefore we can use # use &sym_lookup safely. If it fails to find a symbol then fully legit X::NoSuchSymbol will be thrown. my $ex_type := &sym_lookup(nqp::hllizefor($exception, 'Raku')); # HLLize all named arguments for exception constructor. Note that if an exception attribute is Bool the the # caller of this method is responsible for using nqp::hllboolfor to produce a valid Bool instance. my %hll_named; for %named { %hll_named{nqp::iterkey_s($_)} := nqp::hllizefor(nqp::iterval($_), 'Raku'); } $ex_type.new(|@pos, |%hll_named).throw } } # A class providing some HLL core services. Normally it would be Rakudo::Internals. my $utility_class := nqp::null(); method set_utility_class($type) { $utility_class := $type; } # Produce a unique integer ID. When utility class is available then its NEXT-ID method is used. Otherwise the ID is # generated using local means. In order to avoid conflicts with Rakudo::Internals.NEXT-ID, the local generator # produces negative values. my int $last_id := 0; my $id_lock := NQPLock.new; method next_id() { if nqp::isnull($utility_class) { return $id_lock.protect({ --$last_id }) } $utility_class.NEXT-ID } my $language-revision-type := nqp::null(); method set_language_revision_type($type) { $language-revision-type := $type; } method language_revision_type() { $language-revision-type } method language_revision_object(int $revision) { nqp::box_i($revision, $language-revision-type) } # Register HLL symbol for code which doesn't have direct access to this class. For example, moar/Perl6/Ops.nqp # relies on this symbol. nqp::bindhllsym('Raku', 'METAMODEL_CONFIGURATION', Perl6::Metamodel::Configuration); } #line 1 src/Perl6/Metamodel/Archetypes.nqp use Perl6::Ops; # Provides various properties of the type of type a given meta-object # implements. This are used in various ways by the compiler and meta-model # to do correct code generation or to detect illegal use of types in # contexts with certain requirements. class Perl6::Metamodel::Archetypes { # Can this serve as a nominal type? Implies memoizability # amongst other things. has $!nominal; # If it's not nominal, does it know how to provide a nominal # type part of itself? has $!nominalizable; # Can this be inherited from? has $!inheritable; # If it's not inheritable, does it know how to produce something # that is? has $!inheritalizable; # Can this be composed (either with flattening composition, or used # as a mixin)? has $!composable; # If it's not composable, does it know how to produce something # that is? has $!composalizable; # Is it generic, in the sense of "we don't know what type this is # yet"? Note that a parametric type would not be generic - even if # it has missing parts, it defines a type. A type variable is generic, # however. This tends to cause various kinds of late (or at least # delayed) reification. In some contexts, an unresolved generic is # fatal. has $!generic; # Is it a parametric type - that is, it has missing bits that need # to be filled out before it can be used? Unlike generic, something # that is parametric does define a type - though we may need the gaps # filled it before it's useful in some way. has $!parametric; # Is it a coercive type? has $!coercive; # Is it a definite type? has $!definite; # Are we allowed to augment the type? has $!augmentable; method nominal() { $!nominal // 0 } method nominalizable() { $!nominalizable // 0 } method inheritable() { $!inheritable // 0 } method inheritalizable() { $!inheritalizable // 0 } method composable() { $!composable // 0 } method composalizable() { $!composalizable // 0 } method generic() { $!generic // 0 } method parametric() { $!parametric // 0 } method coercive() { $!coercive // 0 } method definite() { $!definite // 0 } method augmentable() { $!augmentable // 0 } } #line 1 src/Perl6/Metamodel/Naming.nqp role Perl6::Metamodel::Naming { has $!name; has $!shortname; method name($obj) { $!name // ($!name := '') } method set_name($obj, $name) { $!name := $name; $!shortname := NQPMu; # Gets set once it's needed. nqp::setdebugtypename($obj, $name); } method shortname($obj) { sub to_shortname($name) { return '' unless $name; my $shortname := $name; while (my int $colon := nqp::rindex($shortname, '::')) >= 0 { my int $paren := nqp::rindex($shortname, '[', $colon - 1); my int $comma := nqp::rindex($shortname, ',', $colon - 1); my int $chop-start := ($paren < 0 && $comma < 0) ?? 0 !! ($paren >= 0 && $paren < $comma) ?? $comma + 1 !! $paren + 1; $shortname := nqp::concat( nqp::substr($shortname, 0, $chop-start), nqp::substr($shortname, $colon + 2) ); } $shortname } $!shortname // ($!shortname := to_shortname($!name)) } method set_shortname($obj, $shortname) { $!shortname := $shortname; } } #line 1 src/Perl6/Metamodel/Documenting.nqp role Perl6::Metamodel::Documenting { has $!why; method WHY() { nqp::isnull($!why) ?? Nil !! $!why } method set_why($why) { $!why := $why; } method is-implementation-detail($type) { 0 } } #line 1 src/Perl6/Metamodel/Explaining.nqp role Perl6::Metamodel::Explaining { has $!complainee; method complainee() { $!complainee } method SET-COMPLAINEE($complainee) { $!complainee := $complainee } } #line 1 src/Perl6/Metamodel/Stashing.nqp role Perl6::Metamodel::Stashing { method add_stash($type_obj) { my $stash_type := Perl6::Metamodel::Configuration.stash_type; unless nqp::isnull($stash_type) { my $attr_type := Perl6::Metamodel::Configuration.stash_attr_type; my $stash := nqp::create($stash_type); nqp::bindattr($stash, $stash_type, '$!lock', NQPLock.new); nqp::bindattr($stash, $attr_type, '$!storage', my %symbols); nqp::bindattr_s($stash, $stash.WHAT, '$!longname', $type_obj.HOW.name($type_obj)); nqp::setwho($type_obj, $stash); } $type_obj } } #line 1 src/Perl6/Metamodel/Versioning.nqp role Perl6::Metamodel::Versioning { has $!ver; has $!auth; has $!api; method ver($obj) { $!ver // nqp::null() } method auth($obj) { $!auth // '' } method api($obj) { $!api // '' } method set_ver($obj, $ver) { $!ver := $ver if $ver } method set_auth($obj, $auth) { $!auth := $auth } method set_api($obj, $api) { $!api := $api } } #line 1 src/Perl6/Metamodel/LanguageRevision.nqp # This role is for metaclasses with language-revision dependent behavior. role Perl6::Metamodel::LanguageRevision does Perl6::Metamodel::Versioning { # Internal representation, where 1 stands for 'c' has int $!lang_rev; method !set_type_ver($obj, $internal, :$force) { self.set_ver($obj, nqp::getcomp('Raku').lvs.as-public-repr($internal, :as-str)) if ($*COMPILING_CORE_SETTING || $force) && !self.ver($obj); } # The only allowed version formats are 6.X or v6.X method set_language_version($obj, $ver?, :$force = 0) { my @lang-ver; my $comp := nqp::getcomp('Raku'); if nqp::isconcrete($ver) { @lang-ver := $comp.lvs.from-public-repr($ver); } elsif $!lang_rev && !$ver { @lang-ver.push: $!lang_rev; } elsif $comp { # When CORE is being compiled compiler's language revision may not represent the CORE's revision. But the # World's instance knows it. # TODO RakuAST needs different approach. if $*COMPILING_CORE_SETTING && $*W { @lang-ver.push: $*W.setting_revision; } else { @lang-ver.push: $comp.language_revision; } } else { return } self."!set_type_ver"($obj, @lang-ver, :$force); $!lang_rev := @lang-ver[0] if !$!lang_rev || $ver; # Awlays set if $ver is explicit } method set_language_revision($obj, int $rev, :$force = 0) { if nqp::isconcrete($rev) { if nqp::chars($rev) < 1 { nqp::die("Language revision cannot be less than 1, got " ~ $rev); } self."!set_type_ver"($obj, $rev, :$force); $!lang_rev := $rev; } else { nqp::die("Language revision must be a concrete value"); } } # Check if we're compatible with type object $type. I.e. it doesn't come from language version newer than we're # compatible with. For example, 6.c/d classes cannot consume 6.e roles. # Because there could be more than one such boundary in the future they can be passed in as an array. method check-type-compat($obj, $type, @revs) { unless nqp::isnull(self.incompat-revisions($obj, $!lang_rev, $type.HOW.language_revision($type), @revs)) { my $comp := nqp::getcomp('Raku'); Perl6::Metamodel::Configuration.throw_or_die( 'X::Language::IncompatRevisions', "Type object " ~ $obj.HOW.name($obj) ~ " of v" ~ $comp.lvs.as-public-repr($!lang_rev, :as-str) ~ " is not compatible with " ~ $type.HOW.name($type) ~ " of v" ~ $comp.lvs.as-public-repr($type.HOW.language_revision($type), :as-str), :type-a($obj), :type-b($type) ) } } method incompat-revisions($obj, int $rev-a, int $rev-b, @revs) { for @revs -> $rev { if $rev-a < $rev && $rev-b >= $rev { return $rev } } nqp::null() } # Public interface to conform to S14-roles/versioning.t behavior but still maintain compatibility with numeric # internal representation of language revisions. method language-revision($obj) { my $lang-rev-type := Perl6::Metamodel::Configuration.language_revision_type; nqp::isnull($lang-rev-type) ?? $!lang_rev !! nqp::box_i($!lang_rev, $lang-rev-type) } # This method is a private interface always returning an int, akin to compiler's object method of the same name. method language_revision($obj) { $!lang_rev } method language-version($obj) { nqp::getcomp('Raku').lvs.as-public-repr: $!lang_rev, :as-str } } #line 1 src/Perl6/Metamodel/Nominalizable.nqp role Perl6::Metamodel::Nominalizable { method nominalizable_kind($obj) { Perl6::Metamodel::Configuration.throw_or_die( 'X::Nominalizable::NoKind', self.HOW.name(self) ~ " doesn't declare method 'nominalizable_kind'", :nominalizable(self) ); } method !find_wrappee($obj, %kind_of, :$lookup = 0) { unless %kind_of{self.nominalizable_kind} { my $my_wrappee := self."!wrappee"($obj); # .^wrappee without a named parameter returns our immediate wrappee return $my_wrappee unless nqp::elems(%kind_of); # If the immediate wrappee is a nominalizable then bypass the request return $my_wrappee.HOW."!find_wrappee"($my_wrappee, %kind_of, :$lookup) if $my_wrappee.HOW.archetypes($my_wrappee).nominalizable; # Don't be aggressive if it's about introspection purposes return nqp::null() if $lookup; # Otherwise the request cannot be completed Perl6::Metamodel::Configuration.throw_or_die( 'X::Nominalizable::NoWrappee', "Can't find requested wrappee on " ~ $*ORIG-NOMINALIZABLE ~ ": reached a nominal type " ~ $my_wrappee.HOW.name($my_wrappee), :nominalizable($*ORIG-NOMINALIZABLE), :kinds(%kind_of), ) } $obj } method wrappee($obj, *%kind_of) { my $*ORIG-NOMINALIZABLE := $obj; self."!find_wrappee"($obj, %kind_of) } method wrappee-lookup($obj, *%kind_of) { self."!find_wrappee"($obj, %kind_of, :lookup) } method coerce($obj, $value) { # In general, this method should be invoked via $type.^wrappee(:coercion). But this would complicate QAST # generated by parameter binding implementation in Actions. So, let it be here. Hopefully, it'd be possible to # remove it either with RakuAST or by implementing corresponding helper nqp:: Raku op for coercive parameter # binding. my $coercion_type := self.wrappee($obj, :coercion); $coercion_type.HOW.coerce($coercion_type, $value) } } #line 1 src/Perl6/Metamodel/TypePretense.nqp role Perl6::Metamodel::TypePretense { my @pretending; method pretend_to_be(@types) { @pretending := @types; } method pretending_to_be() { @pretending } method type_check($obj, $checkee) { if $obj =:= $checkee { return 1; } for self.pretending_to_be() { if $checkee =:= $_ { return 1; } } 0; } } #line 1 src/Perl6/Metamodel/MethodDelegation.nqp role Perl6::Metamodel::MethodDelegation { my $delegate_type; method delegate_methods_to($type) { $delegate_type := $type } method delegating_methods_to() { $delegate_type } method find_method($obj, $name, :$no_fallback) { $delegate_type.HOW.find_method($delegate_type, $name, :$no_fallback); } } #line 1 src/Perl6/Metamodel/BoolificationProtocol.nqp role Perl6::Metamodel::BoolificationProtocol { has $!boolification_mode; method get_boolification_mode($obj) { $!boolification_mode } method set_boolification_mode($obj, $mode) { $!boolification_mode := $mode; } method publish_boolification_spec($obj) { if $!boolification_mode == 0 { my $meth := self.find_method($obj, 'Bool', :no_fallback(1)); if nqp::defined($meth) { nqp::setboolspec($obj, 0, $meth) } else { # Default to "not a type object" if we've no available method. nqp::setboolspec($obj, 5, nqp::null()) } } else { nqp::setboolspec($obj, $!boolification_mode, nqp::null()) } } } #line 1 src/Perl6/Metamodel/ContainerSpecProtocol.nqp role Perl6::Metamodel::ContainerSpecProtocol { has $!code_pair; method get_container_spec($obj) { $!code_pair } method set_container_spec($obj, $code_pair) { $!code_pair := $code_pair; } method publish_container_spec($obj) { for self.mro($obj) -> $class { if nqp::can($class.HOW, 'get_container_spec') { my $code_pair := $class.HOW.get_container_spec($class); if $code_pair { nqp::setcontspec($obj, 'code_pair', $code_pair); last; } } } } } #line 1 src/Perl6/Metamodel/PackageHOW.nqp class Perl6::Metamodel::PackageHOW does Perl6::Metamodel::Naming does Perl6::Metamodel::Documenting does Perl6::Metamodel::Stashing does Perl6::Metamodel::TypePretense does Perl6::Metamodel::MethodDelegation { has $!composed; my $archetypes := Perl6::Metamodel::Archetypes.new(); method archetypes($obj?) { $archetypes } method new(*%named) { nqp::findmethod(NQPMu, 'BUILDALL')(nqp::create(self), %named) } method new_type(:$name = '', :$repr, :$ver, :$auth) { if $repr { nqp::die("'package' does not support custom representations") } my $metaclass := nqp::create(self); my $obj := nqp::settypehll(nqp::newtype($metaclass, 'Uninstantiable'), 'Raku'); $metaclass.set_name($obj, $name); self.add_stash($obj); } method compose($obj, :$compiler_services) { $!composed := 1; } method is_composed($obj) { $!composed } } #line 1 src/Perl6/Metamodel/ModuleHOW.nqp class Perl6::Metamodel::ModuleHOW does Perl6::Metamodel::Naming does Perl6::Metamodel::Documenting does Perl6::Metamodel::Versioning does Perl6::Metamodel::Stashing does Perl6::Metamodel::TypePretense does Perl6::Metamodel::MethodDelegation { has $!composed; my $archetypes := Perl6::Metamodel::Archetypes.new( ); method archetypes($obj?) { $archetypes } method new(*%named) { nqp::findmethod(NQPMu, 'BUILDALL')(nqp::create(self), %named) } method new_type(:$name = '', :$repr, :$ver, :$auth, :$api) { if $repr { nqp::die("'module' does not support custom representations") } my $metaclass := self.new(); my $obj := nqp::settypehll(nqp::newtype($metaclass, 'Uninstantiable'), 'Raku'); $metaclass.set_name($obj, $name); $metaclass.set_ver($obj, $ver); $metaclass.set_auth($obj, $auth) if $auth; $metaclass.set_api($obj, $api) if $api; self.add_stash($obj); } method compose($obj, :$compiler_services) { $!composed := 1; } method is_composed($obj) { $!composed } } #line 1 src/Perl6/Metamodel/GenericHOW.nqp # A HOW that represents a generic type. It's something of a # placeholder for a type that we don't actually know yet. # It sits anywhere that a type could, and possession of one # of these confers genericity on the holder. class Perl6::Metamodel::GenericHOW does Perl6::Metamodel::Naming { my $archetypes := Perl6::Metamodel::Archetypes.new( :generic(1) ); method archetypes($obj?) { $archetypes } method new(*%named) { nqp::findmethod(NQPMu, 'BUILDALL')(nqp::create(self), %named) } # The name we're created with is both the name we'll claim # to be if asked, but also the name we'll look up in a # supplied type environment when we want to instantiate # ourself. method new_type(:$name) { my $meta := self.new(); my $obj := nqp::settypehll(nqp::newtype($meta, 'Uninstantiable'), 'Raku'); $meta.set_name($obj, $name); $obj } method instantiate_generic($obj, $type_environment) { my $name := self.name($obj); my $found := nqp::getlexrel($type_environment, $name); nqp::isnull($found) ?? $obj !! $found } method compose($obj) { } method find_method($obj, $name, *%c) { nqp::null() } method type_check($obj, $checkee) { 0 } } #line 1 src/Perl6/Metamodel/AttributeContainer.nqp role Perl6::Metamodel::AttributeContainer { # Attributes list. has @!attributes; has %!attribute_lookup; # Do we default them to rw? has $!attr_rw_by_default; # Adds an attribute. method add_attribute($obj, $attr) { my $meta_attr := nqp::decont($attr); my $name := $meta_attr.name; if nqp::isnull(%!attribute_lookup) { @!attributes := nqp::list(); %!attribute_lookup := nqp::hash(); } if nqp::existskey(%!attribute_lookup, $name) { nqp::die("Package '" ~ self.name($obj) ~ "' already has an attribute named '$name'"); } if $!attr_rw_by_default { $meta_attr.default_to_rw() } @!attributes[+@!attributes] := $meta_attr; %!attribute_lookup{$name} := $meta_attr; } # Composes all attributes. method compose_attributes($the-obj, :$compiler_services) { my $obj := nqp::decont($the-obj); my %seen_with_accessor; my %meths := nqp::hllize(self.method_table($obj)); my %orig_meths; for %meths { %orig_meths{$_.key} := 1; } for @!attributes { if $_.has_accessor() { my $acc_name := nqp::substr($_.name, 2); nqp::die("Two or more attributes declared that both want an accessor method '$acc_name'") if %seen_with_accessor{$acc_name} && !nqp::existskey(%orig_meths, $acc_name); %seen_with_accessor{$acc_name} := 1; } # Heuristic to pass along compiler_services only to Perl 6 MOP, # not to NQP one. nqp::isconcrete($compiler_services) && nqp::can($_, 'gist') ?? $_.compose($obj, :$compiler_services) !! $_.compose($obj) } } # Makes setting the type represented by the meta-object rw mean that its # attributes are rw by default. For cases when status is late set, like # with 'also is rw', fixup the previously added attributes. Note that we # can safely use 'default_to_rw' because it would pay respect to `is readonly` method set_rw($obj) { for @!attributes { $_.default_to_rw(); } $!attr_rw_by_default := 1; } # Is this type's attributes rw by default? method rw($obj) { $!attr_rw_by_default } # Gets the attribute meta-object for an attribute if it exists. # This is called by the parser so it should only return attributes # that are visible inside the current package. method get_attribute_for_usage($obj, $name) { unless nqp::existskey(%!attribute_lookup, $name) { nqp::die("No $name attribute in " ~ self.name($obj)) } %!attribute_lookup{$name} } # Returns true if attribute exists locally. method has_attribute($obj, $name) { nqp::existskey(%!attribute_lookup, $name) } method has_public_attribute($obj, $name) { nqp::existskey(%!attribute_lookup, $name) && %!attribute_lookup{$name}.has_accessor } method attribute_table($obj) { %!attribute_lookup } # Introspect attributes. method attributes($obj, :$local, :$excl, :$all) { my @attrs; for @!attributes { @attrs.push($_); } unless $local { for self.parents($obj, :excl($excl), :all($all)) { for $_.HOW.attributes($_, :local(1)) { @attrs.push($_); } } } @attrs } } #line 1 src/Perl6/Metamodel/Finalization.nqp role Perl6::Metamodel::Finalization { has @!destroyers; method setup_finalization($obj) { my @mro := self.mro($obj); my int $i := -1; my int $ocount := nqp::elems(@mro); my @destroyers; while ++$i < $ocount { my $class := @mro[$i]; my $classHOW := $class.HOW; my $destroy := $classHOW.find_method($class, 'DESTROY', :no_fallback(1)); if !nqp::isnull($destroy) && $destroy { nqp::push(@destroyers, $destroy); } if self.language_revision($obj) >= 3 && nqp::can($classHOW, 'ins_roles') && nqp::can($classHOW, 'roles') { my @ins_roles := $classHOW.ins_roles($class, :with-submethods-only); my int $j := -1; my int $rcount := nqp::elems(@ins_roles); while ++$j < $rcount { my $r := @ins_roles[$j]; my $submeth := nqp::atkey(@ins_roles[$j].HOW.submethod_table(@ins_roles[$j]), 'DESTROY'); if !nqp::isnull($submeth) && $submeth { nqp::push(@destroyers, $submeth); } } } } @!destroyers := @destroyers; if @destroyers { nqp::settypefinalize($obj, 1); } } method destroyers($obj) { @!destroyers } } #line 1 src/Perl6/Metamodel/MethodContainer.nqp role Perl6::Metamodel::MethodContainer { # Lookup table of the methods. has %!methods; has %!submethods; # The order that the methods were added in. has @!method_order; has @!method_names; # Cache that expires when we add methods (primarily to support NFA stuff). # The hash here is readonly; we copy/replace in on addition, for thread # safety (additions are dominated by lookups, so a lock - even a rw-lock - # is not ideal here). has %!cache; # Add a method. method add_method($obj, $name, $code_obj, :$handles = 1) { # Ensure we haven't already got it. $code_obj := nqp::decont($code_obj); $name := nqp::decont_s($name); if nqp::existskey(%!methods, $name) || nqp::existskey(%!submethods, $name) { # XXX try within nqp::die() causes a hang. Pre-cache the result and use it later. my $method-type := try { nqp::lc($code_obj.HOW.name($code_obj)) } // 'method'; Perl6::Metamodel::Configuration.throw_or_die( 'X::Method::Duplicate', "Package '" ~ self.name($obj) ~ "' already has a " ~ $method-type ~ " '" ~ $name ~ "' (did you mean to declare a multi method?)", :$method-type, :method($name), :typename(self.name($obj)) ); } # Add to correct table depending on if it's a Submethod. if !nqp::isnull(Perl6::Metamodel::Configuration.submethod_type) && nqp::istype($code_obj, Perl6::Metamodel::Configuration.submethod_type) { %!submethods{$name} := $code_obj; } else { %!methods{$name} := $code_obj; } # See if trait `handles` has been applied and we can use it on the target type. # XXX Also skip this step if method is being added under a different name but the original code object has been # installed earlier. This step is here until Method::Also incorporates support for :!handles argument. if $handles && nqp::can($code_obj, 'apply_handles') && nqp::can($obj.HOW, 'find_method_fallback') { my $do_apply := 1; for @!method_order { if $_ =:= $code_obj { $do_apply := 0; last } } $code_obj.apply_handles($obj) if $do_apply; } # Adding a method means any cache is no longer authoritative. if nqp::can(self, "invalidate_method_caches") { self.invalidate_method_caches($obj); } %!cache := {}; @!method_order[+@!method_order] := $code_obj; @!method_names[+@!method_names] := $name; } # Gets the method hierarchy. method methods($obj, :$local, :$excl, :$all, :$implementation-detail) { my @meths; my $check-implementation-detail := !$implementation-detail; # Always need local methods on the list. for @!method_order { @meths.push(nqp::hllizefor($_,'Raku')) unless $check-implementation-detail && nqp::can($_,'is-implementation-detail') && $_.is-implementation-detail; } # If local flag was not passed, include those from parents. unless $local { for self.parents($obj, :all($all), :excl($excl)) { for nqp::hllize($_.HOW.method_table($_)) { @meths.push(nqp::hllizefor(nqp::decont($_.value),'Raku')) unless $check-implementation-detail && nqp::can($_,'is-implementation-detail') && $_.is-implementation-detail; } for nqp::hllize($_.HOW.submethod_table($_)) { @meths.push(nqp::hllizefor(nqp::decont($_.value),'Raku')) unless $check-implementation-detail && nqp::can($_,'is-implementation-detail') && $_.is-implementation-detail; } } } @meths } method method_order($obj) { @!method_order } method method_names($obj) { @!method_names } # Get the method table. Only contains methods directly declared here, # and excludes submethods. method method_table($obj) { %!methods } # Gets the submethods table. method submethod_table($obj) { %!submethods } # Checks if this package (not its parents) declares a given # method. Checks submethods also. method declares_method($obj, $name) { %!methods{$name} || %!submethods{$name} ?? 1 !! 0 } # Looks up a method with the provided name, for introspection purposes. method lookup($obj, $name) { for self.mro($obj) { my %meth := nqp::hllize($_.HOW.method_table($obj)); if nqp::existskey(%meth, $name) { return nqp::decont(%meth{$name}); } if nqp::can($_.HOW, 'submethod_table') { my %submeth := nqp::hllize($_.HOW.submethod_table($obj)); if nqp::existskey(%submeth, $name) { return nqp::decont(%submeth{$name}); } } } nqp::null() } # Caches or updates a cached value. method cache($obj, str $key, $value_generator) { my %orig_cache := %!cache; nqp::ishash(%orig_cache) && nqp::existskey(%!cache, $key) ?? %!cache{$key} !! self.cache_add($obj, $key, $value_generator()) } method cache_get($obj, str $key) { my %caches := %!cache; nqp::ishash(%caches) ?? nqp::atkey(%caches, $key) !! nqp::null() } method cache_add($obj, str $key, $value) { my %orig_cache := %!cache; my %copy := nqp::ishash(%orig_cache) ?? nqp::clone(%orig_cache) !! {}; %copy{$key} := $value; %!cache := %copy; $value } } #line 1 src/Perl6/Metamodel/PrivateMethodContainer.nqp role Perl6::Metamodel::PrivateMethodContainer { has %!private_methods; has @!private_methods; has @!private_method_names; # Adds a private method. method add_private_method($obj, $name, $code) { $name := nqp::decont_s($name); if nqp::existskey(%!private_methods, $name) { nqp::die("Private method '$name' already declared in package " ~ self.name($obj)); } %!private_methods{$name} := $code; nqp::push(@!private_methods, $code); nqp::push(@!private_method_names, $name); } # Gets the table of private methods. method private_method_table($obj) { %!private_methods } method private_methods($obj) { @!private_methods } method private_method_names($obj) { @!private_method_names } # Locates a private method, and hands back null if it doesn't exist. method find_private_method($obj, $name) { nqp::existskey(%!private_methods, $name) ?? %!private_methods{$name} !! nqp::null() } } #line 1 src/Perl6/Metamodel/MultiMethodContainer.nqp role Perl6::Metamodel::MultiMethodContainer { # Set of multi-methods to incorporate. Not just the method handles; # each is a hash containing keys name and body. has @!multi_methods_to_incorporate; has %!multi_candidate_names; # The proto we'll clone. my $autogen_method_proto; my $autogen_submethod_proto; # Sets the proto we'll auto-gen based on. method set_autogen_proto($method_proto, $submethod_proto) { $autogen_method_proto := $method_proto; $autogen_submethod_proto := $submethod_proto; } # We can't incorporate multis right away as we don't know all parents # yet, maybe, which influences whether we even can have multis, need to # generate a proto and so forth. So just queue them up in a todo list and # we handle it at class composition time. method add_multi_method($obj, $name, $code_obj) { # Represents a multi candidate to incorporate. my class MultiToIncorporate { has $!name; has $!code; method name() { $!name } method code() { $!code } } my $how := MultiToIncorporate.HOW.WHAT; my $todo := MultiToIncorporate.new( :name($name), :code(nqp::decont($code_obj)) ); @!multi_methods_to_incorporate[+@!multi_methods_to_incorporate] := $todo; %!multi_candidate_names{$name} := 1; $code_obj; } # Gets the multi methods that are to be incorporated. method multi_methods_to_incorporate($obj) { @!multi_methods_to_incorporate } # Incorporates the multi candidates into the appropriate proto. Need to # implement proto incorporation yet. method incorporate_multi_candidates($obj) { my $num_todo := +@!multi_methods_to_incorporate; my $i := 0; my $submethod_type := Perl6::Metamodel::Configuration.submethod_type; my @new_protos; while $i != $num_todo { # Get method name and code. my $name := @!multi_methods_to_incorporate[$i].name; my $code := @!multi_methods_to_incorporate[$i].code; # Do we have anything in the methods table already in # this class? my $is_submethod := nqp::istype(nqp::what($code), $submethod_type); my $method_table := $is_submethod ?? 'submethod_table' !! 'method_table'; my $autogen_proto := $is_submethod ?? $autogen_submethod_proto !! $autogen_method_proto; my %meths := nqp::hllize(self."$method_table"($obj)); if nqp::existskey(%meths, $name) { # Yes. Only or dispatcher, though? If only, error. If # dispatcher, simply add new dispatchee. my $dispatcher := %meths{$name}; if $dispatcher.is_dispatcher { $dispatcher.add_dispatchee($code); } else { nqp::die("Cannot have a multi candidate for '" ~ $name ~ "' when an only method is also in the package '" ~ self.name($obj) ~ "'"); } } else { my $found := 0; unless $is_submethod { # Go hunting in the MRO for a method proto. Note that we don't traverse MRO for submethods. my @mro := self.mro($obj); my $j := 1; while $j != +@mro && !$found { my $parent := @mro[$j]; my %meths := nqp::hllize($parent.HOW."$method_table"($parent)); if nqp::existskey(%meths, $name) { # Found a possible - make sure it's a dispatcher, not # an only. my $dispatcher := %meths{$name}; if $dispatcher.is_dispatcher { # Clone it and install it in our method table. my $copy := $dispatcher.derive_dispatcher(); $copy.add_dispatchee($code); self.add_method($obj, $name, $copy); nqp::push(@new_protos, $copy); $found := 1; } } $j := $j + 1; } } unless $found { # No proto found, so we'll generate one here. unless $autogen_proto { nqp::die("Cannot auto-generate a proto method for '$name' in the setting"); } my $proto := $autogen_proto.instantiate_generic( nqp::hash('T', $obj)); $proto.set_name($name); $proto.add_dispatchee($code); self.add_method($obj, $name, $proto); nqp::push(@new_protos, $proto); } } if nqp::can($code, 'apply_handles') && nqp::can($obj.HOW, 'find_method_fallback') { $code.apply_handles($obj); } $i := $i + 1; } for @new_protos { if nqp::can($_, 'sort_dispatchees') { $_.sort_dispatchees(); } } @!multi_methods_to_incorporate := []; %!multi_candidate_names := nqp::hash(); } method has_multi_candidate($obj, $name) { %!multi_candidate_names{$name} } } #line 1 src/Perl6/Metamodel/MetaMethodContainer.nqp role Perl6::Metamodel::MetaMethodContainer { # Table of the methods. has %!meta_methods; has @!meta_methods; # Add a meta-method. method add_meta_method($obj, $name, $code_obj) { if nqp::existskey(%!meta_methods, $name) { nqp::die("Package '" ~ self.name($obj) ~ "' already has a meta-method '$name'"); } %!meta_methods{$name} := $code_obj; nqp::push(@!meta_methods, $code_obj); } # Get the meta-methods table: a hash of meta-methods added. method meta_method_table($obj) { %!meta_methods } # Get the meta-methods in order of declaration. method meta_methods($obj) { @!meta_methods } # Applies the added meta-methods to the current meta-object instance by # building a role containing them, and mixing it in. method compose_meta_methods($obj) { # Build flattened meta-methods set. my %meta; my @meta; for self.mro($obj) { if nqp::can($_.HOW, 'meta_method_table') { for $_.HOW.meta_methods($obj) -> $method { my str $name := $method.name; unless nqp::existskey(%meta, $name) { %meta{$name} := $method; nqp::push(@meta, $name); } } } } # If we have any meta-methods, build a role for them to go in and # compose it into the meta-object.. if %meta { my $role := $?PACKAGE.HOW.new_type(); for @meta -> $key { $role.HOW.add_method($role, $key, %meta{$key}); } $role.HOW.set_body_block($role, sub ($class) { nqp::list($role, nqp::hash('$?CLASS', $class)) }); $role.HOW.compose($role); self.HOW.mixin(self, $role); } } } #line 1 src/Perl6/Metamodel/RoleContainer.nqp role Perl6::Metamodel::RoleContainer { has @!roles_to_compose; method add_role($obj, $role) { @!roles_to_compose[+@!roles_to_compose] := nqp::decont($role) } method roles_to_compose($obj) { @!roles_to_compose } method roles-ordered($obj, @roles, :$transitive = 1, :$mro = 0) { if $transitive { my @result; $mro := nqp::can(self, 'c3_merge') if $mro; for @roles { my @r := $mro ?? [] !! @result; nqp::push(@r, $_); for $_.HOW.roles($_, :transitive) { nqp::push(@r, $_); } nqp::push(@result, @r) if $mro; } $mro ?? self.c3_merge(@result) !! @result; } else { @roles } } } #line 1 src/Perl6/Metamodel/MultipleInheritance.nqp role Perl6::Metamodel::MultipleInheritance { # Array of parents. has @!parents; # Are any of the parents hidden? has @!hides; has %!hides_ids; # Is this class hidden? has $!hidden; # Classes to exclude from the parents list in introspection by default. my @excluded; method exclude_parent($parent) { @excluded.push($parent); } method !rebuild_hides_ids() { %!hides_ids := nqp::hash(); for @!hides { nqp::scwbdisable(); %!hides_ids{~nqp::objectid(nqp::decont($_))} := 1; nqp::scwbenable(); } } # Adds a parent. method add_parent($obj, $parent, :$hides) { if self.is_composed($obj) { nqp::die("Parents cannot be added to class '" ~ self.name($obj) ~ "'after it has been composed"); } if nqp::decont($parent) =:= nqp::decont($obj) { nqp::die("Class " ~ self.name($obj) ~ " cannot inherit from itself"); } my $parent_how := $parent.HOW; if nqp::can($parent_how, 'repr_composed') && !$parent_how.repr_composed($parent) { Perl6::Metamodel::Configuration.throw_or_die( 'X::Inheritance::NotComposed', "Class " ~ self.name($obj) ~ " cannot inherit from " ~ $parent_how.name($parent) ~ " because the parent is not composed yet", :child-name(nqp::hllizefor(self.name($obj), 'Raku')), :parent-name(nqp::hllizefor($parent_how.name($parent), 'Raku')) ); } for @!parents { if nqp::decont($_) =:= nqp::decont($parent) { nqp::die("Package '" ~ self.name($obj) ~ "' already has parent '" ~ $parent.HOW.name($parent) ~ "'"); } } if $hides { @!hides[+@!hides] := $parent; } @!parents[+@!parents] := $parent; } # Introspects the parents. method parents($obj, :$local, :$tree, :$excl, :$all) { if $local { @!parents } elsif $tree { my @result; for @!parents { my @pt := [$_]; my @recursive_parents := $_.HOW.parents($_, :tree(1)); @pt.push(@recursive_parents) if @recursive_parents; @result.push(nqp::hllizefor(@pt, 'Raku').Array); } @result := @result[0] if nqp::elems(@result) == 1; return nqp::hllizefor(@result, 'Raku'); } else { # All parents is MRO minus the first thing (which is us). my @mro := self.mro($obj); my @parents; my $i := 1; while $i < +@mro { my $exclude := 0; unless $all { for @excluded { $exclude := 1 if @mro[$i] =:= $_; } } @parents.push(@mro[$i]) unless $exclude; $i := $i + 1; } @parents } } method hides($obj) { @!hides } method hides_parent($obj, $parent) { self.'!rebuild_hides_ids'() if nqp::elems(%!hides_ids) < nqp::elems(@!hides); %!hides_ids{~nqp::objectid(nqp::decont($parent))} || 0; } method hidden($obj) { $!hidden ?? 1 !! 0 } method set_hidden($obj) { $!hidden := 1; } } #line 1 src/Perl6/Metamodel/DefaultParent.nqp role Perl6::Metamodel::DefaultParent { my @default_parent_type; method set_default_parent_type($type) { @default_parent_type[0] := $type; } method has_default_parent_type() { +@default_parent_type } method get_default_parent_type() { @default_parent_type[0] } } #line 1 src/Perl6/Metamodel/BaseType.nqp # Implemented by meta-objects that don't do inheritance per se, # but want to base themselves on another type and mostly behave # like they support it. role Perl6::Metamodel::BaseType { has $!base_type; has $!base_type_set; has @!mro; method set_base_type($obj, $base_type) { if $!base_type_set { nqp::die("Base type has already been set for " ~ self.name($obj)); } $!base_type := $base_type; $!base_type_set := 1; } # Our MRO is just that of base type. method mro($obj, :$roles = 0, :$concretizations = 0, :$unhidden = 0) { unless @!mro { @!mro := nqp::list(); @!mro[0] := $obj; for $!base_type.HOW.mro($!base_type, :$roles, :$concretizations, :$unhidden) { @!mro.push($_); } } @!mro } method parents($obj, :$local, :$excl, :$all) { my @parents := [$!base_type]; unless $local { for $!base_type.HOW.parents($!base_type, :excl($excl), :all($all)) { @parents.push($_); } } @parents } } #line 1 src/Perl6/Metamodel/C3MRO.nqp role Perl6::Metamodel::C3MRO { # Storage of the MRO. has %!mro; # Computes C3 MRO. method compute_mro($class) { %!mro := nqp::hash( 'all', nqp::hash( 'no_roles', nqp::list(), # MRO with roles excluded 'all', nqp::list(), # MRO with roles as parametric groups 'all_conc', nqp::list(), # MRO with roles as concretizations ), 'unhidden', nqp::hash( 'no_roles', nqp::list(), 'all', nqp::list(), 'all_conc', nqp::list(), # MRO with roles as concretizations ), ); my @immediate_parents := $class.HOW.parents($class, :local); my @immediate_roles; if nqp::can($class.HOW, 'concretizations') { @immediate_roles := $class.HOW.concretizations($class, :local, :transitive); } # Provided we have immediate parents... my @all; # MRO with classes and roles as groups my @all_conc; # MRO with classes and roles as concretizations my @no_roles; # MRO with classes only if +@immediate_parents { if (+@immediate_parents == 1) && (+@immediate_roles == 0) { my $parent := @immediate_parents[0]; @all_conc := nqp::clone( nqp::istype($parent.HOW, Perl6::Metamodel::C3MRO) ?? $parent.HOW.mro($parent, :concretizations) !! $parent.HOW.mro($parent)); } else { # Build merge list of linearizations of all our parents, add # immediate parents and merge. my @merge_list; @merge_list.push(@immediate_roles); for @immediate_parents { @merge_list.push( nqp::istype($_.HOW, Perl6::Metamodel::C3MRO) ?? $_.HOW.mro($_, :concretizations) !! $_.HOW.mro($_) ); } @merge_list.push(@immediate_parents); @all_conc := self.c3_merge(@merge_list); } } # Put this class on the start of the list, and we're done. @all_conc.unshift($class); for @all_conc { if $_.HOW.archetypes.inheritable || nqp::istype($_.HOW, Perl6::Metamodel::NativeHOW) { # I.e. classes or natives nqp::push(@no_roles, $_); nqp::push(@all, $_); } elsif nqp::istype($_.HOW, Perl6::Metamodel::ConcreteRoleHOW) { # For concretizations fetch their respective parametric groups my $parametric := $_.HOW.roles($_, :!transitive)[0]; nqp::push(@all, $parametric.HOW.group($parametric)); } else { nqp::push(@all, $_); } } # Also compute the unhidden MRO (all the things in the MRO that # are not somehow hidden). my @unhidden_all_conc; my @unhidden_all; my @unhidden_no_roles; my %hidden; my $skip_hidden_roles := 0; my $i := -1; while ++$i < nqp::elems(@all_conc) { my $c := @all_conc[$i]; my $is_inheritable := $c.HOW.archetypes.inheritable; next if $skip_hidden_roles && !$is_inheritable; $skip_hidden_roles := 0; if %hidden{~nqp::objectid(nqp::decont($c))} || (nqp::can($c.HOW, 'hidden') && $c.HOW.hidden($c)) { $skip_hidden_roles := 1 } else { nqp::push(@unhidden_all_conc, $c); nqp::push(@unhidden_all, @all[$i]); nqp::push(@unhidden_no_roles, $c) if $is_inheritable || nqp::istype($c.HOW, Perl6::Metamodel::NativeHOW); } if nqp::can($c.HOW, 'hides') { for $c.HOW.hides($c) { %hidden{~nqp::objectid(nqp::decont($_))} := 1; } } } %!mro := nqp::hash( 'all', nqp::hash( 'all', @all, 'all_conc', @all_conc, 'no_roles', @no_roles, ), 'unhidden', nqp::hash( 'all', @unhidden_all, 'all_conc', @unhidden_all_conc, 'no_roles', @unhidden_no_roles, ), ); } # C3 merge routine. method c3_merge(@merge_list) { my @result; my $accepted; my $something_accepted := 0; my $cand_count := 0; # Try to find something appropriate to add to the MRO. for @merge_list { my @cand_list := $_; if nqp::elems(@cand_list) { my $rejected := 0; my $cand_class := @cand_list[0]; $cand_count := $cand_count + 1; for @merge_list { # Skip current list. unless $_ =:= @cand_list { # Is current candidate in the tail? If so, reject. my $cur_pos := 1; while $cur_pos <= nqp::elems($_) { if nqp::decont($_[$cur_pos]) =:= nqp::decont($cand_class) { $rejected := 1; } $cur_pos := $cur_pos + 1; } } } # If we didn't reject it, this candidate will do. unless $rejected { $accepted := $cand_class; $something_accepted := 1; last; } } } # If we never found any candidates, return an empty list. if $cand_count == 0 { return @result; } # If we didn't find anything to accept, error. unless $something_accepted { nqp::die("Could not build C3 linearization: ambiguous hierarchy"); } # Otherwise, remove what was accepted from the merge lists. my int $i := -1; while ++$i < nqp::elems(@merge_list) { my @new_list; for @merge_list[$i] { unless nqp::decont($_) =:= nqp::decont($accepted) { @new_list.push($_); } } @merge_list[$i] := @new_list; } # Need to merge what remains of the list, then put what was accepted on # the start of the list, and we're done. @result := self.c3_merge(@merge_list); @result.unshift($accepted); return @result; } # Introspects the Method Resolution Order. method mro($obj, :$roles = 0, :$concretizations = 0, :$unhidden = 0) { unless nqp::existskey(%!mro, 'all') { self.compute_mro($obj); } my $all_key := $concretizations ?? 'all_conc' !! 'all'; nqp::atkey( nqp::atkey(%!mro, $unhidden ?? 'unhidden' !! 'all'), $concretizations ?? 'all_conc' !! ($roles ?? 'all' !! 'no_roles') ); } # Introspects the Method Resolution Order without anything that has # been hidden. method mro_unhidden($obj, :$roles = 0, :$concretizations = 0) { self.mro($obj, :$roles, :$concretizations, :unhidden) } method mro_hash() { %!mro } } #line 1 src/Perl6/Metamodel/MROBasedMethodDispatch.nqp role Perl6::Metamodel::MROBasedMethodDispatch { # If needed, a cached flattened method table accounting for all methods in # this class and its parents. This is only needed in the situation that a # megamorphic callsite involves the class, so calculated and cached on # demand. has $!cached_all_method_table; # Resolve a method. On MoarVM, with the generalized dispatch mechanism, # this is called to bootstrap callsites. On backends without that, it # is only called on a published cache miss. method find_method($obj, $name, :$no_fallback, *%adverbs) { # uncomment line below for verbose information about uncached method lookups #nqp::say( "looking for " ~ $name ~ " in " ~ $obj.HOW.name($obj) ); my $obj_how := nqp::how_nd($obj); if nqp::can($obj_how, 'submethod_table') { my %submethods := nqp::hllize($obj_how.submethod_table($obj)); my $found := nqp::atkey(%submethods, $name); return $found if nqp::isconcrete($found); } my %methods; my @mro := self.mro($obj); my int $i := 0; my int $n := nqp::elems(@mro); while $i < $n { my $class := nqp::atpos(@mro, $i); %methods := nqp::hllize($class.HOW.method_table($class)); my $found := nqp::atkey(%methods, $name); return $found if nqp::isconcrete($found); $i++; } !$no_fallback && nqp::can(self, 'find_method_fallback') ?? self.find_method_fallback($obj, $name) !!nqp::null() } method find_method_qualified($obj, $qtype, $name) { if $qtype.HOW.archetypes.parametric && nqp::can(self, 'concretization') { # Resolve it via the concrete form of this parametric. Look deep for a candidate. my $conc := self.concretization($obj, $qtype, :local(0), :transitive(1), :relaxed(1)); nqp::hllize($conc.HOW.method_table($conc)){$name} || nqp::hllize($conc.HOW.submethod_table($conc)){$name} } else { # Non-parametric, so just locate it from the already concrete # type (or fallback to this if no .concretization on ourself). $qtype.HOW.find_method($qtype, $name) } } # Maybe this belongs on a role. Also, may be worth memoizing. method can($obj, $name) { my @meths; my %smt := nqp::hllize(self.submethod_table($obj)); if nqp::existskey(%smt, $name) { @meths.push(%smt{$name}); } for self.mro($obj) { my %mt := nqp::hllize($_.HOW.method_table($_)); if nqp::existskey(%mt, $name) { @meths.push(%mt{$name}) } } @meths } method publish_method_cache($obj) { } method all_method_table($obj) { my $table := $!cached_all_method_table; unless nqp::isconcrete($table) { $table := nqp::hash(); my @mro := self.mro($obj); my int $i := nqp::elems(@mro); while $i-- { my $class := nqp::atpos(@mro, $i); for nqp::hllize($class.HOW.method_table($class)) { $table{$_.key} := nqp::decont($_.value); } } for nqp::hllize($obj.HOW.submethod_table($obj)) { $table{$_.key} := nqp::decont($_.value); } nqp::scwbdisable(); $!cached_all_method_table := $table; nqp::scwbenable(); } $table } method invalidate_method_caches($obj) { nqp::scwbdisable(); $!cached_all_method_table := nqp::null(); nqp::scwbenable(); } } #line 1 src/Perl6/Metamodel/MROBasedTypeChecking.nqp role Perl6::Metamodel::MROBasedTypeChecking { method isa($obj, $type) { my $decont := nqp::decont($type); for self.mro($obj) { if nqp::decont($_) =:= $decont { return 1 } } 0 } method does($obj, $type) { nqp::hllboolfor(nqp::istype($obj, $type), "Raku") } method type_check($obj, $checkee) { # The only time we end up in here is if the type check cache was # not yet published, which means the class isn't yet fully composed. # Just hunt through MRO. for self.mro($obj) { if $_ =:= $checkee { return 1; } if nqp::can($_.HOW, 'role_typecheck_list') { for $_.HOW.role_typecheck_list($_) { if $_ =:= $checkee { return 1; } } } } 0 } method publish_type_cache($obj) { my @tc; for self.mro($obj) { @tc.push($_); if nqp::can($_.HOW, 'role_typecheck_list') { for $_.HOW.role_typecheck_list($_) { @tc.push($_); } } } nqp::settypecache($obj, @tc) } } #line 1 src/Perl6/Metamodel/Trusting.nqp # Implements managing trust relationships between types. role Perl6::Metamodel::Trusting { # Who do we trust? has @!trustees; # Adds a type that we trust. method add_trustee($obj, $trustee) { @!trustees[+@!trustees] := $trustee; } # Introspect the types that we trust. method trusts($obj) { @!trustees } # Checks if we trust a certain type. Can be used by the compiler # to check if a private call is allowable. method is_trusted($obj, $claimant) { # Always trust ourself. if $claimant.WHAT =:= $obj.WHAT { return 1; } # Otherwise, look through our trustee list. for @!trustees { if $_.WHAT =:= $claimant.WHAT { return 1; } } # If we get here, not trusted. 0 } } #line 1 src/Perl6/Metamodel/Mixins.nqp my class MixinCacheHOW { method new_type($class_type) { my $mo := self.new(); my $type := nqp::newtype($mo, 'Uninstantiable'); nqp::setparameterizer($type, sub ($type, @roles) { $class_type.HOW.generate_mixin($class_type, @roles); }); nqp::setdebugtypename($type, $class_type.HOW.name($class_type) ~ ' mixin cache'); $type } } role Perl6::Metamodel::Mixins { has $!mixin_cache; has $!is_mixin; has $!mixin_attribute; method set_is_mixin($obj) { $!is_mixin := 1 } method is_mixin($obj) { $!is_mixin } method set_mixin_attribute($obj, $attr) { $!mixin_attribute := $attr } method mixin_attribute($obj) { $!mixin_attribute } method flush_cache($obj) { } method setup_mixin_cache($obj) { $!mixin_cache := MixinCacheHOW.new_type($obj.WHAT); } method mixin($obj, *@roles, :$need-mixin-attribute) { # Lookup mixin, generating it if needed. my int $n := nqp::elems(@roles); my int $i := -1; while ++$i < $n { @roles[$i] := nqp::decont(@roles[$i]); } my $mixin_type := nqp::parameterizetype($!mixin_cache, @roles); nqp::setdebugtypename($mixin_type, $mixin_type.HOW.name($mixin_type) ~ ' mixin'); # Ensure there's a mixin attribute, if we need it. if $need-mixin-attribute { my $found := $mixin_type.HOW.mixin_attribute($mixin_type); unless $found { my $name := @roles[0].HOW.name(@roles[0]); Perl6::Metamodel::Configuration.throw_or_die( 'X::Role::Initialization', "Can only supply an initialization value for a role if it has a single public attribute, but this is not the case for '$name'", :role(@roles[0]) ); } } # If the original object was concrete, change its type by calling a # low level op. Otherwise, we just return the new type object nqp::isconcrete($obj) ?? nqp::rebless($obj, $mixin_type) !! $mixin_type } # Generates a new mixin. Not intended for direct use; use mixin, to hit # the mixin cache. method generate_mixin($obj, @roles) { # Flush its cache as promised, otherwise outdated NFAs will stick around. self.flush_cache($obj) if !nqp::isnull($obj) || self.is_mixin($obj); # Work out a type name for the post-mixed-in role. my @role_names; for @roles { @role_names.push(~$_.HOW.name($_)) } my $new_name := self.name($obj) ~ '+{' ~ nqp::join(',', @role_names) ~ '}'; my @role_shortnames; my $lang_rev := nqp::getcomp('Raku').language_revision; for @roles { my $cur := $_; @role_shortnames.push(~$_.HOW.shortname($_)); my $role_lrev := $_.HOW.language_revision($_) if nqp::can($_.HOW, 'language_revision'); $lang_rev := $role_lrev if $role_lrev && $lang_rev < $role_lrev; } my $new_shortname := $obj.HOW.shortname($obj) ~ '+{' ~ nqp::join(',', @role_shortnames) ~ '}'; # Create new type, derive it from ourself and then add # all the roles we're mixing it. my $new_type := self.new_type(:name($new_name), :repr($obj.REPR), :is_mixin); $new_type.HOW.set_is_mixin($new_type); $new_type.HOW.set_language_revision($new_type, $lang_rev); $new_type.HOW.add_parent($new_type, $obj.WHAT); for @roles { $new_type.HOW.add_role($new_type, $_); } $new_type.HOW.compose($new_type); $new_type.HOW.set_shortname($new_type, $new_shortname); $new_type.HOW.set_boolification_mode($new_type, nqp::existskey(nqp::hllize($new_type.HOW.method_table($new_type)), 'Bool') || nqp::can($new_type.HOW, 'submethod_table') && nqp::existskey(nqp::hllize($new_type.HOW.submethod_table($new_type)), 'Bool') ?? 0 !! self.get_boolification_mode($obj)); $new_type.HOW.publish_boolification_spec($new_type); # Locate an attribute that can serve as the initialization attribute, # if there is one. my $found; for $new_type.HOW.attributes($new_type, :local) { if $_.is_built { if $found { $found := NQPMu; last; } $found := $_; } } if $found { $new_type.HOW.set_mixin_attribute($new_type, $found); } $new_type } method mixin_base($obj) { for self.mro($obj) { unless $_.HOW.is_mixin($_) { return $_; } } } } #line 1 src/Perl6/Metamodel/BUILDPLAN.nqp role Perl6::Metamodel::BUILDPLAN { has @!BUILDALLPLAN; has @!BUILDPLAN; # Empty BUILDPLAN shared by all classes with empty BUILDPLANs my @EMPTY := nqp::list; # Creates the plan for building up the object. This works # out what we'll need to do up front, so we can just zip # through the "todo list" each time we need to make an object. # The plan is an array of code objects / arrays. If the element # is a code object, it should be called as a method with the named # parameters of the call to .bless. If it is an array, then the # first element of each array is an "op" # representing the task # to perform: # code = call as method (for BUILD or TWEAK) # # NOTE: Any changes here, should also be reflected in the # lib/BUILDPLAN.rakumod module, to allow for easier # core debugging of BUILDPLAN issues. # # 0 class name attr_name = set attribute from init hash # 1 class name attr_name = set a native int attribute from init hash # 2 class name attr_name = set a native num attribute from init hash # 3 class name attr_name = set a native str attribute from init hash # 10 class name attr_name = set a native uint attribute from init hash # 400 class attr_name code = call default value closure if needed # 401 class attr_name code = call default value closure if needed, int attr # 402 class attr_name code = call default value closure if needed, num attr # 403 class attr_name code = call default value closure if needed, str attr # 410 class attr_name code = call default value closure if needed, uint attr # 800 die if a required attribute is not present # 900 class attr_name code = run attribute container initializer # 1000 class attr_name = touch/vivify attribute if part of mixin # 1100 same as 0, but init to nqp::list if value absent (nqp only) # 1200 same as 0, but init to nqp::hash if value absent (nqp only) # 1300 same as 0 but *bind* the received value + optional type constraint # 1400 same as 400 but *bind* the default value + optional type constraint # 1501 die if a required int attribute is 0 # 1502 die if a required num attribute is 0e0 # 1503 die if a required str attribute is null_s (will be '' in the future) # 1510 die if a required uint attribute is 0 method create_BUILDPLAN($obj) { # First, we'll create the build plan for just this class. my @plan; my @attrs := $obj.HOW.attributes($obj, :local(1)); # When adding role's BUILD/TWEAK into the buildplan for pre-6.e classes only roles of 6.e+ origin must be # considered. my $ohow := $obj.HOW; my $only_6e_roles := nqp::can($ohow, 'language_revision') ?? $ohow.language_revision($obj) < 3 !! nqp::can($ohow, 'lang-rev-before') ?? $ohow.lang-rev-before($obj, 'e') # Support legacy approach where implemented !! 1; # Assume the HOW being compiled against an older Raku language version # Emit any container initializers. Also build hash of attrs we # do not touch in any of the BUILDPLAN so we can spit out vivify # ops at the end. my %attrs_untouched; for @attrs { if nqp::can($_, 'container_initializer') { my $ci := $_.container_initializer; if nqp::isconcrete($ci) { # https://github.com/rakudo/rakudo/issues/1226 if nqp::can($_, 'build') { my $default := $_.build; if nqp::isconcrete($default) { $*W.find_symbol(["X","Comp","NYI"]).new( feature => "Defaults on compound attribute types", workaround => "Create/Adapt TWEAK method in class " ~ $obj.HOW.name($obj) ~ ", e.g:\n\n method TWEAK() \{\n " ~ $_.name ~ " := (initial values) unless " ~ $_.name ~ ";\n }" ).throw; } } nqp::push(@plan,[900, $obj, $_.name, $ci]); next; } } if nqp::objprimspec($_.type) == 0 { %attrs_untouched{$_.name} := NQPMu; } } sub add_from_roles($name) { my @ins_roles := self.ins_roles($obj, :with-submethods-only); my $i := +@ins_roles; while --$i >= 0 { my $role := @ins_roles[$i]; # Skip any non-6.e+ role if the target is pre-6.e next if $only_6e_roles && $role.HOW.language_revision($role) < 3; my $submeth := nqp::atkey($role.HOW.submethod_table($role), $name); if !nqp::isnull($submeth) { nqp::push(@plan, $submeth); } } } add_from_roles('BUILD'); # Does it have its own BUILD? my $build := $obj.HOW.find_method($obj, 'BUILD', :no_fallback(1)); if !nqp::isnull($build) && $build { # We'll call the custom one. nqp::push(@plan,$build); } else { # No custom BUILD. Rather than having an actual BUILD # in Mu, we produce ops here per attribute that may # need initializing. for @attrs { my int $primspec := nqp::objprimspec($_.type); if $_.is_built { my $name := $_.name; my $action := $primspec || !$_.is_bound ?? 0 + $primspec !! 1300; my $info := [$action,$obj,$name,nqp::substr($name,2)]; # binding may need type info for runtime checks if $action == 1300 { my $type := $_.type; # since we may wind up here at runtime, get Mu by # HLLizing a VMNull instead of looking it up through # $*W unless $type =:= nqp::hllizefor(nqp::null(), 'Raku') { nqp::push($info,$type); } } nqp::push(@plan,$info); } } } # Ensure that any required attributes are set for @attrs { if nqp::can($_, 'required') && $_.required { my $type := $_.type; my int $primspec := nqp::objprimspec($type); my int $op := $primspec ?? 1500 + $primspec !! 800; nqp::push(@plan,[$op, $obj, $_.name, $_.required]); nqp::deletekey(%attrs_untouched, $_.name); } } # Check if there's any default values to put in place. for @attrs { next unless nqp::can($_, 'build'); my $default := nqp::decont($_.build); my $type := $_.type; my int $primspec := nqp::objprimspec($type); # compile check constants for correct type if nqp::isconcrete($default) { my $name := $_.name; my $opcode := $primspec || !$_.is_bound ?? 400 + $primspec !! 1400; my @action := [$opcode, $obj, $name, $default]; # binding defaults to additional check at runtime my $check-at-runtime := $opcode == 1400; # currently compiling, so we can do typechecking now. if !nqp::isnull(nqp::getlexdyn('$*W')) && $*W.in_unit_parse { if nqp::istype(nqp::decont($default), $*W.find_single_symbol('Code')) { # cannot typecheck code to be run later } # check native attribute elsif $primspec { my $destination := $*W.find_single_symbol( $primspec == 2 ?? "Num" !! $primspec == 3 ?? "Str" !! "Int" # 1,4,5,10 ); nqp::istype($default,$destination) ?? ($check-at-runtime := 0) !! self.throw_typecheck($_, $default, $destination) } # check opaque attribute elsif nqp::istype($default,$type) { $check-at-runtime := 0; } # associatives need to be checked at runtime elsif nqp::istype($type,$*W.find_single_symbol('Associative')) { # cannot do type checks on associatives } # positionals could be checked now elsif nqp::istype( $type, my $Positional := $*W.find_single_symbol('Positional') ) && nqp::istype($default,$Positional.of) { $check-at-runtime := 0; } # alas, something is wrong else { self.throw_typecheck($_, $default, $type); } } # add type if we need to check at runtime # since we may wind up here at runtime, get Mu by HLLizing # a VMNull instead of looking it up through $*W nqp::push(@action,$type) if $check-at-runtime && !nqp::eqaddr($type,nqp::hllizefor(nqp::null(), 'Raku')); # store the action, mark as seen nqp::push(@plan,@action); nqp::deletekey(%attrs_untouched, $name); } } # Add vivify instructions. for @attrs { # iterate over the array to get a consistent order if nqp::existskey(%attrs_untouched, $_.name) { nqp::push(@plan,[1000, $obj, $_.name]); } } add_from_roles('TWEAK'); # Does it have a TWEAK? my $TWEAK := $obj.HOW.find_method($obj, 'TWEAK', :no_fallback(1)); if !nqp::isnull($TWEAK) && $TWEAK { nqp::push(@plan,$TWEAK); } # Something in the buildplan of this class if @plan || nqp::elems(self.parents($obj)) > 1 { # Install plan for this class. @!BUILDPLAN := @plan; # Now create the full plan by getting the MRO, and working from # least derived to most derived, copying the plans. my @all_plan; my @mro := self.mro($obj); my $i := +@mro; my $noops := 0; while $i > 0 { $i := $i - 1; my $class := @mro[$i]; for $class.HOW.BUILDPLAN($class) { if nqp::islist($_) && $_[0] == 1000 { # noop in BUILDALLPLAN $noops := 1; } else { nqp::push(@all_plan, $_); } } } # Same number of elems and no noops, identical, so just keep 1 copy @!BUILDALLPLAN := $noops || +@all_plan != +@plan ?? @all_plan !! @plan } # BUILDPLAN of class itself is empty else { # Share the empty BUILDPLAN @!BUILDPLAN := @EMPTY; # Take the first "super"class's BUILDALLPLAN if possible my @mro := self.mro($obj); @!BUILDALLPLAN := +@mro > 1 ?? @mro[1].HOW.BUILDALLPLAN(@mro[1]) !! @EMPTY } } # constant value did not typecheck ok method throw_typecheck($attr, $default, $type) { my $typecheck := $*W.find_symbol(["X","TypeCheck","Attribute","Default"]); if nqp::can($typecheck,'new') { $typecheck.new( operation => $attr.is_bound ?? 'bind' !! 'assign', name => $attr.name, got => $default, expected => $type, ).throw; } # should only be in the setting else { nqp::die("Attribute '" ~ $attr.name ~ "'s default does not match type"); } } method ins_roles($obj, :$with-submethods-only = 0) { my @ins_roles; if nqp::can(self, 'concretizations') { for self.concretizations($obj, :local) { next if $with-submethods-only && !nqp::can($_.HOW, 'submethod_table'); @ins_roles.push($_); } } @ins_roles } method BUILDPLAN($obj) { @!BUILDPLAN } method BUILDALLPLAN($obj) { @!BUILDALLPLAN } } #line 1 src/Perl6/Metamodel/REPRComposeProtocol.nqp role Perl6::Metamodel::REPRComposeProtocol { has $!composed_repr; method compose_repr($obj) { unless $!composed_repr { # Is it an array type? if nqp::can(self, 'is_array_type') && self.is_array_type($obj) { if self.attributes($obj) { nqp::die("Cannot have attributes on an array representation"); } nqp::composetype(nqp::decont($obj), nqp::hash('array', nqp::hash('type', nqp::decont(self.array_type($obj))))); } # Otherwise, presume it's an attribute type. else { # Use any attribute information to produce attribute protocol # data. The protocol consists of an array... my @repr_info; # ...which contains an array per MRO entry... for self.mro($obj) -> $type_obj { my @type_info; nqp::push(@repr_info, @type_info); # ...which in turn contains the current type in the MRO... nqp::push(@type_info, nqp::decont($type_obj)); # ...then an array of hashes per attribute... my @attrs; nqp::push(@type_info, @attrs); for $type_obj.HOW.attributes(nqp::decont($type_obj), :local) -> $attr { my %attr_info; %attr_info := $attr.name; %attr_info := $attr.type; if $attr.box_target { # Merely having the key serves as a "yes". %attr_info := 1; } if nqp::can($attr, 'auto_viv_container') { %attr_info := $attr.auto_viv_container; } if $attr.positional_delegate { %attr_info := 1; } if $attr.associative_delegate { %attr_info := 1; } if nqp::can($attr, 'inlined') { %attr_info := $attr.inlined; } if nqp::can($attr, 'dimensions') { %attr_info := $attr.dimensions; } nqp::push(@attrs, %attr_info); } # ...followed by a list of immediate parents. nqp::push(@type_info, $type_obj.HOW.parents(nqp::decont($type_obj), :local)); } # Compose the representation using it. nqp::composetype(nqp::decont($obj), nqp::hash('attribute', @repr_info)); } $!composed_repr := 1; } } method repr_composed($obj) { $!composed_repr; } } #line 1 src/Perl6/Metamodel/InvocationProtocol.nqp role Perl6::Metamodel::InvocationProtocol { } #line 1 src/Perl6/Metamodel/RolePunning.nqp role Perl6::Metamodel::RolePunning { # Meta-object we use to make a pun. my $pun_meta; # Exceptions to the punning. Hash of name to actual object to call on. my %exceptions; # The pun for the current meta-object. has $!pun; # Did we make a pun? has $!made_pun; # Representation to pun to, if any. has str $!pun_repr; # Configures the punning. method configure_punning($my_pun_meta, %my_exceptions) { $pun_meta := $my_pun_meta; %exceptions := %my_exceptions; } method set_pun_repr($obj, $repr) { $!pun_repr := $repr } method pun_repr($obj) { $!pun_repr } # Produces the pun. method make_pun($obj) { my $pun := $!pun_repr ?? $pun_meta.new_type(:name(self.name($obj)), :repr($!pun_repr)) !! $pun_meta.new_type(:name(self.name($obj))); $pun.HOW.add_role($pun, $obj); $pun.HOW.set_pun_source($pun, $obj); $pun.HOW.compose($pun); my $why := self.WHY; if $why { $pun.set_why(self.WHY); } $pun } # Returns the pun (only creating it if it wasn't already created) method pun($obj) { unless $!made_pun { $!pun := self.make_pun($obj); $!made_pun := 1; } $!pun } # Produces something that can be inherited from (the pun). method inheritalize($obj) { self.pun($obj) } # Do a pun-based dispatch. If we pun, return a thunk that will delegate. method find_method($obj, $name, *%c) { if nqp::existskey(%exceptions, $name) { return nqp::findmethod(%exceptions{$name}, $name); } unless $!made_pun { $!pun := self.make_pun($obj); $!made_pun := 1; } unless nqp::can($!pun, $name) { return nqp::null(); } -> $inv, *@pos, *%named { $!pun."$name"(|@pos, |%named) } } method is_method_call_punned($obj, $name) { !nqp::existskey(%exceptions, $name) } } #line 1 src/Perl6/Metamodel/ArrayType.nqp # Handles type declarations that really map down to array types of some kind, # and thus should be composed as an array-ish representation. role Perl6::Metamodel::ArrayType { has int $!is_array_type; has $!array_type; method is_array_type($obj) { $!is_array_type } method array_type($obj) { $!array_type } method set_array_type($obj, $type) { $!is_array_type := 1; $!array_type := $type; } } #line 1 src/Perl6/Metamodel/RoleToRoleApplier.nqp my class RoleToRoleApplier { method apply($target, @roles) { # Ensure we actually have something to appply. unless +@roles { return []; } # Aggregate all of the methods sharing names, eliminating # any duplicates (a method can't collide with itself). my %meth_info; my @meth_names; my %meth_providers; my %priv_meth_info; my @priv_meth_names; my %priv_meth_providers; my $with_submethods := $target.HOW.language_revision($target) < 3; # less than 6.e my $submethod_type := Perl6::Metamodel::Configuration.submethod_type; for @roles { my $role := $_; sub build_meth_info(@methods, @meth_names, %meth_info_to_use, @meth_names_to_use, %meth_providers_to_use) { my $meth_iterator := nqp::iterator(@methods); for @meth_names -> $name { my $meth := nqp::shift($meth_iterator); # Only transfer submethods from pre-6.e roles into pre-6.e classes. next if nqp::istype($meth, $submethod_type) && !($with_submethods && $role.HOW.language_revision($role) < 3); my @meth_list; my @meth_providers; if nqp::existskey(%meth_info_to_use, $name) { @meth_list := %meth_info_to_use{$name}; @meth_providers := %meth_providers_to_use{$name}; } else { %meth_info_to_use{$name} := @meth_list; nqp::push(@meth_names_to_use, $name); %meth_providers_to_use{$name} := @meth_providers; } my $found := 0; for @meth_list { if $meth =:= $_ { $found := 1; } elsif nqp::can($meth, 'id') && nqp::can($_, 'id') { $found := $meth.id == $_.id; } } unless $found { @meth_list.push($meth); @meth_providers.push($role.HOW.name($role)); } } } build_meth_info( nqp::hllize($_.HOW.method_order($_)), nqp::hllize($_.HOW.method_names($_)), %meth_info, @meth_names, %meth_providers ); build_meth_info( nqp::hllize($_.HOW.private_methods($_)), nqp::hllize($_.HOW.private_method_names($_)), %priv_meth_info, @priv_meth_names, %priv_meth_providers ) if nqp::can($_.HOW, 'private_method_table'); } # Also need methods of target. my %target_meth_info := nqp::hllize($target.HOW.method_table($target)); # Process method list. for @meth_names -> $name { my @add_meths := %meth_info{$name}; # Do we already have a method of this name? If so, ignore all of the # methods we have from elsewhere. unless nqp::existskey(%target_meth_info, $name) { # No methods in the target role. If only one, it's easy... if +@add_meths == 1 { $target.HOW.add_method($target, $name, @add_meths[0]); } else { # Find if any of the methods are actually requirements, not # implementations. my @impl_meths; for @add_meths { my $yada := 0; try { $yada := $_.yada; } unless $yada { @impl_meths.push($_); } } # If there's still more than one possible - add to collisions list. # If we got down to just one, add it. If they were all requirements, # just choose one. if +@impl_meths == 1 { $target.HOW.add_method($target, $name, @impl_meths[0]); } elsif +@impl_meths == 0 { $target.HOW.add_method($target, $name, @add_meths[0]); } else { $target.HOW.add_collision($target, $name, %meth_providers{$name}); } } } } # Process private method list. if nqp::can($target.HOW, 'private_method_table') { my %target_priv_meth_info := nqp::hllize($target.HOW.private_method_table($target)); for @priv_meth_names -> $name { my @add_meths := %priv_meth_info{$name}; unless nqp::existskey(%target_priv_meth_info, $name) { if +@add_meths == 1 { $target.HOW.add_private_method($target, $name, @add_meths[0]); } else { # Find if any of the methods are actually requirements, not # implementations. my @impl_meths; for @add_meths { my $yada := 0; try { $yada := $_.yada; } unless $yada { @impl_meths.push($_); } } # If there's still more than one possible - add to collisions list. # If we got down to just one, add it. If they were all requirements, # just choose one. if +@impl_meths == 1 { $target.HOW.add_private_method($target, $name, @impl_meths[0]); } elsif +@impl_meths == 0 { # any of the method stubs will do $target.HOW.add_private_method($target, $name, @add_meths[0]); } else { $target.HOW.add_collision($target, $name, %priv_meth_providers{$name}, :private(1)); } } } } } # Compose multi-methods; need to pay attention to the signatures. my %multis_by_name; my @multi_names; my %multis_required_by_name; my @multis_required_names; for @roles -> $role { my $how := $role.HOW; if nqp::can($how, 'multi_methods_to_incorporate') { for $how.multi_methods_to_incorporate($role) { my $name := $_.name; my $to_add := $_.code; next if nqp::istype($to_add, $submethod_type) && !($with_submethods && $role.HOW.language_revision($role) < 3); my $yada := 0; try { $yada := $to_add.yada; } if $yada { %multis_required_by_name{$name} := [] unless %multis_required_by_name{$name}; nqp::push(%multis_required_by_name{$name}, $to_add); nqp::push(@multis_required_names, $name); } else { if %multis_by_name{$name} -> @existing { # A multi-method can't conflict with itself. my int $already := 0; for @existing { if $_[1] =:= $to_add { $already := 1; last; } } nqp::push(@existing, [$role, $to_add]) unless $already; } else { %multis_by_name{$name} := [[$role, $to_add],]; nqp::push(@multi_names, $name); } } } } } # Look for conflicts, and compose non-conflicting. for @multi_names -> $name { my @cands := %multis_by_name{$name}; for @cands -> $c1 { my @collides; for @cands -> $c2 { unless $c1[1] =:= $c2[1] { if Perl6::Metamodel::Configuration.compare_multi_sigs($c1[1], $c2[1]) { for ($c1, $c2) { nqp::push(@collides, $_[0].HOW.name($_[0])); } last; } } } if @collides { $target.HOW.add_collision($target, $name, @collides, :multi($c1[1])); } else { $target.HOW.add_multi_method($target, $name, $c1[1]); } } } # Pass on any unsatisfied requirements (note that we check for the # requirements being met when applying the summation of roles to a # class, so we can avoid duplicating that logic here.) for @multis_required_names -> $name { for %multis_required_by_name{$name} { $target.HOW.add_multi_method($target, $name, $_); } } my %cur-attrs; my class AttrReg { has $!attr; has $!from; method attr() { $!attr } method from() { $!from } } sub reg-cur-attr($attr, $from) { %cur-attrs{$attr.name} := AttrReg.new(:$attr, :$from); } my @cur_attrs := $target.HOW.attributes($target, :local(1)); for @cur_attrs { reg-cur-attr($_, $target); } # Now do the other bits. for @roles -> $r { my $how := $r.HOW; # Compose is any attributes, unless there's a conflict. my @attributes := $how.attributes($r, :local(1)); for @attributes -> $add_attr { my $skip := 0; if nqp::can($add_attr, 'original') { my $name := $add_attr.name; if nqp::existskey(%cur-attrs, $name) { my $cur-attr := %cur-attrs{$name}.attr; if (nqp::decont($cur-attr.original) =:= nqp::decont($add_attr.original) && nqp::decont($cur-attr.type) =:= nqp::decont($add_attr.type)) || (nqp::decont($cur-attr) =:= nqp::decont($add_attr)) { $skip := 1; } else { if $cur-attr.name eq $add_attr.name { Perl6::Metamodel::Configuration.throw_or_die( 'X::Role::Attribute::Conflicts', "Attribute '" ~ $cur-attr.name ~ "' conflicts in role composition", :$target, :attribute($cur-attr), :from1(%cur-attrs{$name}.from), :from2($r) ) } } } } unless $skip { $target.HOW.add_attribute($target, $add_attr); reg-cur-attr($add_attr, $r); } } # Any parents can also just be copied over. if nqp::can($how, 'parents') { my @parents := $how.parents($r, :local(1)); for @parents -> $p { $target.HOW.add_parent($target, $p, :hides($how.hides_parent($r, $p))); } } if nqp::can($target.HOW, 'is_array_type') && !$target.HOW.is_array_type($target) { if nqp::can($how, 'is_array_type') { if $how.is_array_type($r) { $target.HOW.set_array_type($target, $how.array_type($r)); } } } } 1; } Perl6::Metamodel::Configuration.set_role_to_role_applier_type(RoleToRoleApplier); } #line 1 src/Perl6/Metamodel/Concretization.nqp # Support for mapping of non-specialized roles into their concretized state. role Perl6::Metamodel::Concretization { has @!concretizations; has %!conc_table; my $lock := NQPLock.new; method add_concretization($obj, $role, $concrete) { @!concretizations[+@!concretizations] := [$role, $concrete]; } method concretizations($obj, :$local = 0, :$transitive = 1) { my @conc; for @!concretizations { my @c := $transitive ?? [] !! @conc; nqp::push(@c, $_[1]); if $transitive && nqp::can($_[1].HOW, 'concretizations') { for $_[1].HOW.concretizations($_[1], :$local) { nqp::push(@c, $_); } } nqp::push(@conc, @c) if $transitive; } @conc := self.c3_merge(@conc) if $transitive; unless $local { for self.parents($obj, :local) { if nqp::can($_.HOW, 'concretizations') { for $_.HOW.concretizations($_, :$local, :$transitive) { nqp::push(@conc, $_) } } } } @conc } method !maybe_rebuild_table() { # Capturing the concretization list is first and foremost because we # depend on its size to know whether or not a rebuild is necessary, but # there may be a wait before then. Try for more predictable output. my int $captured := nqp::elems(@!concretizations); $lock.protect: { # The concretization table can be depended on outside a # thread-safe context, e.g. MRO-based method dispatch. Parsing # a grammar from a start block can lead to a concurrent access # and modification, for instance. my %conc_table := %!conc_table; my int $cached := nqp::elems(%conc_table); if $cached < $captured { %conc_table := nqp::clone(%conc_table); repeat { # Update. my @c := @!concretizations[$cached]; %conc_table{~nqp::objectid(nqp::decont(@c[0]))} := nqp::decont(@c[1]); } while ++$cached < $captured; nqp::scwbdisable(); %!conc_table := %conc_table; nqp::scwbenable(); } %conc_table } } # Returns a list where the first element is the number of roles found and the rest are actual type objects. method concretization_lookup($obj, $ptype, :$local = 0, :$transitive = 1, :$relaxed = 0) { my %working_conc_table := self.'!maybe_rebuild_table'(); return [0] unless !$local || $transitive || nqp::elems(%working_conc_table); $ptype := nqp::decont($ptype); my $id := ~nqp::objectid($ptype); my @result; if nqp::existskey(%working_conc_table, $id) { return [1, %working_conc_table{$id}]; } if $relaxed { # Try search by role group for curryings. The first match is ok. Used by FQN method calls. @result[0] := 0; for @!concretizations { next unless $_[0].HOW.archetypes.parametric; my $conc := nqp::can($_[0].HOW, 'curried_role') ?? $_[0].HOW.curried_role($_[0]) !! $_[0]; if $conc =:= $ptype { ++@result[0]; nqp::push(@result, $_[1]); } } return @result if @result[0]; } return [0] if !$relaxed && $obj.HOW.is_composed($obj) && !nqp::istype(nqp::decont($obj), $ptype); if $transitive { for @!concretizations { if nqp::istype($_[1], $ptype) { @result := $_[1].HOW.concretization_lookup($_[1], $ptype, :$local, :transitive, :$relaxed); return @result if @result[0]; } } } unless $local { for self.parents($obj, :local) { @result := $_.HOW.concretization_lookup($_, $ptype, :local(0), :$transitive, :$relaxed); return @result if @result[0]; } } [0] } method concretization($obj, $ptype, :$local = 0, :$transitive = 1, :$relaxed = 0) { my @result := self.concretization_lookup($obj, $ptype, :$local, :$transitive, :$relaxed); nqp::die("No concretization found for " ~ $ptype.HOW.name($ptype)) unless @result[0]; nqp::die("Ambiguous concretization lookup for " ~ $ptype.HOW.name($ptype)) if @result[0] > 1; @result[1] } } #line 1 src/Perl6/Metamodel/ConcretizationCache.nqp # Cache concretizations on a class. Avoid re-specializing a role if its concretization already exists for the target # type object and matches same arguments. # This is different from Perl6::Metamodel::Concretization in the way that it: # - only used at compile time # - provides interface for role specialization to find out if identical specialization has been done already # - is not an introspection mechanism role Perl6::Metamodel::ConcretizationCache { has %!conc_cache; my $capture_type := nqp::null(); method !make_capture(@pos, %named) { if nqp::isnull($capture_type) { # Fetch and preserve Capture type object. But don't do so until it's fully ready. $capture_type := nqp::gethllsym('Raku', 'Capture'); return nqp::null() if nqp::isnull($capture_type) || !$capture_type.HOW.is_composed($capture_type) } my $capture := nqp::create($capture_type); # We need this at class compilation time. But the class itself isn't composed yet and cannot be used with # Capture. For this reason we remove it from the positionals. It's ok as long as we only operate on the # currently compiled class. my @cpos := nqp::clone(@pos); nqp::shift(@cpos); nqp::bindattr($capture, $capture_type, '@!list', @cpos); nqp::bindattr($capture, $capture_type, '%!hash', %named); $capture } method add_conc_to_cache($class, $role, @pos, %named, $concretization) { my $capture := self.'!make_capture'(@pos, %named); unless nqp::isnull($capture) { my $obj-id := ~nqp::objectid($role); nqp::scwbdisable(); %!conc_cache{$obj-id} := [] unless %!conc_cache{$obj-id}; nqp::push(%!conc_cache{$obj-id}, [$capture, $concretization]); nqp::scwbenable(); } $concretization } method get_cached_conc($class, $role, @pos, %named) { my $capture := self.'!make_capture'(@pos, %named); unless nqp::isnull($capture) { my $obj-id := ~nqp::objectid($role); if nqp::existskey(%!conc_cache, $obj-id) { for %!conc_cache{$obj-id} { return $_[1] if try $capture.ACCEPTS($_[0]); } } } nqp::null() } method wipe_conc_cache() { %!conc_cache := nqp::hash() } } #line 1 src/Perl6/Metamodel/ConcreteRoleHOW.nqp class Perl6::Metamodel::ConcreteRoleHOW does Perl6::Metamodel::Naming does Perl6::Metamodel::LanguageRevision does Perl6::Metamodel::PrivateMethodContainer does Perl6::Metamodel::MethodContainer does Perl6::Metamodel::MultiMethodContainer does Perl6::Metamodel::AttributeContainer does Perl6::Metamodel::RoleContainer does Perl6::Metamodel::MultipleInheritance does Perl6::Metamodel::ArrayType does Perl6::Metamodel::Concretization does Perl6::Metamodel::C3MRO { # Any collisions to resolve. has @!collisions; # The (parametric) role(s) that this concrete one was directly derived # from. has @!roles; # Full flat list of done roles. has @!role_typecheck_list; # Are we composed yet? has $!composed; my $archetypes := Perl6::Metamodel::Archetypes.new( :nominal(1), :composable(1) ); method archetypes($obj?) { $archetypes } method new(*%named) { nqp::findmethod(NQPMu, 'BUILDALL')(nqp::create(self), %named) } my class Collision { has $!name; has @!roles; has $!private; has $!multi; method name() { $!name } method roles() { @!roles } method private() { $!private } method multi() { $!multi } } method new_type(:@roles, :$name = '', :$ver, :$auth, :$repr, :$api) { my $metarole := self.new(:roles(@roles)); my $obj := nqp::settypehll(nqp::newtype($metarole, 'Uninstantiable'), 'Raku'); $metarole.set_name($obj, $name); $metarole.set_ver($obj, $ver); $metarole.set_auth($obj, $auth) if $auth; $metarole.set_api($obj, $api) if $api; $obj; } method add_collision($obj, $colliding_name, @role_names, :$private = 0, :$multi) { @!collisions[+@!collisions] := Collision.new( :name($colliding_name), :roles(@role_names), :$private, :$multi ); } method compose($the-obj) { my $obj := nqp::decont($the-obj); Perl6::Metamodel::Configuration.role_to_role_applier_type.apply($obj, self.roles_to_compose($obj)); for self.roles_to_compose($obj) { @!role_typecheck_list[+@!role_typecheck_list] := $_; for $_.HOW.role_typecheck_list($_) { @!role_typecheck_list[+@!role_typecheck_list] := $_; } } for @!roles { @!role_typecheck_list[+@!role_typecheck_list] := $_; for $_.HOW.role_typecheck_list($_) { @!role_typecheck_list[+@!role_typecheck_list] := $_; } } self.publish_type_cache($obj); $!composed := 1; $obj } method is_composed($obj) { $!composed ?? 1 !! 0 } method collisions($obj) { @!collisions } # It makes sense for concretizations to default to MRO order of roles. method roles($obj, :$transitive = 1, :$mro = 1) { $transitive ?? self.roles-ordered($obj, @!roles, :transitive, :$mro) !! @!roles } method add_to_role_typecheck_list($obj, $type) { @!role_typecheck_list[+@!role_typecheck_list] := $type; } method role_typecheck_list($obj) { @!role_typecheck_list } method type_check($obj, $checkee) { my $decont := nqp::decont($checkee); if $decont =:= $obj.WHAT { return 1; } for @!role_typecheck_list { if nqp::decont($_) =:= $decont { return 1; } } 0 } method publish_type_cache($obj) { my @types := [$obj.WHAT]; for @!role_typecheck_list { @types.push($_) } nqp::settypecache($obj, @types) } method mro($obj, :$roles, :$concretizations, :$unhidden) { [$obj] } method find_method_qualified($obj, $qtype, $name) { $obj := nqp::decont($obj); $qtype := nqp::decont($qtype); if $qtype.HOW.archetypes.parametric { my $found-role := nqp::null(); for self.concretizations($obj, :transitive) { my $candidate := $_; my $role := $_.HOW.roles($_, :!transitive, :!mro)[0]; if nqp::can($role.HOW, 'group') { $role := $role.HOW.group($role); } if $qtype =:= $role { # XXX Better be replaced with Exception throwing. The mechanizm could be provided via # Perl6::Metamodel::Configuration where a property could be set pointing to a Raku object. # It could be something like: # Perl6::Metamodel::Configuration.throw("nqp::die message", ['X', 'Method', 'Ambiguous'], |%exception-params); nqp::die("Ambiguous concretization lookup for " ~ $qtype.HOW.name($qtype)) unless nqp::isnull($found-role); $found-role := $candidate; } } nqp::isnull($found-role) ?? nqp::null() !! $found-role.HOW.method_table($found-role){$name} || $found-role.HOW.submethod_table($found-role){$name} || nqp::null() } elsif nqp::istype($obj, $qtype) { # Non-parametric, so just locate it from the already concrete type. nqp::findmethod($qtype, $name) } else { nqp::null() } } method is-implementation-detail($obj) { @!roles[0].is-implementation-detail($obj) } } #line 1 src/Perl6/Metamodel/CurriedRoleHOW.nqp # Sometimes, we see references to roles that provide parameters but # do not fully resolve them. For example, in: # # class C does R[T] { } # # We need to represent R[T], but we cannot yet fully specialize the # role because we don't have the first parameter to hand. We may also # run into the issue where we have things like: # # sub foo(R[T] $x) { ... } # if $x ~~ R[T] { ... } # # Where we clearly want to talk about a partial parameterization of a # role and actually want to do so in a way distinct from a particular # instantiation of it. This meta-object represents those "partial types" # as both a way to curry on your way to a full specialization, but also # as a way to do type-checking or punning. class Perl6::Metamodel::CurriedRoleHOW does Perl6::Metamodel::RolePunning does Perl6::Metamodel::TypePretense does Perl6::Metamodel::Naming does Perl6::Metamodel::RoleContainer does Perl6::Metamodel::LanguageRevision does Perl6::Metamodel::InvocationProtocol { has $!curried_role; has $!candidate; # Will contain matching candidate from curried role group has @!pos_args; has %!named_args; has @!role_typecheck_list; has @!parent_typecheck_list; # Only for parents instantiated from generics has $!is_complete; has $!archetypes; my $archetypes_g := Perl6::Metamodel::Archetypes.new( :composable(1), :inheritalizable(1), :parametric(1), :generic(1) ); my $archetypes_ng := Perl6::Metamodel::Archetypes.new( :nominal(1), :composable(1), :inheritalizable(1), :parametric(1) ); method !choose_archetype() { for @!pos_args { if $_.HOW.archetypes($_).generic { return $archetypes_g; } } for %!named_args { if $_.value.HOW.archetypes($_.value).generic { return $archetypes_g; } } $archetypes_ng } method archetypes($obj?) { if nqp::isconcrete(self) { $!archetypes := self.'!choose_archetype'() unless $!archetypes; return $!archetypes; } $archetypes_ng } method new(*%named) { nqp::findmethod(NQPMu, 'BUILDALL')(nqp::create(self), %named) } method new_type($curried_role, *@pos_args, *%named_args) { # construct a name my $name := $curried_role.HOW.name($curried_role); if @pos_args { my @pieces := nqp::list_s(); for @pos_args { nqp::push_s(@pieces, $_.HOW.name($_)); } $name := $name ~ "[" ~ nqp::join(",", @pieces) ~ "]"; } my $meta := self.new(:curried_role($curried_role), :pos_args(@pos_args), :named_args(%named_args), :name($name)); my $type := nqp::settypehll(nqp::newtype($meta, 'Uninstantiable'), 'Raku'); $meta.set_name($type, $name); nqp::settypecheckmode($type, 2); } method parameterize_roles($obj) { my @pos_args; nqp::push(@pos_args, $obj); for @!pos_args { nqp::push(@pos_args, $_); } if nqp::istype($!curried_role.HOW, Perl6::Metamodel::ParametricRoleGroupHOW) { $!candidate := $!curried_role.HOW.select_candidate($!curried_role, @pos_args, %!named_args); my $candidate-how := $!candidate.HOW; self.set_language_revision($obj, $candidate-how.language_revision($!candidate)); my $type_env; try { my @result := $candidate-how.body_block($!candidate)(|@pos_args, |%!named_args); $type_env := @result[1]; } for $candidate-how.roles($!candidate, :!transitive) -> $role { if $role.HOW.archetypes.generic && $type_env { $role := $role.HOW.instantiate_generic($role, $type_env); } unless $role.HOW.archetypes.generic || $role.HOW.archetypes.parametric { my $target-name := $obj.HOW.name($obj); my $role-name := $role.HOW.name($role); Perl6::Metamodel::Configuration.throw_or_die( 'X::Composition::NotComposable', $role-name ~ " is not composable, so " ~ $target-name ~ " cannot compose it", :$target-name, composer => $role, ) } self.add_role($obj, $role); } # Contrary to roles, we only consider generic parents. I.e. cases like: # role R[::T] is T {} if $type_env { for $candidate-how.parents($!candidate, :local) -> $parent { if $parent.HOW.archetypes.generic { my $ins := $parent.HOW.instantiate_generic($parent, $type_env); nqp::push(@!parent_typecheck_list, $ins) } } } } self.update_role_typecheck_list($obj); } method update_role_typecheck_list($obj) { my @rtl; nqp::push(@rtl, $!curried_role); # XXX Not sure if it makes sense adding roles from group into the type checking. # for $!curried_role.HOW.role_typecheck_list($obj) { # nqp::push(@rtl, $_); # } for self.roles_to_compose($obj) -> $role { my $how := $role.HOW; if $how.archetypes.composable() || $how.archetypes.composalizable() { nqp::push(@rtl, $role); for $how.role_typecheck_list($role) { nqp::push(@rtl, $_); } } } @!role_typecheck_list := @rtl; } method complete_parameterization($obj) { unless $!is_complete { $!is_complete := 1; self.parameterize_roles($obj); self.update_role_typecheck_list($obj); } } method instantiate_generic($obj, $type_env) { my @new_pos; my %new_named; for @!pos_args { @new_pos.push($_.HOW.archetypes($_).generic ?? $_.HOW.instantiate_generic($_, $type_env) !! $_); } for %!named_args { %new_named{$_.key} := $_.value.HOW.archetypes($_.value).generic ?? $_.value.HOW.instantiate_generic($_.value, $type_env) !! $_.value; } self.new_type($!curried_role, |@new_pos, |%new_named) } method specialize($obj, $first_arg) { $!curried_role.HOW.specialize($!curried_role, $first_arg, |@!pos_args, |%!named_args); } method curried_role($obj) { $!curried_role } method role_arguments($obj) { @!pos_args } method roles($obj, :$transitive = 1, :$mro = 0) { self.complete_parameterization($obj); self.roles-ordered($obj, self.roles_to_compose($obj), :$transitive, :$mro) } method role_typecheck_list($obj) { self.complete_parameterization($obj); @!role_typecheck_list } method type_check($obj, $checkee) { my $decont := nqp::decont($checkee); if $decont =:= $obj.WHAT { return 1; } if $decont =:= $!curried_role { return 1; } for self.pretending_to_be() { if $decont =:= nqp::decont($_) { return 1; } } self.complete_parameterization($obj) unless $!is_complete; if !($!candidate =:= NQPMu) && $!candidate.HOW.type_check_parents($!candidate, $decont) { return 1 } for @!parent_typecheck_list -> $parent { if nqp::istype($decont, $parent) { return 1 } } for @!role_typecheck_list { my $dr := nqp::decont($_); if $decont =:= $dr { return 1; } if nqp::istype($dr, $decont) { return 1; } } 0 } method accepts_type($obj, $checkee) { # First, we locate candidate curryings to check against. If # the checkee is itself a curried role, it also goes in. Note # that we only want those that have the same parametric role # as us. my @cands; my $crdc := nqp::decont($!curried_role); if nqp::istype($checkee.HOW, self.WHAT) { if nqp::decont($checkee.HOW.curried_role($checkee)) =:= $crdc { @cands.push($checkee); } } if nqp::can($checkee.HOW, 'role_typecheck_list') { for $checkee.HOW.role_typecheck_list($checkee) { if nqp::istype($_.HOW, self.WHAT) && !$_.HOW.archetypes($_).generic { if nqp::decont($_.HOW.curried_role($_)) =:= $crdc { @cands.push($_); } } } } # Provided we have some candidates, check the arguments. my int $num_args := +@!pos_args; if @cands { for @cands { my @try_args := $_.HOW.role_arguments($_); if +@try_args == $num_args { my int $i := -1; my int $ok := 1; while ($i := $i + 1) < $num_args { unless nqp::eqaddr(nqp::decont(@!pos_args[$i]), nqp::decont(@try_args[$i])) || @!pos_args[$i].ACCEPTS(@try_args[$i]) { $ok := 0; $i := $num_args; } } if $ok { return 1; } } } } 0; } method shortname($curried_role) { $curried_role.HOW.name($curried_role); } method is-implementation-detail($obj) { $!curried_role.is-implementation-detail($obj) } } #line 1 src/Perl6/Metamodel/ParametricRoleHOW.nqp my $concrete := Perl6::Metamodel::ConcreteRoleHOW; my $currier := Perl6::Metamodel::CurriedRoleHOW; class Perl6::Metamodel::ParametricRoleHOW does Perl6::Metamodel::Naming does Perl6::Metamodel::Documenting does Perl6::Metamodel::LanguageRevision does Perl6::Metamodel::MethodContainer does Perl6::Metamodel::PrivateMethodContainer does Perl6::Metamodel::MultiMethodContainer does Perl6::Metamodel::AttributeContainer does Perl6::Metamodel::RoleContainer does Perl6::Metamodel::MultipleInheritance does Perl6::Metamodel::Stashing does Perl6::Metamodel::TypePretense does Perl6::Metamodel::RolePunning does Perl6::Metamodel::ArrayType does Perl6::Metamodel::InvocationProtocol { has $!composed; has $!body_block; has $!in_group; has $!group; has $!signatured; has @!role_typecheck_list; has $!specialize_lock; my $archetypes := Perl6::Metamodel::Archetypes.new( :nominal(1), :composable(1), :inheritalizable(1), :parametric(1) ); method archetypes($obj?) { $archetypes } method new(*%named) { nqp::findmethod(NQPMu, 'BUILDALL')(nqp::create(self), %named) } my $anon_id := 1; method new_type(:$name, :$ver, :$auth, :$api, :$repr, :$signatured, *%extra) { my $metarole := self.new(:signatured($signatured), :specialize_lock(NQPLock.new)); my $type := nqp::settypehll(nqp::newtype($metarole, 'Uninstantiable'), 'Raku'); $metarole.set_name($type, $name // ""); $metarole.set_ver($type, $ver); $metarole.set_auth($type, $auth) if $auth; $metarole.set_api($type, $api) if $api; $metarole.set_pun_repr($type, $repr) if $repr; if nqp::existskey(%extra, 'group') { $metarole.set_group($type, %extra); } self.add_stash($type); } method parameterize($obj, *@pos_args, *%named_args) { $currier.new_type($obj, |@pos_args, |%named_args) } method set_body_block($obj, $block) { $!body_block := $block } method body_block($obj) { $!body_block } method signatured($obj) { $!signatured } method set_group($obj, $group) { $!group := $group; $!in_group := 1; } method group($obj) { $!in_group ?? $!group !! $obj } method compose($the-obj, :$compiler_services) { my $obj := nqp::decont($the-obj); self.set_language_version($obj); my @rtl; if $!in_group { @rtl.push($!group); } for self.roles_to_compose($obj) { my $how := $_.HOW; if $how.archetypes.composable || $how.archetypes.composalizable { @rtl.push($_); for $_.HOW.role_typecheck_list($_) { @rtl.push($_); } } } @!role_typecheck_list := @rtl; $!composed := 1; $obj } method is_composed($obj) { $!composed } method roles($obj, :$transitive = 1, :$mro) { self.roles-ordered($obj, self.roles_to_compose($obj), :$transitive, :$mro); } method role_typecheck_list($obj) { @!role_typecheck_list } # $checkee must always be decont'ed method type_check_parents($obj, $checkee) { for self.parents($obj, :local) -> $parent { if nqp::istype($parent, $checkee) { return 1; } } 0 } method type_check($obj, $checkee) { my $decont := nqp::decont($checkee); if $decont =:= $obj.WHAT { return 1; } if $!in_group && $decont =:= $!group { return 1; } for self.pretending_to_be() { if $decont =:= nqp::decont($_) { return 1; } } for self.roles_to_compose($obj) { if nqp::istype($decont, $_) { return 1; } } self.type_check_parents($obj, $decont); } method specialize($obj, *@pos_args, *%named_args) { # We only allow one specialization of a role to take place at a time, # since the body block captures the methods into its lexical scope, # but we don't do the appropriate cloning until a bit later. These # must happen before another specialize happens and re-captures the # things we are composing. $!specialize_lock.protect({ my $class := @pos_args[0]; my $conc := nqp::if(nqp::can($class.HOW, 'get_cached_conc'), $class.HOW.get_cached_conc($class, $obj, @pos_args, %named_args), nqp::null()); if (nqp::isnull($conc)) { # Pre-create a concrete role. We'll finalize it later, in specialize_with method. But for now we need it # to initialize $?CONCRETIZATION by role's body block. my $*MOP-ROLE-CONCRETIZATION := $conc := $concrete.new_type(:roles([$obj]), :name(self.name($obj))); $conc.HOW.set_language_revision($conc, $obj.HOW.language_revision($obj)); $conc.HOW.set_hidden($conc) if $obj.HOW.hidden($obj); # Run the body block to get the type environment (we know # the role in this case). my $type_env; my $error; try { my @result := $!body_block(|@pos_args, |%named_args); $type_env := @result[1]; CATCH { $error := $! } } if $error { my $exception := nqp::getpayload($error); Perl6::Metamodel::Configuration.throw_or_die( 'X::Role::Instantiation', "Could not instantiate role '" ~ self.name($obj) ~ "':\n" ~ ($exception || nqp::getmessage($error)), :role($obj), :$exception ) } # Use it to build a concrete role. $conc := self.specialize_with($obj, $conc, $type_env, @pos_args); nqp::if( nqp::can($class.HOW, 'add_conc_to_cache'), $class.HOW.add_conc_to_cache($class, $obj, @pos_args, %named_args, $conc) ); } $conc }) } method specialize_with($obj, $conc, $type_env, @pos_args) { # Go through attributes, reifying as needed and adding to # the concrete role. for self.attributes($obj, :local(1)) { $conc.HOW.add_attribute($conc, $_.is_generic ?? $_.instantiate_generic($type_env) !! nqp::clone($_)); } # Go through methods and instantiate them; we always do this # unconditionally, since we need the clone anyway. my @methods := nqp::hllize(self.method_order($obj)); my @method_names := nqp::hllize(self.method_names($obj)); my $method_iterator := nqp::iterator(@methods); for @method_names -> $name { $conc.HOW.add_method($conc, $name, nqp::shift($method_iterator).instantiate_generic($type_env)) } my %private_methods := nqp::hllize(self.private_method_table($obj)); my @private_methods := nqp::hllize(self.private_method_names($obj)); for @private_methods -> $name { $conc.HOW.add_private_method($conc, $name, %private_methods{$name}.instantiate_generic($type_env)); } for self.multi_methods_to_incorporate($obj) { $conc.HOW.add_multi_method($conc, $_.name, $_.code.instantiate_generic($type_env)) } # Roles done by this role need fully specializing also. for self.roles_to_compose($obj) { my $ins := my $r := $_; if $_.HOW.archetypes($_).generic { $ins := $ins.HOW.instantiate_generic($ins, $type_env); unless $ins.HOW.archetypes.parametric { my $target-name := $obj.HOW.name($obj); my $role-name := $ins.HOW.name($ins); Perl6::Metamodel::Configuration.throw_or_die( 'X::Composition::NotComposable', $role-name ~ " is not composable, so " ~ $target-name ~ " cannot compose it", :$target-name, composer => $ins, ) } $conc.HOW.add_to_role_typecheck_list($conc, $ins); } $ins := $ins.HOW.specialize($ins, @pos_args[0]); $conc.HOW.add_role($conc, $ins); $conc.HOW.add_concretization($conc, $r, $ins); } # Pass along any parents that have been added, resolving them in # the case they're generic (role Foo[::T] is T { }) for self.parents($obj, :local(1)) { my $p := $_; if $p.HOW.archetypes($p).generic { $p := $p.HOW.instantiate_generic($p, $type_env); } $conc.HOW.add_parent($conc, $p, :hides(self.hides_parent($obj, $_))); } # Resolve any array type being passed along (only really used in the # punning case, since roles are the way we get generic types). if self.is_array_type($obj) { my $at := self.array_type($obj); if $at.HOW.archetypes($at).generic { $at := $at.HOW.instantiate_generic($at, $type_env); } $conc.HOW.set_array_type($conc, $at); } $conc.HOW.compose($conc); return $conc; } method mro($obj, :$roles, :$concretizations, :$unhidden) { [$obj] } } #line 1 src/Perl6/Metamodel/ParametricRoleGroupHOW.nqp # This represents a group of parametric roles. For example, given # we have the declarations: # # role Foo[] { } # (which is same as role Foo { }) # role Foo[::T] { } # role Foo[::T1, ::T2] { } # # Each of them results in a type object that has a HOW of type # Perl6::Metamodel::ParametricRoleHOW. In here, we keep the whole # group of those, and know how to specialize to a certain parameter # list by multi-dispatching over the set of candidates to pick # a particular candidate. class Perl6::Metamodel::ParametricRoleGroupHOW does Perl6::Metamodel::Naming does Perl6::Metamodel::Stashing does Perl6::Metamodel::TypePretense does Perl6::Metamodel::RolePunning does Perl6::Metamodel::BoolificationProtocol does Perl6::Metamodel::InvocationProtocol { has @!candidates; has $!selector; has @!role_typecheck_list; has @!nonsignatured; my $archetypes := Perl6::Metamodel::Archetypes.new( :nominal(1), :composable(1), :inheritalizable(1), :parametric(1) ); method archetypes($obj?) { $archetypes } method new(*%named) { nqp::findmethod(NQPMu, 'BUILDALL')(nqp::create(self), %named) } my $selector_creator; method set_selector_creator($sc) { $selector_creator := $sc; } method new_type(:$name!, :$repr) { # Build and configure the type's basic details. my $meta := self.new(:selector($selector_creator())); my $type_obj := nqp::settypehll(nqp::newtype($meta, 'Uninstantiable'), 'Raku'); $meta.set_name($type_obj, $name); $meta.set_pun_repr($meta, $repr) if $repr; $meta.set_boolification_mode($type_obj, 5); $meta.publish_boolification_spec($type_obj); $meta.publish_type_cache($type_obj); self.add_stash($type_obj); # We use 6model parametrics to make this a parametric type on the # arguments we curry with. This means we'll make the curries unique. nqp::setparameterizer($type_obj, sub ($type, @packed) { $type.HOW.'!produce_parameterization'($type, @packed); }); $type_obj } # We only take positional args into account for the parametric key. If # there are no nameds, we push this class in place of them so as to make # an otherwise equal key always the same, and named args make it always # unequal. my class NO_NAMEDS { } method parameterize($obj, *@args, *%named_args) { my int $n := nqp::elems(@args); my int $i := -1; while ++$i < $n { @args[$i] := nqp::decont(@args[$i]); } nqp::push(@args, %named_args || NO_NAMEDS); nqp::parameterizetype($obj, @args); } method !produce_parameterization($obj, @packed) { my @args := nqp::clone(@packed); my $maybe_nameds := nqp::pop(@args); my $curried; if $maybe_nameds { my %nameds := $maybe_nameds; $curried := $currier.new_type($obj, |@args, |%nameds); } else { $curried := $currier.new_type($obj, |@args); } $curried.HOW.set_pun_repr($curried, self.pun_repr($obj)); $curried } method add_possibility($obj, $possible) { @!candidates[+@!candidates] := $possible; nqp::push(@!nonsignatured, nqp::decont($possible)) unless $possible.HOW.signatured($possible); $!selector.add_dispatchee($possible.HOW.body_block($possible)); self.update_role_typecheck_list($obj); } method select_candidate($obj, @pos_args, %named_args) { # Use multi-dispatcher to pick the body block of the best role. my $error; my $selected_body; try { sub try_select(*@pos, *%named) { $!selector.find_best_dispatchee(nqp::usecapture(), 0) } $selected_body := try_select(|@pos_args, |%named_args); CATCH { $error := $! } } if $error { my $payload := nqp::getpayload($error); my $hint := nqp::getmessage($error) || (nqp::defined($payload) ?? $payload.message !! ""); Perl6::Metamodel::Configuration.throw_or_die( 'X::Role::Parametric::NoSuchCandidate', "Could not find an appropriate parametric role variant for '" ~ $obj.HOW.name($obj) ~ "' using the arguments supplied:\n " ~ $hint , :role($obj), :$hint ); } # Locate the role that has that body block. my $selected := NQPMu; for @!candidates { if $_.HOW.body_block($_) =:= $selected_body { $selected := $_; last; } } if $selected =:= NQPMu { nqp::die("Internal error: could not resolve body block to role candidate"); } $selected } method specialize($obj, *@pos_args, *%named_args) { my $selected := self.select_candidate($obj, @pos_args, %named_args); # Having picked the appropriate one, specialize it. $selected.HOW.specialize($selected, |@pos_args, |%named_args); } method update_role_typecheck_list($obj) { my $ns := self.'!get_nonsignatured_candidate'(); @!role_typecheck_list := $ns.HOW.role_typecheck_list($ns) unless nqp::isnull($ns); } method role_typecheck_list($obj) { @!role_typecheck_list } method type_check($obj, $checkee) { my $decont := nqp::decont($checkee); if $decont =:= $obj.WHAT { return 1; } for self.pretending_to_be() { if $decont =:= nqp::decont($_) { return 1; } } for @!role_typecheck_list { if $decont =:= nqp::decont($_) { return 1; } } my $ns := self.'!get_nonsignatured_candidate'(); return $ns.HOW.type_check_parents($ns, $decont) unless nqp::isnull($ns); 0; } method candidates($obj) { nqp::clone(@!candidates) } method lookup($obj, $name) { nqp::isnull(my $c := self.'!get_default_candidate'()) ?? [] !! $c.HOW.lookup($c, $name); } method methods($obj, *@pos, *%name) { nqp::isnull(my $c := self.'!get_default_candidate'()) ?? [] !! $c.HOW.methods($c, |@pos, |%name); } method attributes($obj, *@pos, *%name) { nqp::isnull(my $c := self.'!get_default_candidate'()) ?? [] !! $c.HOW.attributes($c, |@pos, |%name); } method parents($obj, *%named) { nqp::isnull(my $c := self.'!get_default_candidate'()) ?? [] !! $c.HOW.parents($c, |%named) } method roles($obj, *%named) { nqp::isnull(my $c := self.'!get_default_candidate'()) ?? [] !! $c.HOW.roles($c, |%named) } method ver($obj) { nqp::isnull(my $c := self.'!get_default_candidate'()) ?? nqp::null() !! $c.HOW.ver($c) } method auth($obj) { nqp::isnull(my $c := self.'!get_default_candidate'()) ?? nqp::null() !! $c.HOW.auth($c) } # See Perl6::Metamodel::LanguageRevision role comments about the difference between these two language revision # methods. method language-revision($obj) { nqp::isnull(my $c := self.'!get_default_candidate'()) ?? nqp::null() !! $c.HOW.language-revision($c) } method language_revision($obj) { nqp::isnull(my $c := self.'!get_default_candidate'()) ?? nqp::null() !! $c.HOW.language_revision($c) } method is-implementation-detail($obj) { nqp::isnull(my $c := self.'!get_default_candidate'()) ?? nqp::null() !! $c.HOW.is-implementation-detail($c) } method WHY() { nqp::isnull(my $c := self.'!get_default_candidate'()) ?? nqp::null() !! $c.HOW.WHY } method set_why($why) { Perl6::Metamodel::Configuration.throw_or_die( 'X::Role::Group::Documenting', "Parametric role group cannot be documented, use one of the candidates instead for '" ~ self.name ~ "'", :role-name(self.name) ); } method !get_default_candidate() { nqp::isnull(my $c := self.'!get_nonsignatured_candidate'()) ?? nqp::null() !! $c } method !get_nonsignatured_candidate() { +@!nonsignatured ?? @!nonsignatured[0] !! nqp::null() } method publish_type_cache($obj) { # We can at least include ourself and the types a role pretends to be. my @tc := nqp::clone(self.pretending_to_be()); nqp::push(@tc, $obj.WHAT); nqp::settypecache($obj, @tc); nqp::settypecheckmode($obj, 1); } } #line 1 src/Perl6/Metamodel/RoleToClassApplier.nqp my class RoleToClassApplier { has $!target; has $!to_compose; has $!to_compose_meta; has @!roles; sub has_method($target, $name, $local) { if $local { my %mt := nqp::hllize($target.HOW.method_table($target)); return 1 if nqp::existskey(%mt, $name); %mt := nqp::hllize($target.HOW.submethod_table($target)); return nqp::existskey(%mt, $name); } else { for $target.HOW.mro($target) { my %mt := nqp::hllize($_.HOW.method_table($_)); if nqp::existskey(%mt, $name) { return 1; } %mt := nqp::hllize($_.HOW.submethod_table($_)); if nqp::existskey(%mt, $name) { return 1; } } return 0; } } sub has_private_method($target, $name) { my %pmt := nqp::hllize($target.HOW.private_method_table($target)); return nqp::existskey(%pmt, $name) } method prepare($target, @roles) { $!target := $target; @!roles := @roles; # If we have many things to compose, then get them into a single helper # role first. if +@roles == 1 { $!to_compose := @roles[0]; $!to_compose_meta := $!to_compose.HOW; } else { $!to_compose := $concrete.new_type(); $!to_compose_meta := $!to_compose.HOW; $!to_compose_meta.set_language_revision($!to_compose, $target.HOW.language_revision($target)); for @roles { $!to_compose_meta.add_role($!to_compose, $_); } $!to_compose := $!to_compose_meta.compose($!to_compose); } # Collisions? my @collisions := $!to_compose_meta.collisions($!to_compose); for @collisions { if $_.private { unless has_private_method($target, $_.name) { Perl6::Metamodel::Configuration.throw_or_die( 'X::Role::Unresolved::Private', "Private method '" ~ $_.name ~ "' must be resolved by class " ~ $target.HOW.name($target) ~ " because it exists in multiple roles (" ~ nqp::join(", ", $_.roles) ~ ")", :method($_), :$target, ) } } elsif nqp::isconcrete($_.multi) { my $match := 0; for $target.HOW.multi_methods_to_incorporate($target) -> $maybe { if $_.name eq $maybe.name && Perl6::Metamodel::Configuration.compare_multi_sigs($_.multi, $maybe.code) { $match := 1; last; } } unless $match { Perl6::Metamodel::Configuration.throw_or_die( 'X::Role::Unresolved::Multi', "Multi method '" ~ $_.name ~ "' with signature " ~ $_.multi.signature.raku ~ " must be resolved by class " ~ $target.HOW.name($target) ~ " because it exists in multiple roles (" ~ nqp::join(", ", $_.roles) ~ ")", :method($_), :$target ) } } else { unless has_method($target, $_.name, 1) { Perl6::Metamodel::Configuration.throw_or_die( 'X::Role::Unresolved::Method', "Method '" ~ $_.name ~ "' must be resolved by class " ~ $target.HOW.name($target) ~ " because it exists in multiple roles (" ~ nqp::join(", ", $_.roles) ~ ")", :method($_), :$target ) } } } } method apply() { my @stubs; # Only transfer submethods from pre-6.e roles into pre-6.e classes. my $with_submethods := $!target.HOW.language_revision($!target) < 3 && (!nqp::istype($!to_compose_meta, Perl6::Metamodel::LanguageRevision) || $!to_compose.HOW.language_revision($!to_compose) < 3); # Compose in any methods. sub compose_method_table(@methods, @method_names) { my $method_iterator := nqp::iterator(@methods); for @method_names -> str $name { my $method := nqp::shift($method_iterator); my $yada := 0; try { $yada := $method.yada } if $yada { unless has_method($!target, $name, 0) || $!target.HOW.has_public_attribute($!target, $name) { my @needed; for @!roles { for nqp::hllize($_.HOW.method_table($_)) -> $m { if $m.key eq $name { nqp::push(@needed, $_.HOW.name($_)); } } } nqp::push(@stubs, nqp::hash('name', $name, 'needed', @needed, 'target', $!target)); } } elsif !has_method($!target, $name, 1) && ($with_submethods || !nqp::istype($method, Perl6::Metamodel::Configuration.submethod_type)) { $!target.HOW.add_method($!target, $name, $method); } } } my @methods := $!to_compose_meta.method_order($!to_compose); my @method_names := $!to_compose_meta.method_names($!to_compose); compose_method_table( nqp::hllize(@methods), nqp::hllize(@method_names), ); if nqp::can($!to_compose_meta, 'private_method_table') { my @private_methods := nqp::hllize($!to_compose_meta.private_methods($!to_compose)); my @private_method_names := nqp::hllize($!to_compose_meta.private_method_names($!to_compose)); my $i := 0; for @private_method_names -> str $name { unless has_private_method($!target, $name) { $!target.HOW.add_private_method($!target, $name, @private_methods[$i]); } $i++; } } # Compose in any multi-methods, looking for any requirements and # ensuring they are met. if nqp::can($!to_compose_meta, 'multi_methods_to_incorporate') { my @multis := $!to_compose_meta.multi_methods_to_incorporate($!to_compose); my @required; for @multis -> $add { my $yada := 0; try { $yada := $add.code.yada } if $yada { nqp::push(@required, $add); } else { my $already := 0; for $!target.HOW.multi_methods_to_incorporate($!target) -> $existing { if $existing.name eq $add.name { if Perl6::Metamodel::Configuration.compare_multi_sigs($existing.code, $add.code) { $already := 1; last; } } } unless $already { $!target.HOW.add_multi_method($!target, $add.name, $add.code); } } for @required -> $req { my $satisfaction := 0; for $!target.HOW.multi_methods_to_incorporate($!target) -> $existing { if $existing.name eq $req.name { if Perl6::Metamodel::Configuration.compare_multi_sigs($existing.code, $req.code) { $satisfaction := 1; last; } } } unless $satisfaction { my $name := $req.name; my $sig := $req.code.signature.raku; Perl6::Metamodel::Configuration.throw_or_die( 'X::Role::Unimplemented::Multi', "Multi method '$name' with signature $sig must be implemented by " ~ $!target.HOW.name($!target) ~ " because it is required by a role", :method($req), :target($!target) ) } } } } # Compose in any role attributes. my @attributes := $!to_compose_meta.attributes($!to_compose, :local(1)); for @attributes { if $!target.HOW.has_attribute($!target, $_.name) { Perl6::Metamodel::Configuration.throw_or_die( 'X::Role::Attribute::Exists', "Attribute '" ~ $_.name ~ "' already exists in the class '" ~ $!target.HOW.name($!target) ~ "', but a role also wishes to compose it", :target($!target), :attribute($_) ) } $!target.HOW.add_attribute($!target, $_); } # Compose in any parents. if nqp::can($!to_compose_meta, 'parents') { my @parents := $!to_compose_meta.parents($!to_compose, :local(1)); for @parents { $!target.HOW.add_parent($!target, $_, :hides($!to_compose_meta.hides_parent($!to_compose, $_))); } } # Copy any array_type. if nqp::can($!target.HOW, 'is_array_type') && !$!target.HOW.is_array_type($!target) { if nqp::can($!to_compose_meta, 'is_array_type') { if $!to_compose_meta.is_array_type($!to_compose) { $!target.HOW.set_array_type($!target, $!to_compose_meta.array_type($!to_compose)); } } } @stubs; } Perl6::Metamodel::Configuration.set_role_to_class_applier_type(RoleToClassApplier); } #line 1 src/Perl6/Metamodel/ClassHOW.nqp class Perl6::Metamodel::ClassHOW does Perl6::Metamodel::Naming does Perl6::Metamodel::Documenting does Perl6::Metamodel::LanguageRevision does Perl6::Metamodel::Stashing does Perl6::Metamodel::AttributeContainer does Perl6::Metamodel::MethodContainer does Perl6::Metamodel::PrivateMethodContainer does Perl6::Metamodel::MultiMethodContainer does Perl6::Metamodel::MetaMethodContainer does Perl6::Metamodel::RoleContainer does Perl6::Metamodel::MultipleInheritance does Perl6::Metamodel::DefaultParent does Perl6::Metamodel::C3MRO does Perl6::Metamodel::MROBasedMethodDispatch does Perl6::Metamodel::MROBasedTypeChecking does Perl6::Metamodel::Trusting does Perl6::Metamodel::BUILDPLAN does Perl6::Metamodel::Mixins does Perl6::Metamodel::ArrayType does Perl6::Metamodel::BoolificationProtocol does Perl6::Metamodel::REPRComposeProtocol does Perl6::Metamodel::InvocationProtocol does Perl6::Metamodel::ContainerSpecProtocol does Perl6::Metamodel::Finalization does Perl6::Metamodel::Concretization does Perl6::Metamodel::ConcretizationCache { has @!roles; has @!role_typecheck_list; has @!fallbacks; has $!composed; has $!is_pun; has $!pun_source; # If class is coming from a pun then this is the source role my $archetypes := Perl6::Metamodel::Archetypes.new( :nominal(1), :inheritable(1), :augmentable(1) ); method archetypes($obj?) { $archetypes } method new(*%named) { nqp::findmethod(NQPMu, 'BUILDALL')(nqp::create(self), %named) } my $id_lock := NQPLock.new; my $anon_id := 1; method new_type(:$name, :$repr = 'P6opaque', :$ver, :$auth, :$api, :$is_mixin) { my $metaclass := self.new(); my $new_type; if $is_mixin { $new_type := nqp::newmixintype($metaclass, $repr); } else { $new_type := nqp::newtype($metaclass, $repr); } my $obj := nqp::settypehll($new_type, 'Raku'); $metaclass.set_name($obj, $name // ""); self.add_stash($obj); $metaclass.set_ver($obj, $ver) if $ver; $metaclass.set_auth($obj, $auth) if $auth; $metaclass.set_api($obj, $api) if $api; $metaclass.setup_mixin_cache($obj); nqp::setboolspec($obj, 5, nqp::null()); $obj } # Adds a new fallback for method dispatch. Expects the specified # condition to have been met (passes it the object and method name), # and if it is calls $calculator with the object and method name to # calculate an invokable object. method add_fallback($obj, $condition, $calculator) { # Add it. my %desc; %desc := $condition; %desc := $calculator; @!fallbacks[+@!fallbacks] := %desc; } sub has_method($target, $name) { for $target.HOW.mro($target) { my %mt := nqp::hllize($_.HOW.method_table($_)); if nqp::existskey(%mt, $name) { return 1; } %mt := nqp::hllize($_.HOW.submethod_table($_)); if nqp::existskey(%mt, $name) { return 1; } } return 0; } method compose($the-obj, :$compiler_services) { my $obj := nqp::decont($the-obj); self.set_language_version($obj); # Instantiate all of the roles we have (need to do this since # all roles are generic on ::?CLASS) and pass them to the # composer. my @roles_to_compose := self.roles_to_compose($obj); my @stubs; my $rtca; if @roles_to_compose { my @ins_roles; while @roles_to_compose { my $r := @roles_to_compose.pop(); @!roles[+@!roles] := $r; @!role_typecheck_list[+@!role_typecheck_list] := $r; my $ins := $r.HOW.specialize($r, $obj); # If class is a result of pun then transfer hidden flag from the source role if $!pun_source =:= $r { self.set_hidden($obj) if $ins.HOW.hidden($ins); self.set_language_revision($obj, $ins.HOW.language_revision($ins), :force); } @ins_roles.push($ins); self.add_concretization($obj, $r, $ins); } self.compute_mro($obj); # to the best of our knowledge, because the role applier wants it. $rtca := Perl6::Metamodel::Configuration.role_to_class_applier_type.new; $rtca.prepare($obj, @ins_roles); self.wipe_conc_cache; # Add them to the typecheck list, and pull in their # own type check lists also. for @ins_roles { @!role_typecheck_list[+@!role_typecheck_list] := $_; for $_.HOW.role_typecheck_list($_) { @!role_typecheck_list[+@!role_typecheck_list] := $_; } } } # Compose class attributes first. We prioritize them and their accessors over anything coming from roles. self.compose_attributes($obj, :$compiler_services); if $rtca { @stubs := $rtca.apply(); } # Some things we only do if we weren't already composed once, like # building the MRO. my $was_composed := $!composed; unless $!composed { if self.parents($obj, :local(1)) == 0 && self.has_default_parent_type && self.name($obj) ne 'Mu' { self.add_parent($obj, self.get_default_parent_type); } self.compute_mro($obj); $!composed := 1; } # Incorporate any new multi candidates (needs MRO built). self.incorporate_multi_candidates($obj); # Compose remaining attributes from roles. self.compose_attributes($obj, :$compiler_services); # Set up finalization as needed. self.setup_finalization($obj); # Test the remaining stubs for @stubs -> %data { if !has_method(%data, %data) { nqp::die("Method '" ~ %data ~ "' must be implemented by " ~ %data.HOW.name(%data) ~ " because it is required by roles: " ~ nqp::join(", ", %data) ~ "."); } } # See if we have a Bool method other than the one in the top type. # If not, all it does is check if we have the type object. unless self.get_boolification_mode($obj) != 0 { my $i := 0; my @mro := self.mro($obj); while $i < +@mro { my $ptype := @mro[$i]; last if nqp::existskey(nqp::hllize($ptype.HOW.method_table($ptype)), 'Bool'); last if nqp::can($ptype.HOW, 'submethod_table') && nqp::existskey(nqp::hllize($ptype.HOW.submethod_table($ptype)), 'Bool'); $i := $i + 1; } if $i + 1 == +@mro { self.set_boolification_mode($obj, 5) } } # If there's a FALLBACK method, register something to forward calls to it. my $FALLBACK := self.find_method($obj, 'FALLBACK', :no_fallback); if !nqp::isnull($FALLBACK) && nqp::defined($FALLBACK) { self.add_fallback($obj, sub ($obj, str $name) { $name ne 'sink' && $name ne 'CALL-ME' }, sub ($obj, str $name) { -> $inv, *@pos, *%named { $FALLBACK($inv, $name, |@pos, |%named) } }); } # This isn't an augment. unless $was_composed { # Create BUILDPLAN. self.create_BUILDPLAN($obj); # Attempt to auto-generate a BUILDALL method. We can # only auto-generate a BUILDALL method if we have compiler # services. If we don't, then BUILDALL will fall back to the # one in Mu, which will iterate over the BUILDALLPLAN. if nqp::isconcrete($compiler_services) { # Class does not appear to have a BUILDALL yet unless nqp::existskey(nqp::hllize($obj.HOW.submethod_table($obj)),'BUILDALL') || nqp::existskey(nqp::hllize($obj.HOW.method_table($obj)),'BUILDALL') { my $builder := nqp::findmethod( $compiler_services,'generate_buildplan_executor'); my $method := $builder($compiler_services,$obj,self.BUILDALLPLAN($obj)); # We have a generated BUILDALL submethod, so install! unless $method =:= NQPMu { $method.set_name('BUILDALL'); self.add_method($obj,'BUILDALL',$method); } } } # Compose the representation self.compose_repr($obj); } # Publish type and method caches. self.publish_type_cache($obj); self.publish_method_cache($obj); self.publish_boolification_spec($obj); self.publish_container_spec($obj); # Compose the meta-methods. self.compose_meta_methods($obj); $obj } method roles($obj, :$local, :$transitive = 1, :$mro = 0) { my @result := self.roles-ordered($obj, @!roles, :$transitive, :$mro); unless $local { my $first := 1; for self.mro($obj) { if $first { $first := 0; next; } for $_.HOW.roles($_, :$transitive, :$mro, :local(1)) { @result.push($_); } } } @result } method role_typecheck_list($obj) { $!composed ?? @!role_typecheck_list !! self.roles_to_compose($obj) } method is_composed($obj) { $!composed } # Stuff for junctiony dispatch fallback. my $junction_type; my $junction_autothreader; method setup_junction_fallback($type, $autothreader) { $junction_type := $type; $junction_autothreader := $autothreader; } # Handles the various dispatch fallback cases we have. method find_method_fallback($obj, $name, :$local = 0) { # If the object is a junction, need to do a junction dispatch. if nqp::istype($obj.WHAT, $junction_type) && $junction_autothreader { my $p6name := nqp::hllizefor($name, 'Raku'); return -> *@pos_args, *%named_args { $junction_autothreader($p6name, |@pos_args, |%named_args) }; } # Consider other fallbacks, if we have any. for @!fallbacks { if ($_)($obj, $name) { return ($_)($obj, $name); } } unless $local { my @mro := self.mro($obj); my $i := 0; while ++$i < +@mro { my $parent := @mro[$i]; if nqp::can($parent.HOW, 'find_method_fallback') && !nqp::isnull(my $fallback := $parent.HOW.find_method_fallback($obj, $name, :local)) { return $fallback } } } # Otherwise, didn't find anything. nqp::null() } # Does the type have any fallbacks? method has_fallbacks($obj, :$local = 0) { return 1 if nqp::istype($obj, $junction_type) || +@!fallbacks; unless $local { my $i := 0; my @mro := self.mro($obj); while ++$i < +@mro { my $parent := @mro[$i]; return 1 if nqp::can($parent.HOW, 'has_fallbacks') && $parent.HOW.has_fallbacks($obj, :local) } } 0 } method set_pun_source($obj, $role) { $!pun_source := nqp::decont($role); $!is_pun := 1; } method is_pun($obj) { $!is_pun } method pun_source($obj) { $!pun_source } } #line 1 src/Perl6/Metamodel/GrammarHOW.nqp class Perl6::Metamodel::GrammarHOW is Perl6::Metamodel::ClassHOW does Perl6::Metamodel::DefaultParent { } #line 1 src/Perl6/Metamodel/NativeHOW.nqp class Perl6::Metamodel::NativeHOW does Perl6::Metamodel::Naming does Perl6::Metamodel::Documenting does Perl6::Metamodel::Versioning does Perl6::Metamodel::Stashing does Perl6::Metamodel::MultipleInheritance does Perl6::Metamodel::C3MRO does Perl6::Metamodel::MROBasedMethodDispatch does Perl6::Metamodel::MROBasedTypeChecking { has $!nativesize; has int $!unsigned; has $!composed; my $archetypes := Perl6::Metamodel::Archetypes.new( :nominal(1) ); method archetypes($obj?) { $archetypes } method new(*%named) { nqp::findmethod(NQPMu, 'BUILDALL')(nqp::create(self), %named) } method new_type(:$name = '', :$repr = 'P6opaque', :$ver, :$auth, :$api) { my $metaclass := self.new(); my $obj := nqp::settypehll(nqp::newtype($metaclass, $repr), 'Raku'); $metaclass.set_name($obj, $name); $metaclass.set_ver($obj, $ver); $metaclass.set_auth($obj, $auth) if $auth; $metaclass.set_api($obj, $api) if $api; self.add_stash($obj); } method compose($the-obj, :$compiler_services) { my $obj := nqp::decont($the-obj); self.compute_mro($obj); self.publish_method_cache($obj); self.publish_type_cache($obj); if !$!composed && ($!nativesize || $!unsigned) { my $info := nqp::hash(); $info := nqp::hash(); $info := 1 if $!unsigned; $info := nqp::hash(); if nqp::objprimspec($!nativesize) { $info := $!nativesize; $info := $!nativesize; } else { if $!nativesize { $info := nqp::unbox_i($!nativesize); $info := nqp::unbox_i($!nativesize); } } nqp::composetype($obj, $info); } $!composed := 1; } method is_composed($obj) { $!composed } method set_ctype($obj, $ctype) { if $ctype eq 'char' { $!nativesize := nqp::const::C_TYPE_CHAR; } elsif $ctype eq 'short' { $!nativesize := nqp::const::C_TYPE_SHORT; } elsif $ctype eq 'int' { $!nativesize := nqp::const::C_TYPE_INT; } elsif $ctype eq 'long' { $!nativesize := nqp::const::C_TYPE_LONG; } elsif $ctype eq 'longlong' { $!nativesize := nqp::const::C_TYPE_LONGLONG; } elsif $ctype eq 'float' { $!nativesize := nqp::const::C_TYPE_FLOAT; } elsif $ctype eq 'double' { $!nativesize := nqp::const::C_TYPE_DOUBLE; } elsif $ctype eq 'longdouble' { $!nativesize := nqp::const::C_TYPE_LONGDOUBLE; } elsif $ctype eq 'bool' { $!nativesize := nqp::const::C_TYPE_BOOL; } elsif $ctype eq 'size_t' { $!nativesize := nqp::const::C_TYPE_SIZE_T; } elsif $ctype eq 'atomic' { $!nativesize := nqp::const::C_TYPE_ATOMIC_INT; } else { nqp::die("Unhandled C type '$ctype'") } } method set_nativesize($obj, $nativesize) { $!nativesize := $nativesize; } method nativesize($obj) { $!nativesize } method set_unsigned($obj, $unsigned) { $!unsigned := $unsigned ?? 1 !! 0 } method unsigned($obj) { $!unsigned } method method_table($obj) { nqp::hash('new', nqp::getstaticcode(sub (*@_, *%_) { nqp::die('Cannot instantiate a native type') })) } method submethod_table($obj) { nqp::hash() } } #line 1 src/Perl6/Metamodel/NativeRefHOW.nqp class Perl6::Metamodel::NativeRefHOW does Perl6::Metamodel::Naming does Perl6::Metamodel::Documenting does Perl6::Metamodel::Versioning does Perl6::Metamodel::Stashing does Perl6::Metamodel::MultipleInheritance does Perl6::Metamodel::C3MRO does Perl6::Metamodel::MROBasedMethodDispatch does Perl6::Metamodel::MROBasedTypeChecking { has $!type; has $!refkind; has $!composed; has $!repr_composed; my $archetypes := Perl6::Metamodel::Archetypes.new( :nominal(1), :inheritable(1) ); method archetypes($obj?) { $archetypes } method new(*%named) { nqp::findmethod(NQPMu, 'BUILDALL')(nqp::create(self), %named) } method new_type(:$name = '', :$ver, :$auth, :$api) { my $metaclass := self.new(); my $obj := nqp::settypehll(nqp::newtype($metaclass, 'NativeRef'), 'Raku'); $metaclass.set_name($obj, $name); $metaclass.set_ver($obj, $ver); $metaclass.set_auth($obj, $auth) if $auth; $metaclass.set_api($obj, $api) if $api; self.add_stash($obj); } method compose($the-obj, :$compiler_services) { my $obj := nqp::decont($the-obj); self.compose_repr($obj); self.compute_mro($obj); self.publish_method_cache($obj); self.publish_type_cache($obj); $!composed := 1; $obj } method compose_repr($obj) { if !$!repr_composed { my $info := nqp::hash(); $info := nqp::hash(); $info := nqp::decont($!type); $info := $!refkind // 'unknown'; nqp::composetype(nqp::decont($obj), $info); $!repr_composed := 1; } } method is_composed($obj) { $!composed } method set_native_type($obj, $type) { $!type := $type; } method native_type($obj) { $!type } method set_ref_kind($obj, $refkind) { $!refkind := $refkind; } method ref_kind($obj) { $!refkind } method method_table($obj) { nqp::hash() } method submethod_table($obj) { nqp::hash() } } #line 1 src/Perl6/Metamodel/SubsetHOW.nqp class Perl6::Metamodel::SubsetHOW does Perl6::Metamodel::Naming does Perl6::Metamodel::Documenting does Perl6::Metamodel::Stashing does Perl6::Metamodel::LanguageRevision does Perl6::Metamodel::Nominalizable { # The subset type or nominal type that we refine. has $!refinee; # The block implementing the refinement. has $!refinement; # Should we preserve pre-6.e behavior? has $!pre-e-behavior; has $!archetypes; method archetypes($obj?) { unless nqp::isconcrete($!archetypes) { my $refinee_archetypes := $!refinee.HOW.archetypes($!refinee); my $generic := $refinee_archetypes.generic || (nqp::defined($!refinement) && nqp::can($!refinement, 'is_generic') && $!refinement.is_generic); $!archetypes := Perl6::Metamodel::Archetypes.new( :nominalizable, :$generic, definite => $refinee_archetypes.definite, coercive => $refinee_archetypes.coercive, ); } $!archetypes } method new(*%named) { nqp::findmethod(NQPMu, 'BUILDALL')(nqp::create(self), %named) } method mro($obj, *%named) { my @mro; @mro.push($obj); for $!refinee.HOW.mro($!refinee, |%named) { @mro.push($_); } @mro } method BUILD(:$refinee, :$refinement) { $!refinee := $refinee; $!refinement := $refinement; $!pre-e-behavior := self.language_revision(self) < 3; # less than 6.e } method new_type(:$name = '', :$refinee!, :$refinement!) { my $metasubset := self.new(:$refinee, :$refinement); my $type := nqp::settypehll(nqp::newtype($metasubset, 'Uninstantiable'), 'Raku'); $metasubset.set_name($type, $name); $metasubset.set_language_version($metasubset, :force); nqp::settypecheckmode($type, 2); self.add_stash($type) } method set_of($obj, $refinee) { my $archetypes := $refinee.HOW.archetypes($refinee); if $archetypes.generic { nqp::die("Use of a generic as 'of' type of a subset is not implemented yet") } unless $archetypes.nominal || $archetypes.nominalizable { nqp::die("The 'of' type of a subset must either be a valid nominal " ~ "type or a type that can provide one"); } $!refinee := nqp::decont($refinee); if nqp::objprimspec($!refinee) { Perl6::Metamodel::Configuration.throw_or_die( 'X::NYI', "Subsets of native types NYI", :feature(nqp::hllizefor('Subsets of native types', 'Raku')) ); } } method set_where($obj, $refinement) { $!refinement := nqp::decont($refinement) } method refinee($obj) { $!refinee } method refinement($obj) { nqp::hllize($!refinement) } method isa($obj, $type) { $!refinee.isa($type) || nqp::hllboolfor(nqp::istrue($type.HOW =:= self), "Raku") } method instantiate_generic($obj, $type_env) { return $obj unless $!archetypes.generic; my $ins_refinee := $!refinee.HOW.instantiate_generic($!refinee, $type_env); my $ins_refinement := $!refinement; if nqp::isconcrete($!refinement) { if nqp::can($!refinement, 'is_generic') && $!refinement.is_generic { $ins_refinement := $!refinement.instantiate_generic($type_env); } } self.new_type(:name(self.name($obj)), :refinee($ins_refinee), :refinement($ins_refinement)) } method nominalize($obj) { $!refinee.HOW.archetypes($!refinee).nominalizable ?? $!refinee.HOW.nominalize($!refinee) !! $!refinee } # Should have the same methods of the (eventually nominal) type # that we refine. (For the performance win, work out a way to # steal its method cache.) method find_method($obj, $name, *%c) { $!refinee.HOW.find_method($!refinee, $name, |%c) } # Do check when we're on LHS of smartmatch (e.g. Even ~~ Int). method type_check($obj, $checkee) { nqp::hllboolfor( ($!pre-e-behavior && nqp::istrue($checkee.HOW =:= self)) || nqp::istype($!refinee, $checkee), "Raku" ) } # Here we check the value itself (when on RHS on smartmatch). method accepts_type($obj, $checkee) { nqp::hllboolfor( nqp::istype($checkee, $!refinee) && (nqp::isnull($!refinement) || ($!refinee.HOW.archetypes($!refinee).coercive ?? nqp::istrue($!refinement.ACCEPTS($!refinee.HOW.coerce($!refinee, $checkee))) !! nqp::istrue($!refinement.ACCEPTS($checkee)))), "Raku") } # Methods needed by Perl6::Metamodel::Nominalizable method nominalizable_kind() { 'subset' } method !wrappee($obj) { $!refinee } } #line 1 src/Perl6/Metamodel/EnumHOW.nqp # This is the meta-object for an enumeration (declared with enum). # It keeps hold of the enumeration values in an Map, which is # created at composition time. It supports having roles composed in, # one or two of which presumably provide the core enum-ish methods. class Perl6::Metamodel::EnumHOW does Perl6::Metamodel::Naming does Perl6::Metamodel::Documenting does Perl6::Metamodel::LanguageRevision does Perl6::Metamodel::Stashing does Perl6::Metamodel::AttributeContainer does Perl6::Metamodel::MethodContainer does Perl6::Metamodel::PrivateMethodContainer does Perl6::Metamodel::MultiMethodContainer does Perl6::Metamodel::RoleContainer does Perl6::Metamodel::BaseType does Perl6::Metamodel::MROBasedMethodDispatch does Perl6::Metamodel::MROBasedTypeChecking does Perl6::Metamodel::BUILDPLAN does Perl6::Metamodel::BoolificationProtocol does Perl6::Metamodel::REPRComposeProtocol does Perl6::Metamodel::InvocationProtocol does Perl6::Metamodel::Mixins { # Hash representing enumeration keys to values. has %!values; # Reverse mapping hash. has $!value_to_enum; # List of enum values (actual enum objects). has @!enum_value_list; # Roles that we do. has @!role_typecheck_list; # Role'd version of the enum. has $!role; has int $!roled; # Are we composed yet? has $!composed; # Exportation callback for enum symbols, if any. has $!export_callback; my $archetypes := Perl6::Metamodel::Archetypes.new( :nominal(1), :composalizable(1), :augmentable(1) ); method archetypes($obj?) { $archetypes } method new(*%named) { nqp::findmethod(NQPMu, 'BUILDALL')(nqp::create(self), %named) } method new_type(:$name!, :$base_type?, :$repr = 'P6opaque', :$is_mixin) { my $meta := self.new(); my $obj := nqp::settypehll(nqp::newmixintype($meta, $repr), 'Raku'); $meta.set_name($obj, $name); $meta.set_base_type($meta, $base_type) unless $base_type =:= NQPMu; $meta.setup_mixin_cache($obj); self.add_stash($obj); } # We only have add_parent to support mixins, which expect this method. method add_parent($obj, $parent) { self.set_base_type($obj, $parent); } method add_enum_value($obj, $value) { %!values{nqp::unbox_s($value.key)} := $value.value; @!enum_value_list[+@!enum_value_list] := $value; nqp::scwbdisable(); $!value_to_enum := NQPMu; nqp::scwbenable(); } method set_export_callback($obj, $callback) { $!export_callback := $callback } method enum_values($obj) { %!values } method elems($obj) { nqp::elems(%!values) } method enum_from_value($obj, $value) { my $value_to_enum := $!value_to_enum; unless $value_to_enum { $value_to_enum := nqp::hash; for @!enum_value_list { $value_to_enum{$_.value} := $_; } nqp::scwbdisable(); $!value_to_enum := $value_to_enum; nqp::scwbenable(); } nqp::existskey($value_to_enum, $value) ?? $value_to_enum{$value} !! nqp::null() } method enum_value_list($obj) { @!enum_value_list } method compose($the-obj, :$compiler_services) { my $obj := nqp::decont($the-obj); self.set_language_version($obj); # Instantiate all of the roles we have (need to do this since # all roles are generic on ::?CLASS) and pass them to the # composer. my @roles_to_compose := self.roles_to_compose($obj); my $rtca; if @roles_to_compose { my @ins_roles; while @roles_to_compose { my $r := @roles_to_compose.pop(); @!role_typecheck_list[+@!role_typecheck_list] := $r; my $ins := $r.HOW.specialize($r, $obj); self.check-type-compat($obj, $ins, [3]) if nqp::istype($ins.HOW, Perl6::Metamodel::LanguageRevision); @ins_roles.push($ins); } $rtca := Perl6::Metamodel::Configuration.role_to_class_applier_type.new; $rtca.prepare($obj, @ins_roles); # Add them to the typecheck list, and pull in their # own type check lists also. for @ins_roles { @!role_typecheck_list[+@!role_typecheck_list] := $_; for $_.HOW.role_typecheck_list($_) { @!role_typecheck_list[+@!role_typecheck_list] := $_; } } } # Compose own attributes first. for self.attributes($obj, :local) { $_.compose($obj); } if $rtca { $rtca.apply(); } # Incorporate any new multi candidates (needs MRO built). self.incorporate_multi_candidates($obj); # Compose remaining attributes. for self.attributes($obj, :local) { $_.compose($obj); } # Publish type and method caches. self.publish_type_cache($obj); self.publish_method_cache($obj); # Publish boolification spec. self.publish_boolification_spec($obj); # Create BUILDPLAN. self.create_BUILDPLAN($obj); # Compose the representation. unless $!composed { self.compose_repr($obj); $!composed := 1; } $obj } # Called by the compiler when all enum values have been added, to trigger # any needed actions. method compose_values($obj) { if $!export_callback { $!export_callback(); $!export_callback := Mu; } } my $composalizer; method set_composalizer($c) { $composalizer := $c } method composalize($obj) { unless $!roled { $!role := $composalizer($obj, self.name($obj), @!enum_value_list); $!roled := 1; } $!role } method is_composed($obj) { $!composed } method role_typecheck_list($obj) { @!role_typecheck_list } } #line 1 src/Perl6/Metamodel/CoercionHOW.nqp # Coercion types, of the form TargetType(ConstraintType), are implemented with # 6model parametrics. We create a single BEGIN-time "root" for the coercion # type family, and the target and constraint types are stored as parameters. # This means we get cross-compilation-unit interning "for free", as well as # avoiding a meta-object instance per coercion type created. class Perl6::Metamodel::CoercionHOW does Perl6::Metamodel::LanguageRevision does Perl6::Metamodel::Nominalizable { has $!target_type; has $!nominal_target; has $!constraint_type; has $!archetypes; method archetypes($obj?) { unless nqp::isconcrete($!archetypes) { my $generic := $!target_type.HOW.archetypes($!target_type).generic || $!constraint_type.HOW.archetypes($!constraint_type).generic; $!archetypes := Perl6::Metamodel::Archetypes.new( :coercive, :nominalizable, :$generic, definite => $!target_type.HOW.archetypes($!target_type).definite, ); } $!archetypes } method new(*%named) { nqp::findmethod(NQPMu, 'BUILDALL')(nqp::create(self), %named) } method new_type($target, $constraint) { my $coercion_type := nqp::parameterizetype((Perl6::Metamodel::CoercionHOW.WHO), [$target, $constraint]); nqp::setdebugtypename($coercion_type, $coercion_type.HOW.name($coercion_type)); $coercion_type } method set_target_type($target_type) { $!target_type := $target_type; $!nominal_target := $!target_type.HOW.archetypes($!target_type).nominalizable ?? $!target_type.HOW.nominalize($!target_type) !! $!target_type; } method set_constraint_type($constraint_type) { $!constraint_type := $constraint_type; } method name($coercion_type) { $!target_type.HOW.name($!target_type) ~ '(' ~ $!constraint_type.HOW.name($!constraint_type) ~ ')' } method shortname($coercion_type) { $!target_type.HOW.shortname($!target_type) ~ '(' ~ $!constraint_type.HOW.shortname($!constraint_type) ~ ')' } method target_type($coercion_type) { $!target_type } method constraint_type($coercion_type) { $!constraint_type } method nominal_target($coercion_type) { $!nominal_target } method nominalize($coercion_type) { $!target_type.HOW.archetypes($!target_type).nominalizable ?? $!target_type.HOW.nominalize($!target_type) !! $!target_type } method instantiate_generic($coercion_type, $type_env) { return $coercion_type unless $!archetypes.generic; my $ins_target := $!target_type.HOW.archetypes($!target_type).generic ?? $!target_type.HOW.instantiate_generic($!target_type, $type_env) !! $!target_type; my $ins_constraint := $!constraint_type.HOW.archetypes($!constraint_type).generic ?? $!constraint_type.HOW.instantiate_generic($!constraint_type, $type_env) !! $!constraint_type; self.new_type($ins_target, $ins_constraint); } method find_method($coercion_type, $name, *%c) { $!target_type.HOW.find_method($!target_type, $name, |%c) } method find_method_qualified($coercion_type, $qtype, $name) { $!target_type.HOW.find_method_qualified($!target_type, $qtype, $name) } method isa($obj, $type) { $!nominal_target.HOW.isa($obj, $type) } method does($obj, $type) { $!nominal_target.HOW.does($obj, $type) } method type_check($coercion_type, $checkee) { $coercion_type =:= $checkee || $!target_type.HOW.type_check($!target_type, $checkee); } method accepts_type($coercion_type, $checkee) { nqp::istype($checkee, $!target_type) || nqp::istype($checkee, $!constraint_type); } # Coercion protocol method. method coerce($obj, $value) { nqp::dispatch('raku-coercion', nqp::decont($obj), $value) } # Attempt coercion on TargetType method !coerce_TargetType($obj, $value) { my $constraintHOW := $!constraint_type.HOW; $value := $constraintHOW.coerce($!constraint_type, $value) if nqp::can((my $archetypes := $constraintHOW.archetypes($!constraint_type)), 'coercive') && $archetypes.coercive; my $nominal_target := $!nominal_target; nqp::istype($value, $!constraint_type) ?? nqp::defined( my $method := nqp::tryfindmethod( nqp::what($value), $nominal_target.HOW.name($nominal_target) ) ) ?? (nqp::istype((my $coerced := $method($value)),$!target_type) || nqp::istype($coerced, nqp::gethllsym('Raku', 'Failure'))) ?? $coerced !! self."!invalid_coercion"($value, $nominal_target.HOW.name($nominal_target), $coerced) !! self."!coerce_COERCE"($obj, $value, $nominal_target) !! self."!invalid_type"($value) } # Handle errors method !invalid($value, $hint) { my $from-type := nqp::what($value); Perl6::Metamodel::Configuration.throw_or_die( 'X::Coerce::Impossible', "Impossible coercion from " ~ $from-type.HOW.name($from-type) ~ " into " ~ $!target_type.HOW.name($!target_type) ~ ": $hint", :target-type($!target_type), :$from-type, :$hint ) } # Handle invalid type on accepting method !invalid_type($value) { self."!invalid"( $value, "value is of unacceptable type " ~ $value.HOW.name($value) ) } # Attempt coercion with TargetType.COERCE($value). method !coerce_COERCE($obj, $value, $nominal_target) { nqp::defined( my $method := nqp::tryfindmethod( (my $HOW := $nominal_target.HOW).archetypes.composable ?? ($nominal_target := $HOW.pun($nominal_target)) !! $nominal_target, 'COERCE' ) ) && nqp::can($method, 'cando') && $method.cando($nominal_target, $value) ?? ( nqp::istype( (my $coerced_value := $method($nominal_target, $value)), $!target_type ) || nqp::istype( $coerced_value, nqp::gethllsym('Raku', 'Failure') ) ) ?? $coerced_value !! self."!invalid_coercion"($value, 'COERCE', $coerced_value) !! self."!coerce_new"($obj, $value, $nominal_target) } # Handle invalid coercion method !invalid_coercion($value, $method_name, $coerced_value) { self."!invalid"( $value, "method $method_name returned " ~ (nqp::defined($coerced_value) ?? "an instance of " !! "a type object " ) ~ $coerced_value.HOW.name($coerced_value) ) } # Attempt to coerce via TargetType.new method !coerce_new($obj, $value, $nominal_target) { if nqp::defined( my $method := nqp::tryfindmethod($nominal_target, 'new') ) && nqp::can($method, 'cando') && $method.cando($nominal_target, $value) { # There should be no significant performance penalty on this path # because if method call ever throws then this is going to result # in an exception one way or another. my $exception; my $coerced_value := nqp::null(); try { CATCH { my $exception_obj := nqp::getpayload($!); if $exception_obj.HOW.name($exception_obj) ne 'X::Constructor::Positional' { $exception := $!; } } # Provide context information to the method 'new' my $*COERCION-TYPE := $obj; $coerced_value := $method($nominal_target, $value); return $coerced_value if nqp::istype($coerced_value, $!target_type) || nqp::istype( $coerced_value, nqp::gethllsym('Raku', 'Failure') ) } if nqp::defined($exception) { nqp::rethrow($exception); } elsif !nqp::isnull($coerced_value) { self."!invalid_coercion"($value, 'new', $coerced_value) } } self."!invalid"($value, "no acceptable coercion method found") } # Methods needed by Perl6::Metamodel::Nominalizable method nominalizable_kind() { 'coercion' } method !wrappee($obj) { $!target_type } } BEGIN { my $root := nqp::newtype(Perl6::Metamodel::CoercionHOW.new, 'Uninstantiable'); nqp::settypehll($root, 'Raku'); nqp::setdebugtypename(nqp::settypehll($root, 'Raku'), 'CoercionHOW root'); nqp::setparameterizer($root, sub ($type, $params) { my $metaclass := $type.HOW.new(); $metaclass.set_target_type($params[0]); $metaclass.set_constraint_type($params[1]); my $coercion_type := nqp::settypehll(nqp::newtype($metaclass, 'Uninstantiable'), 'Raku'); $metaclass.set_language_version($coercion_type, :force); nqp::settypecheckmode($coercion_type, 2); $coercion_type }); (Perl6::Metamodel::CoercionHOW.WHO) := $root; } #line 1 src/Perl6/Metamodel/DefiniteHOW.nqp class Perl6::Metamodel::DefiniteHOW does Perl6::Metamodel::Documenting does Perl6::Metamodel::Nominalizable { # ATypeN are used as parameterization arguments to have a definite report correct archetypes. my class ArchetypeDefinite { my $type := Perl6::Metamodel::Archetypes.new(:definite, :nominalizable, :!coercive, :!generic); method archetype() { $type } } my class ArchetypeDefiniteGeneric { my $type := Perl6::Metamodel::Archetypes.new(:definite, :nominalizable, :!coercive, :generic); method archetype() { $type } } my class ArchetypeDefiniteCoercive { my $type := Perl6::Metamodel::Archetypes.new(:definite, :nominalizable, :coercive, :!generic); method archetype() { $type } } my class ArchetypeDefiniteCoerciveGeneric { my $type := Perl6::Metamodel::Archetypes.new(:definite, :nominalizable, :coercive, :generic); method archetype() { $type } } my @archetypes := nqp::list(ArchetypeDefinite, ArchetypeDefiniteGeneric, ArchetypeDefiniteCoercive, ArchetypeDefiniteCoerciveGeneric); method archetypes($definite_type = nqp::null()) { nqp::isnull($definite_type) ?? ArchetypeDefinite.archetype() !! nqp::typeparameterat(nqp::decont($definite_type), 2).archetype() } #~ has @!mro; my class Definite { } my class NotDefinite { } method new_type(:$base_type!, :$definite!) { my $base_archetypes := $base_type.HOW.archetypes($base_type); # Use generic and coercive as positional bits to form a numeric suffix for 'ATypeN' names. my $atype := @archetypes[ nqp::bitor_i( nqp::bitshiftl_i(nqp::istrue($base_archetypes.coercive), 1), nqp::istrue($base_archetypes.generic))]; my $root := nqp::parameterizetype((Perl6::Metamodel::DefiniteHOW.WHO), [$base_type, $definite ?? Definite !! NotDefinite, $atype]); nqp::setdebugtypename($root, self.name($root)); } method name($definite_type) { if nqp::isnull(nqp::typeparameterized($definite_type)) { '?:?' } else { my $base_type := nqp::typeparameterat($definite_type, 0); my $definite := nqp::typeparameterat($definite_type, 1); $base_type.HOW.name($base_type) ~ ':' ~ (nqp::eqaddr($definite, Definite) ?? 'D' !! 'U') } } method shortname($definite_type) { if nqp::isnull(nqp::typeparameterized($definite_type)) { '?:?' } else { my $base_type := nqp::typeparameterat($definite_type, 0); my $definite := nqp::typeparameterat($definite_type, 1); $base_type.HOW.shortname($base_type) ~ ':' ~ (nqp::eqaddr($definite, Definite) ?? 'D' !! 'U') } } sub check_instantiated($definite_type) { nqp::die('Cannot perform this operation on an uninstantiated definite type') if nqp::isnull(nqp::typeparameterized($definite_type)); } method base_type($definite_type) { check_instantiated($definite_type); nqp::typeparameterat($definite_type, 0) } method definite($definite_type) { check_instantiated($definite_type); nqp::eqaddr(nqp::typeparameterat($definite_type, 1), Definite) ?? 1 !! 0 } method nominalize($obj) { my $base_type := $obj.HOW.base_type($obj); $base_type.HOW.archetypes($base_type).nominalizable ?? $base_type.HOW.nominalize($base_type) !! $base_type } method instantiate_generic($definite_type, $type_env) { my $base_type := $definite_type.HOW.base_type($definite_type); return $definite_type unless $base_type.HOW.archetypes($base_type).generic; self.new_type( base_type => $base_type.HOW.instantiate_generic($base_type, $type_env), definite => $definite_type.HOW.definite($definite_type)) } #~ # Should have the same methods of the base type that we refine. #~ # (For the performance win, work out a way to steal its method cache.) method find_method($definite_type, $name, *%c) { my $base_type := self.base_type($definite_type); $base_type.HOW.find_method($base_type, $name, |%c) } method find_method_qualified($definite_type, $qtype, $name) { my $base_type := self.base_type($definite_type); $base_type.HOW.find_method_qualified($base_type, $qtype, $name) } # Do check when we're on LHS of smartmatch (e.g. Even ~~ Int). method type_check($definite_type, $checkee) { my $base_type := self.base_type($definite_type); nqp::hllboolfor(nqp::istype($base_type, $checkee), "Raku") } # Here we check the value itself (when on RHS on smartmatch). method accepts_type($definite_type, $checkee) { my $base_type := self.base_type($definite_type); my $definite := self.definite($definite_type); nqp::hllboolfor( nqp::istype($checkee, $base_type) && nqp::isconcrete($checkee) == $definite, "Raku" ) } # Methods needed by Perl6::Metamodel::Nominalizable method nominalizable_kind() { 'definite' } method !wrappee($obj) { self.base_type($obj) } } BEGIN { my $root := nqp::newtype(Perl6::Metamodel::DefiniteHOW, 'Uninstantiable'); nqp::setdebugtypename(nqp::settypehll($root, 'Raku'), 'DefiniteHOW root'); nqp::setparameterizer($root, sub ($type, $params) { # Re-use same HOW. my $thing := nqp::settypehll(nqp::newtype($type.HOW, 'Uninstantiable'), 'Raku'); nqp::settypecheckmode($thing, 2) }); (Perl6::Metamodel::DefiniteHOW.WHO) := $root; } #line 1 src/Perl6/Metamodel/Dispatchers.nqp # MoarVM uses new-disp instead of these # vim: set ft=perl6 nomodifiable :