Add an API for generating analyzer summaries in one step.

Currently, analyzer summaries are generated in a two-step process,
translating compilation units into unlinked summaries and then linking
the unlinked summaries together to form linked summaries.  In order to
support full unrestricted type inference, we'll need to build
summaries in one step, so that the full AST of initializers is
available for inference during linking.

This CL introduces a new API for one-step summarizing, along with test
cases to exercise it.  For now, the one-step summary logic just
invokes the old two-step summary process.  In future CLs I'll rework
it to be a true one-step summarizer so that the type inference
restrictions can be lifted.

Change-Id: Ic8d55850972f4697b5c6cc6fabe5d26dc7c1288c
Reviewed-on: https://dart-review.googlesource.com/73300
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Paul Berry <paulberry@google.com>
diff --git a/pkg/analyzer/lib/src/summary/one_phase.dart b/pkg/analyzer/lib/src/summary/one_phase.dart
new file mode 100644
index 0000000..0775161
--- /dev/null
+++ b/pkg/analyzer/lib/src/summary/one_phase.dart
@@ -0,0 +1,64 @@
+// Copyright (c) 2018, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer/src/summary/format.dart';
+import 'package:analyzer/src/summary/idl.dart';
+import 'package:analyzer/src/summary/link.dart';
+import 'package:analyzer/src/summary/package_bundle_reader.dart';
+import 'package:analyzer/src/summary/prelink.dart';
+import 'package:analyzer/src/summary/summarize_ast.dart';
+import 'package:analyzer/src/summary/summarize_elements.dart';
+
+/// Builds the summary for a build unit based on unresolved ASTs of its
+/// compilation units.
+///
+/// The compilation units in [uriToUnit] are summarized, and the results are
+/// stored in [assembler].  References to other compilation units are resolved
+/// using the summaries stored in [dependencies].
+///
+/// [getDeclaredVariable] is used to resolve configurable imports.  If
+/// [allowMissingFiles] is `false`, then failure to resolve an import will
+/// result in an exception being thrown; otherwise unresolved imports will be
+/// silently recovered from.
+void summarize(
+    Map<String, CompilationUnit> uriToUnit,
+    SummaryDataStore dependencies,
+    PackageBundleAssembler assembler,
+    GetDeclaredVariable getDeclaredVariable,
+    bool allowMissingFiles) {
+  var uriToUnlinked = <String, UnlinkedUnitBuilder>{};
+  uriToUnit.forEach((uri, compilationUnit) {
+    var unlinkedUnit = serializeAstUnlinked(compilationUnit);
+    uriToUnlinked[uri] = unlinkedUnit;
+    assembler.addUnlinkedUnitViaUri(uri, unlinkedUnit);
+  });
+
+  LinkedLibrary getDependency(String absoluteUri) {
+    var dependency = dependencies.linkedMap[absoluteUri];
+    if (dependency == null && !allowMissingFiles) {
+      throw new StateError('Missing dependency $absoluteUri');
+    }
+    return dependency;
+  }
+
+  UnlinkedUnit getUnit(String absoluteUri) {
+    if (absoluteUri == null) {
+      return null;
+    }
+    var unlinkedUnit =
+        uriToUnlinked[absoluteUri] ?? dependencies.unlinkedMap[absoluteUri];
+    if (unlinkedUnit == null && !allowMissingFiles) {
+      throw new StateError('Missing unit $absoluteUri');
+    }
+    return unlinkedUnit;
+  }
+
+  // TODO(paulberry): is this bad?  Are we passing parts to link that we
+  // shouldn't?
+  var linkedLibraries = link(
+      uriToUnlinked.keys.toSet(), getDependency, getUnit, getDeclaredVariable);
+
+  linkedLibraries.forEach(assembler.addLinkedLibrary);
+}
diff --git a/pkg/analyzer/test/src/summary/summarize_ast_one_phase_test.dart b/pkg/analyzer/test/src/summary/summarize_ast_one_phase_test.dart
new file mode 100644
index 0000000..10863d3
--- /dev/null
+++ b/pkg/analyzer/test/src/summary/summarize_ast_one_phase_test.dart
@@ -0,0 +1,108 @@
+// Copyright (c) 2018, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import 'summary_common.dart';
+import 'test_strategies.dart';
+
+main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(SummarizeAstOnePhaseTest);
+  });
+}
+
+@reflectiveTest
+class SummarizeAstOnePhaseTest extends SummaryBlackBoxTestStrategyOnePhase
+    with SummaryTestCases {
+  @override
+  @failingTest
+  test_bottom_reference_shared() {
+    super.test_bottom_reference_shared();
+  }
+
+  @override
+  @failingTest
+  test_closure_executable_with_imported_return_type() {
+    super.test_closure_executable_with_imported_return_type();
+  }
+
+  @override
+  @failingTest
+  test_closure_executable_with_return_type_from_closure() {
+    super.test_closure_executable_with_return_type_from_closure();
+  }
+
+  @override
+  @failingTest
+  test_closure_executable_with_unimported_return_type() {
+    super.test_closure_executable_with_unimported_return_type();
+  }
+
+  @override
+  @failingTest
+  test_implicit_dependencies_follow_other_dependencies() {
+    super.test_implicit_dependencies_follow_other_dependencies();
+  }
+
+  @override
+  @failingTest
+  test_inferred_type_refers_to_function_typed_param_of_typedef() {
+    super.test_inferred_type_refers_to_function_typed_param_of_typedef();
+  }
+
+  @override
+  @failingTest
+  test_inferred_type_refers_to_nested_function_typed_param() {
+    super.test_inferred_type_refers_to_nested_function_typed_param();
+  }
+
+  @override
+  @failingTest
+  test_inferred_type_refers_to_nested_function_typed_param_named() {
+    super.test_inferred_type_refers_to_nested_function_typed_param_named();
+  }
+
+  @override
+  @failingTest
+  test_initializer_executable_with_bottom_return_type() {
+    super.test_initializer_executable_with_bottom_return_type();
+  }
+
+  @override
+  @failingTest
+  test_initializer_executable_with_imported_return_type() {
+    super.test_initializer_executable_with_imported_return_type();
+  }
+
+  @override
+  @failingTest
+  test_initializer_executable_with_return_type_from_closure() {
+    super.test_initializer_executable_with_return_type_from_closure();
+  }
+
+  @override
+  @failingTest
+  test_initializer_executable_with_return_type_from_closure_field() {
+    super.test_initializer_executable_with_return_type_from_closure_field();
+  }
+
+  @override
+  @failingTest
+  test_initializer_executable_with_unimported_return_type() {
+    super.test_initializer_executable_with_unimported_return_type();
+  }
+
+  @override
+  @failingTest
+  test_syntheticFunctionType_genericClosure() {
+    super.test_syntheticFunctionType_genericClosure();
+  }
+
+  @override
+  @failingTest
+  test_syntheticFunctionType_inGenericClass() {
+    super.test_syntheticFunctionType_inGenericClass();
+  }
+}
diff --git a/pkg/analyzer/test/src/summary/test_all.dart b/pkg/analyzer/test/src/summary/test_all.dart
index 1ffc606..fd2f7a4 100644
--- a/pkg/analyzer/test/src/summary/test_all.dart
+++ b/pkg/analyzer/test/src/summary/test_all.dart
@@ -11,6 +11,7 @@
 import 'package_bundle_reader_test.dart' as package_bundle_reader_test;
 import 'prelinker_test.dart' as prelinker_test;
 import 'resynthesize_ast_test.dart' as resynthesize_ast_test;
+import 'summarize_ast_one_phase_test.dart' as summarize_ast_one_phase_test;
 import 'summarize_ast_strong_test.dart' as summarize_ast_strong_test;
 import 'top_level_inference_test.dart' as top_level_inference_test;
 
@@ -23,6 +24,7 @@
     package_bundle_reader_test.main();
     prelinker_test.main();
     resynthesize_ast_test.main();
+    summarize_ast_one_phase_test.main();
     summarize_ast_strong_test.main();
     top_level_inference_test.main();
   }, name: 'summary');
diff --git a/pkg/analyzer/test/src/summary/test_strategies.dart b/pkg/analyzer/test/src/summary/test_strategies.dart
index 01a5b13..b12f5da 100644
--- a/pkg/analyzer/test/src/summary/test_strategies.dart
+++ b/pkg/analyzer/test/src/summary/test_strategies.dart
@@ -13,6 +13,7 @@
 import 'package:analyzer/src/summary/format.dart';
 import 'package:analyzer/src/summary/idl.dart';
 import 'package:analyzer/src/summary/link.dart';
+import 'package:analyzer/src/summary/one_phase.dart';
 import 'package:analyzer/src/summary/package_bundle_reader.dart';
 import 'package:analyzer/src/summary/prelink.dart';
 import 'package:analyzer/src/summary/summarize_ast.dart';
@@ -30,6 +31,55 @@
   return posix.toUri(absolutePath).toString();
 }
 
+CompilationUnit _parseText(String text) {
+  CharSequenceReader reader = new CharSequenceReader(text);
+  Scanner scanner =
+      new Scanner(null, reader, AnalysisErrorListener.NULL_LISTENER);
+  Token token = scanner.tokenize();
+  Parser parser = new Parser(
+      NonExistingSource.unknown, AnalysisErrorListener.NULL_LISTENER);
+  (parser as ParserAdapter).fastaParser.isMixinSupportEnabled = true;
+  CompilationUnit unit = parser.parseCompilationUnit(token);
+  unit.lineInfo = new LineInfo(scanner.lineStarts);
+  return unit;
+}
+
+/**
+ * Verify invariants of the given [linkedLibrary].
+ */
+void _validateLinkedLibrary(LinkedLibrary linkedLibrary) {
+  for (LinkedUnit unit in linkedLibrary.units) {
+    for (LinkedReference reference in unit.references) {
+      switch (reference.kind) {
+        case ReferenceKind.classOrEnum:
+        case ReferenceKind.topLevelPropertyAccessor:
+        case ReferenceKind.topLevelFunction:
+        case ReferenceKind.typedef:
+          // This reference can have either a zero or a nonzero dependency,
+          // since it refers to top level element which might or might not be
+          // imported from another library.
+          break;
+        case ReferenceKind.prefix:
+          // Prefixes should have a dependency of 0, since they come from the
+          // current library.
+          expect(reference.dependency, 0,
+              reason: 'Nonzero dependency for prefix');
+          break;
+        case ReferenceKind.unresolved:
+          // Unresolved references always have a dependency of 0.
+          expect(reference.dependency, 0,
+              reason: 'Nonzero dependency for undefined');
+          break;
+        default:
+          // This reference should have a dependency of 0, since it refers to
+          // an element that is contained within some other element.
+          expect(reference.dependency, 0,
+              reason: 'Nonzero dependency for ${reference.kind}');
+      }
+    }
+  }
+}
+
 /**
  * [SerializedMockSdk] is a singleton class representing the result of
  * serializing the mock SDK to summaries.  It is computed once and then shared
@@ -119,6 +169,96 @@
 }
 
 /// Implementation of [SummaryBlackBoxTestStrategy] that drives summary
+/// generation using the new one-phase API.
+class SummaryBlackBoxTestStrategyOnePhase
+    implements SummaryBlackBoxTestStrategy {
+  /// Information about the files to be summarized.
+  final _filesToSummarize = _FilesToLink<CompilationUnit>();
+
+  final _testUriString = absUri('/test.dart');
+
+  bool _allowMissingFiles = false;
+
+  @override
+  LinkedLibrary linked;
+
+  @override
+  List<UnlinkedUnit> unlinkedUnits;
+
+  SummaryBlackBoxTestStrategyOnePhase() {
+    // TODO(paulberry): cache the bundle?
+    _filesToSummarize.summaryDataStore
+        .addBundle(null, new MockSdk().getLinkedBundle());
+  }
+
+  @override
+  void set allowMissingFiles(bool value) {
+    _allowMissingFiles = value;
+  }
+
+  @override
+  bool get skipFullyLinkedData => false;
+
+  @override
+  void addNamedSource(String filePath, String contents) {
+    _filesToSummarize.uriToUnit[absUri(filePath)] = _parseText(contents);
+  }
+
+  @override
+  void serializeLibraryText(String text, {bool allowErrors = false}) {
+    addNamedSource('/test.dart', text);
+    var assembler = PackageBundleAssembler();
+    summarize(_filesToSummarize.uriToUnit, _filesToSummarize.summaryDataStore,
+        assembler, (name) => null, _allowMissingFiles);
+    var result = assembler.assemble();
+    linked = _findLinkedLibrary(result, _testUriString);
+    unlinkedUnits =
+        _findUnlinkedUnits(result, _testUriString, _allowMissingFiles);
+  }
+
+  static LinkedLibrary _findLinkedLibrary(
+      PackageBundle bundle, String uriString) {
+    for (int i = 0; i < bundle.linkedLibraryUris.length; i++) {
+      if (bundle.linkedLibraryUris[i] == uriString) {
+        return bundle.linkedLibraries[i];
+      }
+    }
+    throw new StateError('LinkedLibrary $uriString not found in bundle');
+  }
+
+  static List<UnlinkedUnit> _findUnlinkedUnits(
+      PackageBundle bundle, String uriString, bool allowMissingFiles) {
+    var uriToUnlinkedUnit = <String, UnlinkedUnit>{};
+    for (int i = 0; i < bundle.unlinkedUnitUris.length; i++) {
+      uriToUnlinkedUnit[bundle.unlinkedUnitUris[i]] = bundle.unlinkedUnits[i];
+    }
+    var unlinkedDefiningUnit = uriToUnlinkedUnit[uriString];
+    var unlinkedUnits = <UnlinkedUnit>[unlinkedDefiningUnit];
+    var definingUnitUri = Uri.parse(uriString);
+    for (String relativeUriStr in unlinkedDefiningUnit.publicNamespace.parts) {
+      Uri relativeUri;
+      try {
+        relativeUri = Uri.parse(relativeUriStr);
+      } on FormatException {
+        unlinkedUnits.add(new UnlinkedUnitBuilder());
+        continue;
+      }
+
+      UnlinkedUnit unit = uriToUnlinkedUnit[
+          resolveRelativeUri(definingUnitUri, relativeUri).toString()];
+      if (unit == null) {
+        if (!allowMissingFiles) {
+          fail('Test referred to unknown unit $relativeUriStr');
+        }
+      } else {
+        unlinkedUnits.add(unit);
+      }
+    }
+    return unlinkedUnits;
+  }
+}
+
+/// Implementation of [SummaryBlackBoxTestStrategy] that drives summary
 /// generation using the old two-phase API, and exercises the pre-linker only.
 class SummaryBlackBoxTestStrategyPrelink
     extends _SummaryBlackBoxTestStrategyTwoPhase
@@ -343,6 +483,9 @@
     _filesToLink.uriToUnit[absUri(filePath)] = unlinkedUnit;
   }
 
+  UnlinkedUnitBuilder createUnlinkedSummary(Uri uri, String text) =>
+      serializeAstUnlinked(_parseText(text));
+
   _LinkerInputs _createLinkerInputs(String text,
       {String path: '/test.dart', String uri}) {
     uri ??= absUri(path);
@@ -361,22 +504,6 @@
     _filesToLink = new _FilesToLink<UnlinkedUnitBuilder>();
     return linkerInputs;
   }
-
-  UnlinkedUnitBuilder createUnlinkedSummary(Uri uri, String text) =>
-      serializeAstUnlinked(_parseText(text));
-}
-
-CompilationUnit _parseText(String text) {
-  CharSequenceReader reader = new CharSequenceReader(text);
-  Scanner scanner =
-      new Scanner(null, reader, AnalysisErrorListener.NULL_LISTENER);
-  Token token = scanner.tokenize();
-  Parser parser = new Parser(
-      NonExistingSource.unknown, AnalysisErrorListener.NULL_LISTENER);
-  (parser as ParserAdapter).fastaParser.isMixinSupportEnabled = true;
-  CompilationUnit unit = parser.parseCompilationUnit(token);
-  unit.lineInfo = new LineInfo(scanner.lineStarts);
-  return unit;
 }
 
 /// Implementation of [SummaryBlackBoxTestStrategy] that drives summary
@@ -435,40 +562,4 @@
       }
     }
   }
-
-  /**
-   * Verify invariants of the given [linkedLibrary].
-   */
-  void _validateLinkedLibrary(LinkedLibrary linkedLibrary) {
-    for (LinkedUnit unit in linkedLibrary.units) {
-      for (LinkedReference reference in unit.references) {
-        switch (reference.kind) {
-          case ReferenceKind.classOrEnum:
-          case ReferenceKind.topLevelPropertyAccessor:
-          case ReferenceKind.topLevelFunction:
-          case ReferenceKind.typedef:
-            // This reference can have either a zero or a nonzero dependency,
-            // since it refers to top level element which might or might not be
-            // imported from another library.
-            break;
-          case ReferenceKind.prefix:
-            // Prefixes should have a dependency of 0, since they come from the
-            // current library.
-            expect(reference.dependency, 0,
-                reason: 'Nonzero dependency for prefix');
-            break;
-          case ReferenceKind.unresolved:
-            // Unresolved references always have a dependency of 0.
-            expect(reference.dependency, 0,
-                reason: 'Nonzero dependency for undefined');
-            break;
-          default:
-            // This reference should have a dependency of 0, since it refers to
-            // an element that is contained within some other element.
-            expect(reference.dependency, 0,
-                reason: 'Nonzero dependency for ${reference.kind}');
-        }
-      }
-    }
-  }
 }