Implement aspect attributes and expose them to aspect implementation function.

--
MOS_MIGRATED_REVID=110356954
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/RuleContext.java b/src/main/java/com/google/devtools/build/lib/analysis/RuleContext.java
index b75c6b9..cc99514 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/RuleContext.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/RuleContext.java
@@ -136,7 +136,7 @@
   private final Set<ConfigMatchingProvider> configConditions;
   private final AttributeMap attributes;
   private final ImmutableSet<String> features;
-  private final Map<String, Attribute> aspectAttributes;
+  private final ImmutableMap<String, Attribute> aspectAttributes;
   private final BuildConfiguration hostConfiguration;
   private final ConfigurationFragmentPolicy configurationFragmentPolicy;
   private final Class<? extends BuildConfiguration.Fragment> universalFragment;
@@ -147,11 +147,13 @@
   /* lazily computed cache for Make variables, computed from the above. See get... method */
   private transient ConfigurationMakeVariableContext configurationMakeVariableContext = null;
 
-  private RuleContext(Builder builder, ListMultimap<String, ConfiguredTarget> targetMap,
+  private RuleContext(
+      Builder builder,
+      ListMultimap<String, ConfiguredTarget> targetMap,
       ListMultimap<String, ConfiguredFilesetEntry> filesetEntryMap,
       Set<ConfigMatchingProvider> configConditions,
       Class<? extends BuildConfiguration.Fragment> universalFragment,
-      Map<String, Attribute> aspectAttributes) {
+      ImmutableMap<String, Attribute> aspectAttributes) {
     super(builder.env, builder.rule, builder.configuration, builder.prerequisiteMap.get(null),
         builder.visibility);
     this.rule = builder.rule;
@@ -234,6 +236,13 @@
   }
 
   /**
+   * Attributes from aspects.
+   */
+  public ImmutableMap<String, Attribute> getAspectAttributes() {
+    return aspectAttributes;
+  }
+
+  /**
    * Accessor for the Rule's attribute values.
    */
   public AttributeMap attributes() {
@@ -1241,7 +1250,7 @@
     private ListMultimap<Attribute, ConfiguredTarget> prerequisiteMap;
     private Set<ConfigMatchingProvider> configConditions;
     private NestedSet<PackageSpecification> visibility;
-    private Map<String, Attribute> aspectAttributes;
+    private ImmutableMap<String, Attribute> aspectAttributes;
 
     Builder(AnalysisEnvironment env, Rule rule, BuildConfiguration configuration,
         BuildConfiguration hostConfiguration,
@@ -1284,7 +1293,7 @@
      * Adds attributes which are defined by an Aspect (and not by RuleClass).
      */
     Builder setAspectAttributes(Map<String, Attribute> aspectAttributes) {
-      this.aspectAttributes = aspectAttributes;
+      this.aspectAttributes = ImmutableMap.copyOf(aspectAttributes);
       return this;
     }
 
diff --git a/src/main/java/com/google/devtools/build/lib/packages/AspectDefinition.java b/src/main/java/com/google/devtools/build/lib/packages/AspectDefinition.java
index 15829d8..763ae11 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/AspectDefinition.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/AspectDefinition.java
@@ -250,6 +250,17 @@
      */
     public <TYPE> Builder add(Attribute.Builder<TYPE> attr) {
       Attribute attribute = attr.build();
+      return add(attribute);
+    }
+
+    /**
+     * Adds an attribute to the aspect.
+     *
+     * <p>Since aspects do not appear in BUILD files, the attribute must be either implicit
+     * (not available in the BUILD file, starting with '$') or late-bound (determined after the
+     * configuration is available, starting with ':')
+     */
+    public Builder add(Attribute attribute) {
       Preconditions.checkState(attribute.isImplicit() || attribute.isLateBound());
       Preconditions.checkState(!attributes.containsKey(attribute.getName()),
           "An attribute with the name '%s' already exists.", attribute.getName());
diff --git a/src/main/java/com/google/devtools/build/lib/packages/Attribute.java b/src/main/java/com/google/devtools/build/lib/packages/Attribute.java
index 8cd855c..3bbccdf 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/Attribute.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/Attribute.java
@@ -479,6 +479,10 @@
       return this;
     }
 
+    public boolean isValueSet() {
+      return valueSet;
+    }
+
     /**
      * Sets the attribute default value to a computed default value - use
      * this when the default value is a function of other attributes of the
diff --git a/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleClassFunctions.java b/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleClassFunctions.java
index c3c4897..ac705a5 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleClassFunctions.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleClassFunctions.java
@@ -62,6 +62,7 @@
 import com.google.devtools.build.lib.packages.SkylarkAspectClass;
 import com.google.devtools.build.lib.packages.TargetUtils;
 import com.google.devtools.build.lib.packages.TestSize;
+import com.google.devtools.build.lib.rules.SkylarkAttr.Descriptor;
 import com.google.devtools.build.lib.syntax.BaseFunction;
 import com.google.devtools.build.lib.syntax.BuiltinFunction;
 import com.google.devtools.build.lib.syntax.ClassObject;
@@ -202,7 +203,6 @@
   }
 
   // TODO(bazel-team): implement attribute copy and other rule properties
-
   @SkylarkSignature(name = "rule", doc =
       "Creates a new rule. Store it in a global value, so that it can be loaded and called "
       + "from BUILD files.",
@@ -243,14 +243,14 @@
             doc = "If true, the files will be generated in the genfiles directory instead of the "
             + "bin directory. Unless you need it for compatibility with existing rules "
             + "(e.g. when generating header files for C++), do not set this flag."),
-       @Param(name = "fragments", type = SkylarkList.class, generic1 = String.class,
-           defaultValue = "[]",
-           doc = "List of names of configuration fragments that the rule requires "
-           + "in target configuration."),
-       @Param(name = "host_fragments", type = SkylarkList.class, generic1 = String.class,
-           defaultValue = "[]",
-           doc = "List of names of configuration fragments that the rule requires "
-           + "in host configuration.")},
+        @Param(name = "fragments", type = SkylarkList.class, generic1 = String.class,
+            defaultValue = "[]",
+            doc = "List of names of configuration fragments that the rule requires "
+            + "in target configuration."),
+        @Param(name = "host_fragments", type = SkylarkList.class, generic1 = String.class,
+            defaultValue = "[]",
+            doc = "List of names of configuration fragments that the rule requires "
+            + "in host configuration.")},
       useAst = true, useEnvironment = true)
   private static final BuiltinFunction rule = new BuiltinFunction("rule") {
     @SuppressWarnings({"rawtypes", "unchecked"}) // castMap produces
@@ -265,19 +265,8 @@
 
       // We'll set the name later, pass the empty string for now.
       RuleClass.Builder builder = new RuleClass.Builder("", type, true, parent);
-      ImmutableList.Builder<Pair<String, SkylarkAttr.Descriptor>> attributes =
-          ImmutableList.builder();
-
-      if (attrs != Runtime.NONE) {
-        for (Map.Entry<String, SkylarkAttr.Descriptor> attr :
-            castMap(attrs, String.class, SkylarkAttr.Descriptor.class, "attrs").entrySet()) {
-          SkylarkAttr.Descriptor attrDescriptor = attr.getValue();
-          String attrName =
-              attributeToNative(attr.getKey(), ast.getLocation(),
-                  attrDescriptor.getAttributeBuilder().hasLateBoundValue());
-          attributes.add(Pair.of(attrName, attrDescriptor));
-        }
-      }
+      ImmutableList<Pair<String, SkylarkAttr.Descriptor>> attributes =
+          attrObjectToAttributesList(attrs, ast);
       if (executable || test) {
         addAttribute(
             ast.getLocation(),
@@ -292,16 +281,18 @@
       if (implicitOutputs != Runtime.NONE) {
         if (implicitOutputs instanceof BaseFunction) {
           BaseFunction func = (BaseFunction) implicitOutputs;
-          SkylarkCallbackFunction callback =
-              new SkylarkCallbackFunction(func, ast, funcallEnv);
+          SkylarkCallbackFunction callback = new SkylarkCallbackFunction(func, ast, funcallEnv);
           builder.setImplicitOutputsFunction(
               new SkylarkImplicitOutputsFunctionWithCallback(callback, ast.getLocation()));
         } else {
           builder.setImplicitOutputsFunction(
               new SkylarkImplicitOutputsFunctionWithMap(
-                  ImmutableMap.copyOf(castMap(
-                      implicitOutputs, String.class, String.class,
-                      "implicit outputs of the rule class"))));
+                  ImmutableMap.copyOf(
+                      castMap(
+                          implicitOutputs,
+                          String.class,
+                          String.class,
+                          "implicit outputs of the rule class"))));
         }
       }
 
@@ -312,27 +303,48 @@
       registerRequiredFragments(fragments, hostFragments, builder);
       builder.setConfiguredTargetFunction(implementation);
       builder.setRuleDefinitionEnvironment(funcallEnv);
-      return new RuleFunction(builder, type, attributes.build(), ast.getLocation());
+      return new RuleFunction(builder, type, attributes, ast.getLocation());
     }
 
+      private void registerRequiredFragments(
+          SkylarkList fragments, SkylarkList hostFragments, RuleClass.Builder builder)
+          throws EvalException {
+        Map<ConfigurationTransition, ImmutableSet<String>> map = new HashMap<>();
+        addFragmentsToMap(map, fragments, NONE); // NONE represents target configuration
+        addFragmentsToMap(map, hostFragments, HOST);
 
-    private void registerRequiredFragments(
-        SkylarkList fragments, SkylarkList hostFragments, RuleClass.Builder builder)
-        throws EvalException {
-      Map<ConfigurationTransition, ImmutableSet<String>> map = new HashMap<>();
-      addFragmentsToMap(map, fragments, NONE); // NONE represents target configuration
-      addFragmentsToMap(map, hostFragments, HOST);
+        builder.requiresConfigurationFragments(new SkylarkModuleNameResolver(), map);
+      }
 
-      builder.requiresConfigurationFragments(new SkylarkModuleNameResolver(), map);
-    }
+      private void addFragmentsToMap(
+          Map<ConfigurationTransition, ImmutableSet<String>> map,
+          SkylarkList fragments,
+          ConfigurationTransition config)
+          throws EvalException {
+        if (!fragments.isEmpty()) {
+          map.put(config, ImmutableSet.copyOf(fragments.getContents(String.class, "fragments")));
+        }
+      }
+    };
 
-    private void addFragmentsToMap(Map<ConfigurationTransition, ImmutableSet<String>> map,
-        SkylarkList fragments, ConfigurationTransition config) throws EvalException {
-      if (!fragments.isEmpty()) {
-        map.put(config, ImmutableSet.copyOf(fragments.getContents(String.class, "fragments")));
+  protected static ImmutableList<Pair<String, Descriptor>> attrObjectToAttributesList(
+      Object attrs, FuncallExpression ast) throws EvalException {
+    ImmutableList.Builder<Pair<String, Descriptor>> attributes = ImmutableList.builder();
+
+    if (attrs != Runtime.NONE) {
+      for (Map.Entry<String, Descriptor> attr :
+          castMap(attrs, String.class, Descriptor.class, "attrs").entrySet()) {
+        Descriptor attrDescriptor = attr.getValue();
+        String attrName =
+            attributeToNative(
+                attr.getKey(),
+                ast.getLocation(),
+                attrDescriptor.getAttributeBuilder().hasLateBoundValue());
+        attributes.add(Pair.of(attrName, attrDescriptor));
       }
     }
-  };
+    return attributes.build();
+  }
 
   private static void addAttribute(
       Location location, RuleClass.Builder builder, Attribute attribute) throws EvalException {
@@ -356,12 +368,7 @@
         generic1 = String.class,
         defaultValue = "[]"
       ),
-      @Param(
-        name = "extra_deps",
-        type = SkylarkList.class,
-        generic1 = String.class,
-        defaultValue = "[]"
-      )
+      @Param(name = "attrs", type = Map.class, noneable = true, defaultValue = "None")
     },
     useEnvironment = true,
     useAst = true
@@ -371,29 +378,37 @@
         public SkylarkAspect invoke(
             BaseFunction implementation,
             SkylarkList attributeAspects,
-            SkylarkList extraDeps,
+            Object attrs,
             FuncallExpression ast,
-            Environment funcallEnv) throws EvalException {
-          ImmutableList.Builder<String> attributeListBuilder = ImmutableList.builder();
+            Environment funcallEnv)
+            throws EvalException {
+          ImmutableList.Builder<String> attrAspects = ImmutableList.builder();
           for (Object attributeAspect : attributeAspects) {
-            attributeListBuilder.add(STRING.convert(attributeAspect, "attr_aspects"));
-          }
-          ImmutableList.Builder<Label> extraDepsBuilder = ImmutableList.builder();
-          for (Object extraDep : extraDeps) {
-            String extraDepsString = STRING.convert(extraDep, "extra_deps");
-            Label label;
-            try {
-              label = Label.parseAbsolute(extraDepsString);
-            } catch (LabelSyntaxException e) {
-              throw new EvalException(ast.getLocation(), e.getMessage());
-            }
-            extraDepsBuilder.add(label);
+            attrAspects.add(STRING.convert(attributeAspect, "attr_aspects"));
           }
 
-          return new SkylarkAspect(implementation,
-              attributeListBuilder.build(),
-              extraDepsBuilder.build(),
-              funcallEnv);
+          ImmutableList<Pair<String, SkylarkAttr.Descriptor>> attributes =
+              attrObjectToAttributesList(attrs, ast);
+          for (Pair<String, Descriptor> attribute : attributes) {
+            String nativeName = attribute.getFirst();
+            if (!Attribute.isImplicit(nativeName)) {
+              throw new EvalException(
+                  ast.getLocation(),
+                  String.format(
+                      "Aspect attribute '%s' must be implicit (its name should start with '_').",
+                      nativeName));
+            }
+
+            String skylarkName = "_" + nativeName.substring(1);
+
+            if (!attribute.getSecond().getAttributeBuilder().isValueSet()) {
+              throw new EvalException(
+                  ast.getLocation(),
+                  String.format("Aspect attribute '%s' has no default value.", skylarkName));
+            }
+          }
+
+          return new SkylarkAspect(implementation, attrAspects.build(), attributes, funcallEnv);
         }
       };
 
@@ -634,17 +649,18 @@
   public static final class SkylarkAspect implements SkylarkValue {
     private final BaseFunction implementation;
     private final ImmutableList<String> attributeAspects;
-    private final ImmutableList<Label> extraDeps;
+    private final ImmutableList<Pair<String, Descriptor>> attributes;
     private final Environment funcallEnv;
     private Exported exported;
 
     public SkylarkAspect(
         BaseFunction implementation,
         ImmutableList<String> attributeAspects,
-        ImmutableList<Label> extraDeps, Environment funcallEnv) {
+        ImmutableList<Pair<String, Descriptor>> attributes,
+        Environment funcallEnv) {
       this.implementation = implementation;
       this.attributeAspects = attributeAspects;
-      this.extraDeps = extraDeps;
+      this.attributes = attributes;
       this.funcallEnv = funcallEnv;
     }
 
@@ -660,8 +676,8 @@
       return funcallEnv;
     }
 
-    public ImmutableList<Label> getExtraDeps() {
-      return extraDeps;
+    public ImmutableList<Pair<String, Descriptor>> getAttributes() {
+      return attributes;
     }
 
     @Override
@@ -737,9 +753,11 @@
       for (String attributeAspect : skylarkAspect.getAttributeAspects()) {
         builder.attributeAspect(attributeAspect, this);
       }
-      builder.add(attr("$extra_deps", LABEL_LIST).value(skylarkAspect.getExtraDeps()));
+      ImmutableList<Pair<String, Descriptor>> attributes = skylarkAspect.getAttributes();
+      for (Pair<String, Descriptor> attribute : attributes) {
+        builder.add(attribute.second.getAttributeBuilder().build(attribute.first));
+      }
       this.aspectDefinition = builder.build();
-
     }
 
     @Override
diff --git a/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleConfiguredTargetBuilder.java b/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleConfiguredTargetBuilder.java
index 31693fc..ad7ff5e 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleConfiguredTargetBuilder.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleConfiguredTargetBuilder.java
@@ -27,6 +27,7 @@
 import com.google.devtools.build.lib.events.Location;
 import com.google.devtools.build.lib.packages.Rule;
 import com.google.devtools.build.lib.packages.TargetUtils;
+import com.google.devtools.build.lib.rules.SkylarkRuleContext.Kind;
 import com.google.devtools.build.lib.syntax.BaseFunction;
 import com.google.devtools.build.lib.syntax.ClassObject;
 import com.google.devtools.build.lib.syntax.ClassObject.SkylarkClassObject;
@@ -53,7 +54,7 @@
       throws InterruptedException {
     String expectFailure = ruleContext.attributes().get("expect_failure", Type.STRING);
     try (Mutability mutability = Mutability.create("configured target")) {
-      SkylarkRuleContext skylarkRuleContext = new SkylarkRuleContext(ruleContext);
+      SkylarkRuleContext skylarkRuleContext = new SkylarkRuleContext(ruleContext, Kind.RULE);
       Environment env = Environment.builder(mutability)
           .setSkylark()
           .setGlobals(
diff --git a/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleContext.java b/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleContext.java
index 54c9b7f..d335cc4 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleContext.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleContext.java
@@ -13,11 +13,13 @@
 // limitations under the License.
 package com.google.devtools.build.lib.rules;
 
+import com.google.common.base.Function;
 import com.google.common.cache.CacheBuilder;
 import com.google.common.cache.CacheLoader;
 import com.google.common.cache.LoadingCache;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableMap.Builder;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
 import com.google.devtools.build.lib.actions.Artifact;
@@ -34,6 +36,7 @@
 import com.google.devtools.build.lib.analysis.config.FragmentCollection;
 import com.google.devtools.build.lib.cmdline.Label;
 import com.google.devtools.build.lib.collect.nestedset.NestedSet;
+import com.google.devtools.build.lib.events.Location;
 import com.google.devtools.build.lib.packages.Attribute;
 import com.google.devtools.build.lib.packages.Attribute.ConfigurationTransition;
 import com.google.devtools.build.lib.packages.BuildType;
@@ -57,6 +60,7 @@
 import com.google.devtools.build.lib.vfs.PathFragment;
 
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.LinkedList;
 import java.util.List;
@@ -94,6 +98,53 @@
         return Class.forName(classPath);
       }
     });
+  public static final String EXECUTABLE_DOC =
+      "A <code>struct</code> containing executable files defined in label type "
+          + "attributes marked as <code>executable=True</code>. The struct fields correspond "
+          + "to the attribute names. Each struct value is always a <code>file</code>s or "
+          + "<code>None</code>. If an optional attribute is not specified in the rule "
+          + "then the corresponding struct value is <code>None</code>. If a label type is not "
+          + "marked as <code>executable=True</code>, no corresponding struct field is generated.";
+  public static final String FILES_DOC =
+      "A <code>struct</code> containing files defined in label or label list "
+          + "type attributes. The struct fields correspond to the attribute names. The struct "
+          + "values are <code>list</code> of <code>file</code>s. If an optional attribute is "
+          + "not specified in the rule, an empty list is generated."
+          + "It is a shortcut for:"
+          + "<pre class=language-python>[f for t in ctx.attr.<ATTR> for f in t.files]</pre>";
+  public static final String FILE_DOC =
+      "A <code>struct</code> containing files defined in label type "
+          + "attributes marked as <code>single_file=True</code>. The struct fields correspond "
+          + "to the attribute names. The struct value is always a <code>file</code> or "
+          + "<code>None</code>. If an optional attribute is not specified in the rule "
+          + "then the corresponding struct value is <code>None</code>. If a label type is not "
+          + "marked as <code>single_file=True</code>, no corresponding struct field is generated. "
+          + "It is a shortcut for:"
+          + "<pre class=language-python>list(ctx.attr.<ATTR>.files)[0]</pre>";
+  public static final String ATTR_DOC =
+      "A struct to access the values of the attributes. The values are provided by "
+          + "the user (if not, a default value is used).";
+  public static final String OUTPUTS_DOC =
+      "A <code>struct</code> containing all the output files."
+          + " The struct is generated the following way:<br>"
+          + "<ul><li>If the rule is marked as <code>executable=True</code> the struct has an "
+          + "\"executable\" field with the rules default executable <code>file</code> value."
+          + "<li>For every entry in the rule's <code>outputs</code> dict an attr is generated with "
+          + "the same name and the corresponding <code>file</code> value."
+          + "<li>For every output type attribute a struct attribute is generated with the "
+          + "same name and the corresponding <code>file</code> value or <code>None</code>, "
+          + "if no value is specified in the rule."
+          + "<li>For every output list type attribute a struct attribute is generated with the "
+          + "same name and corresponding <code>list</code> of <code>file</code>s value "
+          + "(an empty list if no value is specified in the rule).</ul>";
+  public static final Function<Attribute, Object> ATTRIBUTE_VALUE_EXTRACTOR_FOR_ASPECT =
+      new Function<Attribute, Object>() {
+        @Nullable
+        @Override
+        public Object apply(Attribute attribute) {
+          return attribute.getDefaultValue(null);
+        }
+      };
 
   private final RuleContext ruleContext;
 
@@ -101,94 +152,134 @@
 
   private final FragmentCollection hostFragments;
 
-  // TODO(bazel-team): support configurable attributes.
-  private final SkylarkClassObject attrObject;
-
-  private final SkylarkClassObject outputsObject;
-
-  private final SkylarkClassObject executableObject;
-
-  private final SkylarkClassObject fileObject;
-
-  private final SkylarkClassObject filesObject;
+  private final ImmutableMap<String, String> makeVariables;
+  private final SkylarkRuleAttributesCollection attributesCollection;
+  private final SkylarkRuleAttributesCollection ruleAttributesCollection;
 
   // TODO(bazel-team): we only need this because of the css_binary rule.
-  private final ImmutableMap<Artifact, Label> artifactLabelMap;
+  private final ImmutableMap<Artifact, Label> artifactsLabelMap;
+  private final SkylarkClassObject outputsObject;
 
-  private final ImmutableMap<Artifact, FilesToRunProvider> executableRunfilesMap;
-
-  private final ImmutableMap<String, String> makeVariables;
+  /**
+   * Determines whether this context is for rule implementation or for aspect implementation.
+   */
+  public enum Kind {
+    RULE,
+    ASPECT
+  }
 
   /**
    * Creates a new SkylarkRuleContext using ruleContext.
-   * @throws InterruptedException 
+   * @throws InterruptedException
    */
-  public SkylarkRuleContext(RuleContext ruleContext) throws EvalException, InterruptedException {
+  public SkylarkRuleContext(RuleContext ruleContext, Kind kind)
+      throws EvalException, InterruptedException {
     this.ruleContext = Preconditions.checkNotNull(ruleContext);
     fragments = new FragmentCollection(ruleContext, ConfigurationTransition.NONE);
     hostFragments = new FragmentCollection(ruleContext, ConfigurationTransition.HOST);
 
-    HashMap<String, Object> outputsBuilder = new HashMap<>();
-    if (ruleContext.getRule().getRuleClassObject().outputsDefaultExecutable()) {
-      addOutput(outputsBuilder, "executable", ruleContext.createOutputArtifact());
-    }
-    ImplicitOutputsFunction implicitOutputsFunction =
-        ruleContext.getRule().getRuleClassObject().getImplicitOutputsFunction();
-
-    if (implicitOutputsFunction instanceof SkylarkImplicitOutputsFunction) {
-      SkylarkImplicitOutputsFunction func = (SkylarkImplicitOutputsFunction)
+    if (kind == Kind.RULE) {
+      Collection<Attribute> attributes = ruleContext.getRule().getAttributes();
+      HashMap<String, Object> outputsBuilder = new HashMap<>();
+      if (ruleContext.getRule().getRuleClassObject().outputsDefaultExecutable()) {
+        addOutput(outputsBuilder, "executable", ruleContext.createOutputArtifact());
+      }
+      ImplicitOutputsFunction implicitOutputsFunction =
           ruleContext.getRule().getRuleClassObject().getImplicitOutputsFunction();
-      for (Map.Entry<String, String> entry : func.calculateOutputs(
-          RawAttributeMapper.of(ruleContext.getRule())).entrySet()) {
-        addOutput(outputsBuilder, entry.getKey(),
-            ruleContext.getImplicitOutputArtifact(entry.getValue()));
-      }
-    }
 
-    ImmutableMap.Builder<Artifact, Label> artifactLabelMapBuilder =
-        ImmutableMap.builder();
-    for (Attribute a : ruleContext.getRule().getAttributes()) {
-      String attrName = a.getName();
-      Type<?> type = a.getType();
-      if (type != BuildType.OUTPUT && type != BuildType.OUTPUT_LIST) {
-        continue;
-      }
-      ImmutableList.Builder<Artifact> artifactsBuilder = ImmutableList.builder();
-      for (OutputFile outputFile : ruleContext.getRule().getOutputFileMap().get(attrName)) {
-        Artifact artifact = ruleContext.createOutputArtifact(outputFile);
-        artifactsBuilder.add(artifact);
-        artifactLabelMapBuilder.put(artifact, outputFile.getLabel());
-      }
-      ImmutableList<Artifact> artifacts = artifactsBuilder.build();
-
-      if (type == BuildType.OUTPUT) {
-        if (artifacts.size() == 1) {
-          addOutput(outputsBuilder, attrName, Iterables.getOnlyElement(artifacts));
-        } else {
-          addOutput(outputsBuilder, attrName, Runtime.NONE);
+      if (implicitOutputsFunction instanceof SkylarkImplicitOutputsFunction) {
+        SkylarkImplicitOutputsFunction func =
+            (SkylarkImplicitOutputsFunction)
+                ruleContext.getRule().getRuleClassObject().getImplicitOutputsFunction();
+        for (Map.Entry<String, String> entry :
+            func.calculateOutputs(RawAttributeMapper.of(ruleContext.getRule())).entrySet()) {
+          addOutput(
+              outputsBuilder,
+              entry.getKey(),
+              ruleContext.getImplicitOutputArtifact(entry.getValue()));
         }
-      } else if (type == BuildType.OUTPUT_LIST) {
-        addOutput(outputsBuilder, attrName, new MutableList(artifacts));
-      } else {
-        throw new IllegalArgumentException(
-            "Type of " + attrName + "(" + type + ") is not output type ");
       }
-    }
-    artifactLabelMap = artifactLabelMapBuilder.build();
-    outputsObject =
-        new SkylarkClassObject(
-            outputsBuilder,
-            "No attribute '%s' in outputs. Make sure you declared a rule output with this name.");
 
-    ImmutableMap.Builder<String, Object> attrBuilder = new ImmutableMap.Builder<>();
-    ImmutableMap.Builder<String, Object> executableBuilder = new ImmutableMap.Builder<>();
-    ImmutableMap.Builder<Artifact, FilesToRunProvider> executableRunfilesbuilder =
-        new ImmutableMap.Builder<>();
-    ImmutableMap.Builder<String, Object> fileBuilder = new ImmutableMap.Builder<>();
-    ImmutableMap.Builder<String, Object> filesBuilder = new ImmutableMap.Builder<>();
-    for (Attribute a : ruleContext.getRule().getAttributes()) {
+      Builder<Artifact, Label> artifactLabelMapBuilder = ImmutableMap.builder();
+      for (Attribute a : attributes) {
+        String attrName = a.getName();
+        Type<?> type = a.getType();
+        if (type != BuildType.OUTPUT && type != BuildType.OUTPUT_LIST) {
+          continue;
+        }
+        ImmutableList.Builder<Artifact> artifactsBuilder = ImmutableList.builder();
+        for (OutputFile outputFile : ruleContext.getRule().getOutputFileMap().get(attrName)) {
+          Artifact artifact = ruleContext.createOutputArtifact(outputFile);
+          artifactsBuilder.add(artifact);
+          artifactLabelMapBuilder.put(artifact, outputFile.getLabel());
+        }
+        ImmutableList<Artifact> artifacts = artifactsBuilder.build();
+
+        if (type == BuildType.OUTPUT) {
+          if (artifacts.size() == 1) {
+            addOutput(outputsBuilder, attrName, Iterables.getOnlyElement(artifacts));
+          } else {
+            addOutput(outputsBuilder, attrName, Runtime.NONE);
+          }
+        } else if (type == BuildType.OUTPUT_LIST) {
+          addOutput(outputsBuilder, attrName, new MutableList(artifacts));
+        } else {
+          throw new IllegalArgumentException(
+              "Type of " + attrName + "(" + type + ") is not output type ");
+        }
+      }
+
+      this.artifactsLabelMap = artifactLabelMapBuilder.build();
+      this.outputsObject =
+          new SkylarkClassObject(
+              outputsBuilder,
+              "No attribute '%s' in outputs. Make sure you declared a rule output with this name.");
+
+      this.attributesCollection =
+          buildAttributesCollection(
+              attributes, ruleContext, attributeValueExtractorForRule(ruleContext));
+      this.ruleAttributesCollection = null;
+    } else { // ASPECT
+      this.artifactsLabelMap = ImmutableMap.of();
+      this.outputsObject = null;
+      this.attributesCollection =
+          buildAttributesCollection(
+              ruleContext.getAspectAttributes().values(),
+              ruleContext,
+              ATTRIBUTE_VALUE_EXTRACTOR_FOR_ASPECT);
+      this.ruleAttributesCollection =
+          buildAttributesCollection(
+              ruleContext.getRule().getAttributes(),
+              ruleContext,
+              attributeValueExtractorForRule(ruleContext));
+    }
+
+    makeVariables = ruleContext.getConfigurationMakeVariableContext().collectMakeVariables();
+  }
+
+  private Function<Attribute, Object> attributeValueExtractorForRule(
+      final RuleContext ruleContext) {
+    return new Function<Attribute, Object>() {
+      @Nullable
+      @Override
+      public Object apply(Attribute attribute) {
+        return ruleContext.attributes().get(attribute.getName(), attribute.getType());
+      }
+    };
+  }
+
+  private static SkylarkRuleAttributesCollection buildAttributesCollection(
+      Collection<Attribute> attributes,
+      RuleContext ruleContext,
+      Function<Attribute, Object> attributeValueExtractor) {
+    Builder<String, Object> attrBuilder = new Builder<>();
+    Builder<String, Object> executableBuilder = new Builder<>();
+    Builder<Artifact, FilesToRunProvider> executableRunfilesbuilder = new Builder<>();
+    Builder<String, Object> fileBuilder = new Builder<>();
+    Builder<String, Object> filesBuilder = new Builder<>();
+    for (Attribute a : attributes) {
       Type<?> type = a.getType();
-      Object val = ruleContext.attributes().get(a.getName(), type);
+      Object val = attributeValueExtractor.apply(a);
       if (type != BuildType.LABEL && type != BuildType.LABEL_LIST) {
         attrBuilder.put(a.getPublicName(), val == null ? Runtime.NONE
             // Attribute values should be type safe
@@ -231,28 +322,77 @@
         attrBuilder.put(skyname, new MutableList(allPrereq));
       }
     }
-    attrObject =
-        new SkylarkClassObject(
-            attrBuilder.build(),
-            "No attribute '%s' in attr. Make sure you declared a rule attribute with this name.");
-    executableObject =
-        new SkylarkClassObject(
-            executableBuilder.build(),
-            "No attribute '%s' in executable. Make sure there is a label type attribute marked "
-                + "as 'executable' with this name");
-    fileObject =
-        new SkylarkClassObject(
-            fileBuilder.build(),
-            "No attribute '%s' in file. Make sure there is a label type attribute marked "
-                + "as 'single_file' with this name");
-    filesObject =
-        new SkylarkClassObject(
-            filesBuilder.build(),
-            "No attribute '%s' in files. Make sure there is a label or label_list type attribute "
-                + "with this name");
-    executableRunfilesMap = executableRunfilesbuilder.build();
 
-    makeVariables = ruleContext.getConfigurationMakeVariableContext().collectMakeVariables();
+    return new SkylarkRuleAttributesCollection(
+        attrBuilder.build(),
+        executableBuilder.build(),
+        fileBuilder.build(),
+        filesBuilder.build(),
+        executableRunfilesbuilder.build());
+  }
+
+  @SkylarkModule(
+    name = "rule_attributes",
+    doc = "Information about attributes of a rule an aspect is applied to."
+  )
+  private static class SkylarkRuleAttributesCollection {
+    private final SkylarkClassObject attrObject;
+    private final SkylarkClassObject executableObject;
+    private final SkylarkClassObject fileObject;
+    private final SkylarkClassObject filesObject;
+    private final ImmutableMap<Artifact, FilesToRunProvider> executableRunfilesMap;
+
+    private SkylarkRuleAttributesCollection(
+        ImmutableMap<String, Object> attrs,
+        ImmutableMap<String, Object> executables,
+        ImmutableMap<String, Object> singleFiles,
+        ImmutableMap<String, Object> files,
+        ImmutableMap<Artifact, FilesToRunProvider> executableRunfilesMap) {
+      attrObject =
+          new SkylarkClassObject(
+              attrs,
+              "No attribute '%s' in attr. Make sure you declared a rule attribute with this name.");
+      executableObject =
+          new SkylarkClassObject(
+              executables,
+              "No attribute '%s' in executable. Make sure there is a label type attribute marked "
+                  + "as 'executable' with this name");
+      fileObject =
+          new SkylarkClassObject(
+              singleFiles,
+              "No attribute '%s' in file. Make sure there is a label type attribute marked "
+                  + "as 'single_file' with this name");
+      filesObject =
+          new SkylarkClassObject(
+              files,
+              "No attribute '%s' in files. Make sure there is a label or label_list type attribute "
+                  + "with this name");
+      this.executableRunfilesMap = executableRunfilesMap;
+    }
+
+    @SkylarkCallable(name = "attr", structField = true, doc = ATTR_DOC)
+    public SkylarkClassObject getAttr() {
+      return attrObject;
+    }
+
+    @SkylarkCallable(name = "executable", structField = true, doc = EXECUTABLE_DOC)
+    public SkylarkClassObject getExecutable() {
+      return executableObject;
+    }
+
+    @SkylarkCallable(name = "file", structField = true, doc = FILE_DOC)
+    public SkylarkClassObject getFile() {
+      return fileObject;
+    }
+
+    @SkylarkCallable(name = "files", structField = true, doc = FILES_DOC)
+    public SkylarkClassObject getFiles() {
+      return filesObject;
+    }
+
+    public ImmutableMap<Artifact, FilesToRunProvider> getExecutableRunfilesMap() {
+      return executableRunfilesMap;
+    }
   }
 
   private void addOutput(HashMap<String, Object> outputsBuilder, String key, Object value)
@@ -270,55 +410,33 @@
     return ruleContext;
   }
 
-  @SkylarkCallable(name = "attr", structField = true,
-      doc = "A struct to access the values of the attributes. The values are provided by "
-      + "the user (if not, a default value is used).")
+  @SkylarkCallable(name = "attr", structField = true, doc = ATTR_DOC)
   public SkylarkClassObject getAttr() {
-    return attrObject;
+    return attributesCollection.getAttr();
   }
 
   /**
    * <p>See {@link RuleContext#getExecutablePrerequisite(String, Mode)}.
    */
-  @SkylarkCallable(name = "executable", structField = true,
-      doc = "A <code>struct</code> containing executable files defined in label type "
-          + "attributes marked as <code>executable=True</code>. The struct fields correspond "
-          + "to the attribute names. Each struct value is always a <code>file</code>s or "
-          + "<code>None</code>. If an optional attribute is not specified in the rule "
-          + "then the corresponding struct value is <code>None</code>. If a label type is not "
-          + "marked as <code>executable=True</code>, no corresponding struct field is generated.")
+  @SkylarkCallable(name = "executable", structField = true, doc = EXECUTABLE_DOC)
   public SkylarkClassObject getExecutable() {
-    return executableObject;
+    return attributesCollection.getExecutable();
   }
 
   /**
    * See {@link RuleContext#getPrerequisiteArtifact(String, Mode)}.
    */
-  @SkylarkCallable(name = "file", structField = true,
-      doc = "A <code>struct</code> containing files defined in label type "
-          + "attributes marked as <code>single_file=True</code>. The struct fields correspond "
-          + "to the attribute names. The struct value is always a <code>file</code> or "
-          + "<code>None</code>. If an optional attribute is not specified in the rule "
-          + "then the corresponding struct value is <code>None</code>. If a label type is not "
-          + "marked as <code>single_file=True</code>, no corresponding struct field is generated. "
-          + "It is a shortcut for:"
-          + "<pre class=language-python>list(ctx.attr.<ATTR>.files)[0]</pre>")
+  @SkylarkCallable(name = "file", structField = true, doc = FILE_DOC)
   public SkylarkClassObject getFile() {
-    return fileObject;
+    return attributesCollection.getFile();
   }
 
   /**
    * See {@link RuleContext#getPrerequisiteArtifacts(String, Mode)}.
    */
-  @SkylarkCallable(name = "files", structField = true,
-      doc = "A <code>struct</code> containing files defined in label or label list "
-          + "type attributes. The struct fields correspond to the attribute names. The struct "
-          + "values are <code>list</code> of <code>file</code>s. If an optional attribute is "
-          + "not specified in the rule, an empty list is generated."
-          + "It is a shortcut for:"
-          + "<pre class=language-python>[f for t in ctx.attr.<ATTR> for f in t.files]</pre>")
+  @SkylarkCallable(name = "files", structField = true, doc = FILES_DOC)
   public SkylarkClassObject getFiles() {
-    return filesObject;
+    return attributesCollection.getFiles();
   }
 
   @SkylarkCallable(name = "workspace_name", structField = true,
@@ -368,23 +486,22 @@
     return ruleContext.getHostConfiguration();
   }
 
-  @SkylarkCallable(structField = true,
-      doc = "A <code>struct</code> containing all the output files."
-          + " The struct is generated the following way:<br>"
-          + "<ul><li>If the rule is marked as <code>executable=True</code> the struct has an "
-          + "\"executable\" field with the rules default executable <code>file</code> value."
-          + "<li>For every entry in the rule's <code>outputs</code> dict an attr is generated with "
-          + "the same name and the corresponding <code>file</code> value."
-          + "<li>For every output type attribute a struct attribute is generated with the "
-          + "same name and the corresponding <code>file</code> value or <code>None</code>, "
-          + "if no value is specified in the rule."
-          + "<li>For every output list type attribute a struct attribute is generated with the "
-          + "same name and corresponding <code>list</code> of <code>file</code>s value "
-          + "(an empty list if no value is specified in the rule).</ul>")
-  public SkylarkClassObject outputs() {
+  @SkylarkCallable(structField = true, doc = OUTPUTS_DOC)
+  public SkylarkClassObject outputs() throws EvalException {
+    if (outputsObject == null) {
+      throw new EvalException(Location.BUILTIN, "'outputs' is not defined");
+    }
     return outputsObject;
   }
 
+  @SkylarkCallable(structField = true, doc = "Returns rule attributes descriptor")
+  public SkylarkRuleAttributesCollection rule() throws EvalException {
+    if (ruleAttributesCollection == null) {
+      throw new EvalException(Location.BUILTIN, "'rule' is not defined");
+    }
+    return ruleAttributesCollection;
+  }
+
   @SkylarkCallable(structField = true,
       doc = "Dictionary (String to String) of configuration variables")
   public ImmutableMap<String, String> var() {
@@ -407,16 +524,19 @@
     return new MutableList(options); // no env is provided, so it's effectively immutable
   }
 
-  @SkylarkCallable(doc =
-      "Expands all references to labels embedded within a string for all files using a mapping "
-    + "from definition labels (i.e. the label in the output type attribute) to files. Deprecated.",
-      documented = false)
-  public String expand(@Nullable String expression,
-      SkylarkList artifacts, Label labelResolver) throws EvalException, FuncallException {
+  @SkylarkCallable(
+    doc =
+        "Expands all references to labels embedded within a string for all files using a mapping "
+          + "from definition labels (i.e. the label in the output type attribute) to files. "
+          + "Deprecated.",
+    documented = false
+  )
+  public String expand(@Nullable String expression, SkylarkList artifacts, Label labelResolver)
+      throws EvalException, FuncallException {
     try {
       Map<Label, Iterable<Artifact>> labelMap = new HashMap<>();
       for (Artifact artifact : artifacts.getContents(Artifact.class, "artifacts")) {
-        labelMap.put(artifactLabelMap.get(artifact), ImmutableList.of(artifact));
+        labelMap.put(artifactsLabelMap.get(artifact), ImmutableList.of(artifact));
       }
       return LabelExpander.expand(expression, labelMap, labelResolver);
     } catch (NotUniqueExpansionException e) {
@@ -510,8 +630,9 @@
         });
   }
 
+
   FilesToRunProvider getExecutableRunfiles(Artifact executable) {
-    return executableRunfilesMap.get(executable);
+    return attributesCollection.getExecutableRunfilesMap().get(executable);
   }
 
   @SkylarkCallable(name = "info_file", structField = true, documented = false,
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/SkylarkAspectFactory.java b/src/main/java/com/google/devtools/build/lib/skyframe/SkylarkAspectFactory.java
index 794d746..c2d489c 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/SkylarkAspectFactory.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/SkylarkAspectFactory.java
@@ -24,6 +24,7 @@
 import com.google.devtools.build.lib.packages.AspectParameters;
 import com.google.devtools.build.lib.rules.SkylarkRuleClassFunctions.SkylarkAspect;
 import com.google.devtools.build.lib.rules.SkylarkRuleContext;
+import com.google.devtools.build.lib.rules.SkylarkRuleContext.Kind;
 import com.google.devtools.build.lib.syntax.ClassObject.SkylarkClassObject;
 import com.google.devtools.build.lib.syntax.Environment;
 import com.google.devtools.build.lib.syntax.EvalException;
@@ -50,7 +51,7 @@
     try (Mutability mutability = Mutability.create("aspect")) {
       SkylarkRuleContext skylarkRuleContext;
       try {
-        skylarkRuleContext = new SkylarkRuleContext(ruleContext);
+        skylarkRuleContext = new SkylarkRuleContext(ruleContext, Kind.ASPECT);
       } catch (EvalException e) {
         ruleContext.ruleError(e.getMessage());
         return null;
diff --git a/src/test/java/com/google/devtools/build/lib/skylark/SkylarkAspectsTest.java b/src/test/java/com/google/devtools/build/lib/skylark/SkylarkAspectsTest.java
index 3ea5106..ab9c57a 100644
--- a/src/test/java/com/google/devtools/build/lib/skylark/SkylarkAspectsTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skylark/SkylarkAspectsTest.java
@@ -90,7 +90,7 @@
         "test/aspect.bzl",
         "def _impl(target, ctx):",
         "   s = set([target.label])",
-        "   for i in ctx.attr.deps:",
+        "   for i in ctx.rule.attr.deps:",
         "       s += i.target_labels",
         "   return struct(target_labels = s)",
         "",
@@ -154,7 +154,7 @@
         "test/aspect.bzl",
         "def _aspect_impl(target, ctx):",
         "   s = set([target.label])",
-        "   for i in ctx.attr.deps:",
+        "   for i in ctx.rule.attr.deps:",
         "       s += i.target_labels",
         "   return struct(target_labels = s)",
         "",
diff --git a/src/test/java/com/google/devtools/build/lib/skylark/SkylarkRuleClassFunctionsTest.java b/src/test/java/com/google/devtools/build/lib/skylark/SkylarkRuleClassFunctionsTest.java
index 1dc7896..717a132 100644
--- a/src/test/java/com/google/devtools/build/lib/skylark/SkylarkRuleClassFunctionsTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skylark/SkylarkRuleClassFunctionsTest.java
@@ -33,6 +33,7 @@
 import com.google.devtools.build.lib.packages.RuleClass;
 import com.google.devtools.build.lib.packages.RuleClass.Builder.RuleClassType;
 import com.google.devtools.build.lib.rules.SkylarkAttr;
+import com.google.devtools.build.lib.rules.SkylarkAttr.Descriptor;
 import com.google.devtools.build.lib.rules.SkylarkFileType;
 import com.google.devtools.build.lib.rules.SkylarkRuleClassFunctions;
 import com.google.devtools.build.lib.rules.SkylarkRuleClassFunctions.RuleFunction;
@@ -41,6 +42,7 @@
 import com.google.devtools.build.lib.syntax.EvalException;
 import com.google.devtools.build.lib.syntax.Type;
 import com.google.devtools.build.lib.util.FileTypeSet;
+import com.google.devtools.build.lib.util.Pair;
 
 import org.junit.Assert;
 import org.junit.Before;
@@ -211,11 +213,35 @@
         "def _impl(target, ctx):",
         "   pass",
         "my_aspect = aspect(_impl,",
-        "   extra_deps=['//foo/bar:baz']",
-        ")"
-    );
+        "   attrs = { '_extra_deps' : attr.label(default = Label('//foo/bar:baz')) }",
+        ")");
     SkylarkAspect aspect = (SkylarkAspect) ev.lookup("my_aspect");
-    assertThat(aspect.getExtraDeps()).containsExactly(Label.parseAbsolute("//foo/bar:baz"));
+    Pair<String, Descriptor> pair = Iterables.getOnlyElement(aspect.getAttributes());
+    assertThat(pair.first).isEqualTo("$extra_deps");
+    assertThat(pair.second.getAttributeBuilder().build("$extra_deps").getDefaultValue(null))
+        .isEqualTo(Label.parseAbsolute("//foo/bar:baz"));
+  }
+
+  @Test
+  public void testAspectNonImplicitAttribute() throws Exception {
+    checkErrorContains(
+        "Aspect attribute 'extra_deps' must be implicit (its name should start with '_')",
+        "def _impl(target, ctx):",
+        "   pass",
+        "my_aspect = aspect(_impl,",
+        "   attrs = { 'extra_deps' : attr.label(default = Label('//foo/bar:baz')) }",
+        ")");
+  }
+
+  @Test
+  public void testAspectNoDefaultValueAttribute() throws Exception {
+    checkErrorContains(
+        "Aspect attribute '_extra_deps' has no default value",
+        "def _impl(target, ctx):",
+        "   pass",
+        "my_aspect = aspect(_impl,",
+        "   attrs = { '_extra_deps' : attr.label() }",
+        ")");
   }
 
   @Test
diff --git a/src/test/java/com/google/devtools/build/lib/skylark/util/SkylarkTestCase.java b/src/test/java/com/google/devtools/build/lib/skylark/util/SkylarkTestCase.java
index 670029f..bc54518 100644
--- a/src/test/java/com/google/devtools/build/lib/skylark/util/SkylarkTestCase.java
+++ b/src/test/java/com/google/devtools/build/lib/skylark/util/SkylarkTestCase.java
@@ -26,6 +26,7 @@
 import com.google.devtools.build.lib.packages.PackageFactory.PackageContext;
 import com.google.devtools.build.lib.rules.SkylarkModules;
 import com.google.devtools.build.lib.rules.SkylarkRuleContext;
+import com.google.devtools.build.lib.rules.SkylarkRuleContext.Kind;
 import com.google.devtools.build.lib.syntax.Environment;
 import com.google.devtools.build.lib.syntax.EvalException;
 import com.google.devtools.build.lib.syntax.util.EvaluationTestCase;
@@ -90,7 +91,7 @@
   }
 
   protected SkylarkRuleContext createRuleContext(String label) throws Exception {
-    return new SkylarkRuleContext(getRuleContextForSkylark(getConfiguredTarget(label)));
+    return new SkylarkRuleContext(getRuleContextForSkylark(getConfiguredTarget(label)), Kind.RULE);
   }
 
   protected Object evalRuleContextCode(String... lines) throws Exception {